test-pmd-rule 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ import { argv } from "process";
7
7
  import { cpus } from "os";
8
8
 
9
9
  // src/tester/RuleTester.ts
10
- import { readFileSync as readFileSync3, existsSync } from "fs";
10
+ import { readFileSync as readFileSync4, existsSync } from "fs";
11
11
  import { DOMParser as DOMParser3 } from "@xmldom/xmldom";
12
12
 
13
13
  // src/xpath/extractXPath.ts
@@ -63,8 +63,8 @@ import { readFileSync as readFileSync2 } from "fs";
63
63
 
64
64
  // src/xpath/extractors/extractNodeTypes.ts
65
65
  function extractNodeTypes(xpath) {
66
- const MIN_STRING_LENGTH5 = 0;
67
- if (xpath.length === MIN_STRING_LENGTH5) return [];
66
+ const MIN_STRING_LENGTH8 = 0;
67
+ if (xpath.length === MIN_STRING_LENGTH8) return [];
68
68
  const nodeTypes = /* @__PURE__ */ new Set();
69
69
  const nodeTypeMatches1 = xpath.matchAll(
70
70
  /(?:\.\/\/|\/\/|\s|\/|\(|\[|,|\|)([A-Z][a-zA-Z]*(?:Statement|Expression|Declaration|Node|Block))(?=\s|$|\[|\(|\/|\)|,|\||]|or|and|not|return|let)/g
@@ -152,8 +152,8 @@ function extractAttributes(xpath) {
152
152
 
153
153
  // src/xpath/extractors/extractConditionals.ts
154
154
  function extractConditionals(xpath) {
155
- const MIN_STRING_LENGTH5 = 0;
156
- if (xpath.length === MIN_STRING_LENGTH5) return [];
155
+ const MIN_STRING_LENGTH8 = 0;
156
+ if (xpath.length === MIN_STRING_LENGTH8) return [];
157
157
  const conditionals = [];
158
158
  const MATCH_INDEX3 = 1;
159
159
  const notMatches = xpath.matchAll(/not\s*\(([^)]+)\)/g);
@@ -1022,6 +1022,34 @@ import tmp from "tmp";
1022
1022
  var EMPTY_LENGTH = 0;
1023
1023
  var FIRST_CAPTURE_GROUP_INDEX = 1;
1024
1024
  var SECOND_CAPTURE_GROUP_INDEX = 2;
1025
+ var TAB_CHAR = " ";
1026
+ var SPACE_CHAR = " ";
1027
+ var DEFAULT_INDENT = " ";
1028
+ var FIRST_ELEMENT_INDEX2 = 0;
1029
+ function removeInlineMarkers(line) {
1030
+ if (line.includes("// \u274C")) {
1031
+ const splitResult = line.split("// \u274C");
1032
+ const codeLine = splitResult[FIRST_ELEMENT_INDEX2];
1033
+ return codeLine.replace(/\s+$/, "");
1034
+ }
1035
+ if (line.includes("// \u2705")) {
1036
+ const splitResult = line.split("// \u2705");
1037
+ const codeLine = splitResult[FIRST_ELEMENT_INDEX2];
1038
+ return codeLine.replace(/\s+$/, "");
1039
+ }
1040
+ return line;
1041
+ }
1042
+ function formatMethodDeclarationIndent(methodDecl) {
1043
+ const startsWithTab = methodDecl.startsWith(TAB_CHAR);
1044
+ if (startsWithTab) {
1045
+ return methodDecl;
1046
+ }
1047
+ const startsWithSpace = methodDecl.startsWith(SPACE_CHAR);
1048
+ if (startsWithSpace) {
1049
+ return methodDecl;
1050
+ }
1051
+ return `${DEFAULT_INDENT}${methodDecl}`;
1052
+ }
1025
1053
  function inferReturnType(code, methodName) {
1026
1054
  if (new RegExp(`\\b${methodName}\\s*\\(\\s*\\)\\s*[><=!]+\\s*\\d+`).test(
1027
1055
  code
@@ -1256,8 +1284,15 @@ function createTestFile({
1256
1284
  const extractedCode = [];
1257
1285
  let insideClass = false;
1258
1286
  let classBraceDepth = ZERO_BRACE_DEPTH;
1287
+ const INITIAL_BRACE_DEPTH = 1;
1288
+ let methodBraceDepth = ZERO_BRACE_DEPTH;
1259
1289
  let currentMode = null;
1260
- for (const line of exampleLines) {
1290
+ let insideMethod = false;
1291
+ let methodDeclaration = null;
1292
+ let methodDeclarationOriginal = null;
1293
+ let hasIncludedMethodContent = false;
1294
+ for (let lineIndex = 0; lineIndex < exampleLines.length; lineIndex++) {
1295
+ const line = exampleLines[lineIndex];
1261
1296
  const trimmed = line.trim();
1262
1297
  if (trimmed.includes("// \u274C")) {
1263
1298
  currentMode = "violation";
@@ -1270,7 +1305,6 @@ function createTestFile({
1270
1305
  }
1271
1306
  const shouldInclude = !trimmed.startsWith("//") && trimmed.length > EMPTY_LENGTH && (includeViolations && currentMode === "violation" || includeValids && currentMode === "valid");
1272
1307
  if (trimmed.startsWith("public class ") || trimmed.startsWith("class ") || trimmed.startsWith("private class ")) {
1273
- const INITIAL_BRACE_DEPTH = 1;
1274
1308
  const CLASS_PREFIX_GROUP_INDEX = 1;
1275
1309
  if (classBraceDepth === ZERO_BRACE_DEPTH) {
1276
1310
  const classRegex = /^(public\s+|private\s+)?class\s+(\w+)/;
@@ -1290,33 +1324,64 @@ function createTestFile({
1290
1324
  const closeBracesMatch = line.match(/}/g);
1291
1325
  const openBraces = openBracesMatch ? openBracesMatch.length : ZERO_BRACE_DEPTH;
1292
1326
  const closeBraces = closeBracesMatch ? closeBracesMatch.length : ZERO_BRACE_DEPTH;
1327
+ const prevClassBraceDepth = classBraceDepth;
1293
1328
  classBraceDepth += openBraces - closeBraces;
1329
+ const methodRegex = /\w+\s*\(/;
1330
+ const methodMatch = methodRegex.exec(trimmed);
1331
+ const hasMethodPattern = methodMatch !== null;
1332
+ const isMethodDeclaration = prevClassBraceDepth === INITIAL_BRACE_DEPTH && classBraceDepth > INITIAL_BRACE_DEPTH && !trimmed.startsWith("{") && trimmed.includes("{") && (trimmed.includes("void") || trimmed.includes("(") || hasMethodPattern);
1333
+ if (isMethodDeclaration) {
1334
+ insideMethod = true;
1335
+ methodBraceDepth = classBraceDepth - INITIAL_BRACE_DEPTH;
1336
+ methodDeclaration = trimmed;
1337
+ methodDeclarationOriginal = line;
1338
+ hasIncludedMethodContent = false;
1339
+ }
1340
+ if (insideMethod) {
1341
+ methodBraceDepth += openBraces - closeBraces;
1342
+ if (methodBraceDepth <= ZERO_BRACE_DEPTH) {
1343
+ insideMethod = false;
1344
+ methodDeclaration = null;
1345
+ methodDeclarationOriginal = null;
1346
+ hasIncludedMethodContent = false;
1347
+ }
1348
+ }
1294
1349
  if (shouldInclude || trimmed === "{" || trimmed === "}") {
1295
- let codeLine = line;
1296
- const FIRST_ELEMENT_INDEX2 = 0;
1297
- if (line.includes("// \u274C")) {
1298
- const splitResult = line.split("// \u274C");
1299
- codeLine = splitResult[FIRST_ELEMENT_INDEX2].trim();
1300
- } else if (line.includes("// \u2705")) {
1301
- const splitResult = line.split("// \u2705");
1302
- codeLine = splitResult[FIRST_ELEMENT_INDEX2].trim();
1350
+ const hasMethodDeclaration = methodDeclaration !== null && methodDeclarationOriginal !== null;
1351
+ const markerRegex = /\s*\/\/\s*[❌✅].*$/;
1352
+ const trimmedWithoutMarker = trimmed.replace(markerRegex, "").trim();
1353
+ const isMethodDeclarationLine = insideMethod && hasMethodDeclaration && (trimmed === methodDeclaration || trimmedWithoutMarker === methodDeclaration);
1354
+ if (shouldInclude && insideMethod && hasMethodDeclaration && !hasIncludedMethodContent) {
1355
+ if (isMethodDeclarationLine) {
1356
+ const codeLine = removeInlineMarkers(line);
1357
+ extractedCode.push(codeLine);
1358
+ hasIncludedMethodContent = true;
1359
+ } else {
1360
+ const methodDeclOriginal = methodDeclarationOriginal;
1361
+ const methodDeclWithIndent = formatMethodDeclarationIndent(
1362
+ methodDeclOriginal
1363
+ );
1364
+ extractedCode.push(methodDeclWithIndent);
1365
+ const codeLine = removeInlineMarkers(line);
1366
+ extractedCode.push(codeLine);
1367
+ hasIncludedMethodContent = true;
1368
+ }
1369
+ } else if (shouldInclude && !isMethodDeclarationLine) {
1370
+ const codeLine = removeInlineMarkers(line);
1371
+ extractedCode.push(codeLine);
1372
+ } else {
1373
+ extractedCode.push(line);
1303
1374
  }
1304
- extractedCode.push(codeLine);
1305
1375
  }
1306
1376
  if (classBraceDepth <= ZERO_BRACE_DEPTH) {
1307
1377
  insideClass = false;
1308
1378
  classBraceDepth = ZERO_BRACE_DEPTH;
1379
+ insideMethod = false;
1380
+ methodDeclaration = null;
1381
+ methodDeclarationOriginal = null;
1309
1382
  }
1310
1383
  } else if (shouldInclude) {
1311
- let codeLine = line;
1312
- const FIRST_ELEMENT_INDEX2 = 0;
1313
- if (line.includes("// \u274C")) {
1314
- const splitResult = line.split("// \u274C");
1315
- codeLine = splitResult[FIRST_ELEMENT_INDEX2].trim();
1316
- } else if (line.includes("// \u2705")) {
1317
- const splitResult = line.split("// \u2705");
1318
- codeLine = splitResult[FIRST_ELEMENT_INDEX2].trim();
1319
- }
1384
+ const codeLine = removeInlineMarkers(line).trim();
1320
1385
  extractedCode.push(codeLine);
1321
1386
  }
1322
1387
  }
@@ -1734,7 +1799,7 @@ function checkDuplicates(examples) {
1734
1799
 
1735
1800
  // src/tester/qualityChecks.ts
1736
1801
  var MIN_ISSUES_COUNT = 0;
1737
- function runQualityChecks(ruleMetadata, examples) {
1802
+ function runQualityChecks(ruleFilePath, ruleMetadata, examples) {
1738
1803
  const issues = [];
1739
1804
  const warnings = [];
1740
1805
  const metadataResult = checkRuleMetadata(ruleMetadata);
@@ -1753,6 +1818,595 @@ function runQualityChecks(ruleMetadata, examples) {
1753
1818
  };
1754
1819
  }
1755
1820
 
1821
+ // src/tester/quality/checkQualityChecks.ts
1822
+ import { readFileSync as readFileSync3 } from "fs";
1823
+
1824
+ // src/xpath/extractors/extractLetVariables.ts
1825
+ var MIN_STRING_LENGTH5 = 0;
1826
+ var MATCH_FULL_INDEX = 0;
1827
+ var NOT_FOUND_INDEX2 = -1;
1828
+ var QUOTE_MATCH_GROUP_INDEX = 1;
1829
+ var VALUE_MATCH_GROUP_INDEX = 2;
1830
+ var ZERO_COUNT = 0;
1831
+ function extractLetVariables(xpath) {
1832
+ if (xpath.length === MIN_STRING_LENGTH5) return [];
1833
+ const variables = [];
1834
+ const letStartMatch = /let\s+/i.exec(xpath);
1835
+ if (letStartMatch?.index === void 0) {
1836
+ return variables;
1837
+ }
1838
+ const letStartIndex = letStartMatch.index + letStartMatch[MATCH_FULL_INDEX].length;
1839
+ let inSingleQuote = false;
1840
+ let inDoubleQuote = false;
1841
+ let parenDepth = 0;
1842
+ let declarationsEnd = NOT_FOUND_INDEX2;
1843
+ for (let i = letStartIndex; i < xpath.length; i++) {
1844
+ const char = xpath.charAt(i);
1845
+ if (char === "'" && !inDoubleQuote) {
1846
+ inSingleQuote = !inSingleQuote;
1847
+ } else if (char === '"' && !inSingleQuote) {
1848
+ inDoubleQuote = !inDoubleQuote;
1849
+ } else if (!inSingleQuote && !inDoubleQuote) {
1850
+ if (char === "(") {
1851
+ parenDepth++;
1852
+ } else if (char === ")") {
1853
+ parenDepth--;
1854
+ } else if (parenDepth === ZERO_COUNT && xpath.substring(i).toLowerCase().startsWith("return")) {
1855
+ declarationsEnd = i;
1856
+ break;
1857
+ }
1858
+ }
1859
+ }
1860
+ if (declarationsEnd === NOT_FOUND_INDEX2) {
1861
+ return variables;
1862
+ }
1863
+ const declarationsPart = xpath.substring(letStartIndex, declarationsEnd);
1864
+ let inQuote2 = false;
1865
+ let inDoubleQuote2 = false;
1866
+ let parenDepth2 = 0;
1867
+ let currentVarStart = NOT_FOUND_INDEX2;
1868
+ const varDeclarations = [];
1869
+ for (let i = 0; i < declarationsPart.length; i++) {
1870
+ const char = declarationsPart.charAt(i);
1871
+ if (char === "'" && !inDoubleQuote2) {
1872
+ inQuote2 = !inQuote2;
1873
+ } else if (char === '"' && !inQuote2) {
1874
+ inDoubleQuote2 = !inDoubleQuote2;
1875
+ } else if (!inQuote2 && !inDoubleQuote2) {
1876
+ if (char === "(") {
1877
+ parenDepth2++;
1878
+ } else if (char === ")") {
1879
+ parenDepth2--;
1880
+ } else if (char === "$" && parenDepth2 === ZERO_COUNT) {
1881
+ if (currentVarStart >= ZERO_COUNT) {
1882
+ varDeclarations.push({ end: i, start: currentVarStart });
1883
+ }
1884
+ currentVarStart = i;
1885
+ } else if (char === "," && parenDepth2 === ZERO_COUNT && currentVarStart >= ZERO_COUNT) {
1886
+ varDeclarations.push({ end: i, start: currentVarStart });
1887
+ currentVarStart = NOT_FOUND_INDEX2;
1888
+ }
1889
+ }
1890
+ }
1891
+ if (currentVarStart >= ZERO_COUNT) {
1892
+ varDeclarations.push({
1893
+ end: declarationsPart.length,
1894
+ start: currentVarStart
1895
+ });
1896
+ }
1897
+ for (const decl of varDeclarations) {
1898
+ const declText = declarationsPart.substring(decl.start, decl.end).trim();
1899
+ const equalsMatch = /\$([a-zA-Z_][a-zA-Z0-9_]*)\s*:?=\s*(.+)/s.exec(
1900
+ declText
1901
+ );
1902
+ if (equalsMatch) {
1903
+ const varName = `$${equalsMatch[QUOTE_MATCH_GROUP_INDEX]}`;
1904
+ const varValue = equalsMatch[VALUE_MATCH_GROUP_INDEX].trim();
1905
+ variables.push({
1906
+ name: varName,
1907
+ value: varValue
1908
+ });
1909
+ }
1910
+ }
1911
+ return variables;
1912
+ }
1913
+
1914
+ // src/xpath/extractors/extractHardcodedValues.ts
1915
+ var MIN_STRING_LENGTH6 = 0;
1916
+ var MATCH_FULL_INDEX2 = 0;
1917
+ var NUMBER_VALUE_GROUP_INDEX = 1;
1918
+ var QUOTE_GROUP_INDEX = 1;
1919
+ var STRING_VALUE_GROUP_INDEX = 2;
1920
+ var ZERO_COUNT2 = 0;
1921
+ function extractHardcodedValues(xpath) {
1922
+ if (xpath.length === MIN_STRING_LENGTH6) return [];
1923
+ const values = [];
1924
+ let letStart = -1;
1925
+ let letEnd = -1;
1926
+ const letStartMatch = /let\s+/i.exec(xpath);
1927
+ if (letStartMatch?.index !== void 0) {
1928
+ letStart = letStartMatch.index;
1929
+ const letStartIndex = letStartMatch.index + letStartMatch[MATCH_FULL_INDEX2].length;
1930
+ let inSingleQuote = false;
1931
+ let inDoubleQuote = false;
1932
+ let parenDepth = 0;
1933
+ for (let i = letStartIndex; i < xpath.length; i++) {
1934
+ const char = xpath.charAt(i);
1935
+ if (char === "'" && !inDoubleQuote) {
1936
+ inSingleQuote = !inSingleQuote;
1937
+ } else if (char === '"' && !inSingleQuote) {
1938
+ inDoubleQuote = !inDoubleQuote;
1939
+ } else if (!inSingleQuote && !inDoubleQuote) {
1940
+ if (char === "(") {
1941
+ parenDepth++;
1942
+ } else if (char === ")") {
1943
+ parenDepth--;
1944
+ } else if (parenDepth === ZERO_COUNT2 && xpath.substring(i).toLowerCase().startsWith("return")) {
1945
+ const returnEnd = i + "return".length;
1946
+ letEnd = returnEnd;
1947
+ break;
1948
+ }
1949
+ }
1950
+ }
1951
+ }
1952
+ const stringPattern = /(['"])([^'"]*)\1/g;
1953
+ let stringMatch = null;
1954
+ while ((stringMatch = stringPattern.exec(xpath)) !== null) {
1955
+ if (letStart >= ZERO_COUNT2 && stringMatch.index >= letStart && stringMatch.index < letEnd) {
1956
+ continue;
1957
+ }
1958
+ const stringValue = stringMatch[STRING_VALUE_GROUP_INDEX];
1959
+ if (stringValue.length === MIN_STRING_LENGTH6) continue;
1960
+ values.push({
1961
+ position: stringMatch.index,
1962
+ type: "string",
1963
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Regex capture groups ensure these exist
1964
+ value: `${stringMatch[QUOTE_GROUP_INDEX]}${stringValue}${stringMatch[QUOTE_GROUP_INDEX]}`
1965
+ });
1966
+ }
1967
+ const numberPattern = /\b([2-9]\d*|0\d+)\b/g;
1968
+ let numberMatch = null;
1969
+ while ((numberMatch = numberPattern.exec(xpath)) !== null) {
1970
+ if (letStart >= ZERO_COUNT2 && numberMatch.index >= letStart && numberMatch.index < letEnd) {
1971
+ continue;
1972
+ }
1973
+ const numberValue = numberMatch[NUMBER_VALUE_GROUP_INDEX];
1974
+ values.push({
1975
+ position: numberMatch.index,
1976
+ type: "number",
1977
+ value: numberValue
1978
+ });
1979
+ }
1980
+ return values;
1981
+ }
1982
+
1983
+ // src/tester/quality/checkQualityChecks.ts
1984
+ var MAX_MESSAGE_LENGTH = 80;
1985
+ var MIN_STRING_LENGTH7 = 0;
1986
+ var LINE_NUMBER_OFFSET2 = 1;
1987
+ var ARRAY_LAST_INDEX_OFFSET = 1;
1988
+ var NOT_FOUND_INDEX3 = -1;
1989
+ var ZERO_COUNT3 = 0;
1990
+ var SINGLE_OCCURRENCE = 1;
1991
+ var VERSION_PATTERN = /^Version:\s*(\d+)\.(\d+)\.(\d+)$/;
1992
+ var VARIABLE_DOC_PATTERN = /^(\$[a-zA-Z_][a-zA-Z0-9_]*):\s*.+$/;
1993
+ var INLINE_VIOLATION_MARKER = "Inline violation marker // \u274C";
1994
+ var INLINE_VALID_MARKER = "Inline valid marker // \u2705";
1995
+ var NEXT_LINE_OFFSET = 1;
1996
+ var VAR_PREFIX_LENGTH = 1;
1997
+ function hasAtLeastTwoItems(items) {
1998
+ return items.length > SINGLE_OCCURRENCE;
1999
+ }
2000
+ function findMessageLineNumber(xmlContent) {
2001
+ const lines = xmlContent.split("\n");
2002
+ for (let i = 0; i < lines.length; i++) {
2003
+ const line = lines[i];
2004
+ if (line.includes("message=")) {
2005
+ return i + LINE_NUMBER_OFFSET2;
2006
+ }
2007
+ }
2008
+ return void 0;
2009
+ }
2010
+ function findDescriptionLineNumber(xmlContent) {
2011
+ const lines = xmlContent.split("\n");
2012
+ for (let i = 0; i < lines.length; i++) {
2013
+ const line = lines[i];
2014
+ if (line.includes("<description>")) {
2015
+ return i + LINE_NUMBER_OFFSET2;
2016
+ }
2017
+ }
2018
+ return void 0;
2019
+ }
2020
+ function findXPathValueLocation(xmlContent) {
2021
+ const lines = xmlContent.split("\n");
2022
+ let inProperties = false;
2023
+ let inXPathProperty = false;
2024
+ let valueStartLine = -1;
2025
+ let valueStartChar = -1;
2026
+ let valueContent = "";
2027
+ for (let i = 0; i < lines.length; i++) {
2028
+ const line = lines[i];
2029
+ if (line.includes("<properties>")) {
2030
+ inProperties = true;
2031
+ }
2032
+ if (inProperties && line.includes('name="xpath"')) {
2033
+ inXPathProperty = true;
2034
+ }
2035
+ if (inXPathProperty) {
2036
+ const valueTagIndex = line.indexOf("<value>");
2037
+ if (valueTagIndex !== NOT_FOUND_INDEX3) {
2038
+ valueStartLine = i;
2039
+ valueStartChar = valueTagIndex + "<value>".length;
2040
+ let valueEndIndex = line.indexOf("</value>");
2041
+ if (valueEndIndex !== NOT_FOUND_INDEX3) {
2042
+ valueContent = line.substring(
2043
+ valueStartChar,
2044
+ valueEndIndex
2045
+ );
2046
+ } else {
2047
+ valueContent = line.substring(valueStartChar);
2048
+ for (let j = i + NEXT_LINE_OFFSET; j < lines.length; j++) {
2049
+ const nextLine = lines[j];
2050
+ const endTagIndex = nextLine.indexOf("</value>");
2051
+ if (endTagIndex !== NOT_FOUND_INDEX3) {
2052
+ valueContent += "\n" + nextLine.substring(ZERO_COUNT3, endTagIndex);
2053
+ break;
2054
+ }
2055
+ valueContent += "\n" + nextLine;
2056
+ }
2057
+ }
2058
+ const trimmedContent = valueContent.trim();
2059
+ let actualContentStartLine = valueStartLine;
2060
+ let actualContent = trimmedContent;
2061
+ const cdataIndexInValue = valueContent.indexOf("<![CDATA[");
2062
+ if (cdataIndexInValue !== NOT_FOUND_INDEX3) {
2063
+ const afterCdataStart = valueContent.substring(
2064
+ cdataIndexInValue + "<![CDATA[".length
2065
+ );
2066
+ const cdataEndIndex = afterCdataStart.indexOf("]]>");
2067
+ if (cdataEndIndex !== NOT_FOUND_INDEX3) {
2068
+ actualContent = afterCdataStart.substring(ZERO_COUNT3, cdataEndIndex).trim();
2069
+ const newlineCountToCdata = (valueContent.substring(ZERO_COUNT3, cdataIndexInValue).match(/\n/g) ?? []).length;
2070
+ actualContentStartLine = valueStartLine + newlineCountToCdata + LINE_NUMBER_OFFSET2;
2071
+ }
2072
+ }
2073
+ return {
2074
+ startChar: valueStartChar,
2075
+ startLine: actualContentStartLine + LINE_NUMBER_OFFSET2,
2076
+ valueContent: actualContent.trim()
2077
+ };
2078
+ }
2079
+ }
2080
+ if (line.includes("</properties>")) {
2081
+ break;
2082
+ }
2083
+ }
2084
+ return void 0;
2085
+ }
2086
+ function calculateXPathLineNumber(xpathValueLocation, positionInXPath) {
2087
+ const xpathBeforePosition = xpathValueLocation.valueContent.substring(
2088
+ ZERO_COUNT3,
2089
+ positionInXPath
2090
+ );
2091
+ const newlineCount = (xpathBeforePosition.match(/\n/g) ?? []).length;
2092
+ return xpathValueLocation.startLine + newlineCount;
2093
+ }
2094
+ function checkMessageLength(message, messageLine) {
2095
+ if (message === null || message === void 0 || message.length === MIN_STRING_LENGTH7) {
2096
+ return messageLine !== void 0 ? `Line ${String(messageLine)}: message attribute is missing` : "message attribute is missing";
2097
+ }
2098
+ if (message.length > MAX_MESSAGE_LENGTH) {
2099
+ return messageLine !== void 0 ? `Line ${String(messageLine)}: message attribute exceeds ${String(MAX_MESSAGE_LENGTH)} characters (${String(message.length)} chars)` : `message attribute exceeds ${String(MAX_MESSAGE_LENGTH)} characters (${String(message.length)} chars)`;
2100
+ }
2101
+ return null;
2102
+ }
2103
+ function checkVersionLine(description, descriptionLine) {
2104
+ if (description === null || description === void 0 || description.length === MIN_STRING_LENGTH7) {
2105
+ return descriptionLine !== void 0 ? `Line ${String(descriptionLine)}: description must end with 'Version: X.Y.Z'` : "description must end with 'Version: X.Y.Z'";
2106
+ }
2107
+ const lines = description.split("\n");
2108
+ const lastLineIndex = lines.length - ARRAY_LAST_INDEX_OFFSET;
2109
+ const lastLine = lines[lastLineIndex]?.trim();
2110
+ if (lastLine === void 0 || lastLine.length === MIN_STRING_LENGTH7 || !VERSION_PATTERN.test(lastLine)) {
2111
+ return descriptionLine !== void 0 ? `Line ${String(descriptionLine)}: description must end with 'Version: X.Y.Z' (SemVer format)` : "description must end with 'Version: X.Y.Z' (SemVer format)";
2112
+ }
2113
+ return null;
2114
+ }
2115
+ function checkXPathHardcodedValues(xpath, xmlContent) {
2116
+ const issues = [];
2117
+ if (xpath === null || xpath === void 0 || xpath.length === MIN_STRING_LENGTH7) {
2118
+ return issues;
2119
+ }
2120
+ const xpathLocation = findXPathValueLocation(xmlContent);
2121
+ if (xpathLocation === void 0) {
2122
+ return issues;
2123
+ }
2124
+ const rawXPath = xpathLocation.valueContent;
2125
+ const hardcodedValues = extractHardcodedValues(rawXPath);
2126
+ if (hardcodedValues.length === MIN_STRING_LENGTH7) {
2127
+ return issues;
2128
+ }
2129
+ const letVariables = extractLetVariables(xpath);
2130
+ const letVariableValues = new Set(
2131
+ letVariables.map((v) => {
2132
+ const normalized = v.value.replace(/^['"]|['"]$/g, "").toLowerCase().trim();
2133
+ return normalized;
2134
+ })
2135
+ );
2136
+ for (const hardcoded of hardcodedValues) {
2137
+ const normalizedValue = hardcoded.value.replace(/^['"]|['"]$/g, "").toLowerCase().trim();
2138
+ if (!letVariableValues.has(normalizedValue)) {
2139
+ const exactLine = calculateXPathLineNumber(
2140
+ xpathLocation,
2141
+ hardcoded.position
2142
+ );
2143
+ issues.push(
2144
+ `Line ${String(exactLine)}: ${hardcoded.value} outside initial let statement`
2145
+ );
2146
+ }
2147
+ }
2148
+ return issues;
2149
+ }
2150
+ function findVariablePositionInXPath(xpath, varName) {
2151
+ const pattern = new RegExp(
2152
+ `\\$${varName.substring(VAR_PREFIX_LENGTH)}\\s*:?=`
2153
+ );
2154
+ const match = pattern.exec(xpath);
2155
+ return match?.index ?? NOT_FOUND_INDEX3;
2156
+ }
2157
+ function checkVariableDocumentation(xpath, description, xmlContent) {
2158
+ const issues = [];
2159
+ if (xpath === null || xpath === void 0 || xpath.length === MIN_STRING_LENGTH7) {
2160
+ return issues;
2161
+ }
2162
+ const letVariables = extractLetVariables(xpath);
2163
+ if (letVariables.length === MIN_STRING_LENGTH7) {
2164
+ return issues;
2165
+ }
2166
+ if (description === null || description === void 0 || description.length === MIN_STRING_LENGTH7) {
2167
+ const xpathLocation2 = findXPathValueLocation(xmlContent);
2168
+ for (const variable of letVariables) {
2169
+ const xpathForPosition = xpathLocation2 !== void 0 ? xpathLocation2.valueContent : xpath;
2170
+ const varPosition = findVariablePositionInXPath(
2171
+ xpathForPosition,
2172
+ variable.name
2173
+ );
2174
+ const varLine = xpathLocation2 !== void 0 && varPosition !== NOT_FOUND_INDEX3 ? calculateXPathLineNumber(xpathLocation2, varPosition) : void 0;
2175
+ issues.push(
2176
+ varLine !== void 0 ? `Line ${String(varLine)}: variable ${variable.name} undocumented` : `variable ${variable.name} undocumented`
2177
+ );
2178
+ }
2179
+ return issues;
2180
+ }
2181
+ const descriptionLines = description.split("\n");
2182
+ const documentedVars = /* @__PURE__ */ new Set();
2183
+ for (const line of descriptionLines) {
2184
+ const trimmed = line.trim();
2185
+ const varMatch = VARIABLE_DOC_PATTERN.exec(trimmed);
2186
+ if (varMatch !== null) {
2187
+ const colonIndex = trimmed.indexOf(":");
2188
+ const varName = trimmed.substring(ZERO_COUNT3, colonIndex);
2189
+ documentedVars.add(varName);
2190
+ }
2191
+ }
2192
+ const xpathLocation = findXPathValueLocation(xmlContent);
2193
+ for (const variable of letVariables) {
2194
+ if (!documentedVars.has(variable.name)) {
2195
+ const xpathForPosition = xpathLocation !== void 0 ? xpathLocation.valueContent : xpath;
2196
+ const varPosition = findVariablePositionInXPath(
2197
+ xpathForPosition,
2198
+ variable.name
2199
+ );
2200
+ const varLine = xpathLocation !== void 0 && varPosition !== NOT_FOUND_INDEX3 ? calculateXPathLineNumber(xpathLocation, varPosition) : void 0;
2201
+ issues.push(
2202
+ varLine !== void 0 ? `Line ${String(varLine)}: variable ${variable.name} undocumented` : `variable ${variable.name} undocumented`
2203
+ );
2204
+ }
2205
+ }
2206
+ return issues;
2207
+ }
2208
+ function extractTextAfterMarker(line, marker) {
2209
+ const markerIndex = line.indexOf(marker);
2210
+ const textAfter = line.substring(markerIndex + marker.length).trim();
2211
+ return textAfter;
2212
+ }
2213
+ function findMarkerLineNumber(input) {
2214
+ const { exampleIndex, lines, markerLineInExample } = input;
2215
+ let currentExampleIndex = 0;
2216
+ let exampleStart = NOT_FOUND_INDEX3;
2217
+ for (let i = 0; i < lines.length; i++) {
2218
+ const line = lines[i];
2219
+ if (line.includes("<example>")) {
2220
+ currentExampleIndex++;
2221
+ if (currentExampleIndex === exampleIndex) {
2222
+ exampleStart = i;
2223
+ }
2224
+ }
2225
+ if (exampleStart >= ZERO_COUNT3 && line.includes("</example>")) {
2226
+ const lineOffset = markerLineInExample - LINE_NUMBER_OFFSET2;
2227
+ const estimatedLine = exampleStart + lineOffset + LINE_NUMBER_OFFSET2;
2228
+ if (estimatedLine < i) {
2229
+ return estimatedLine + LINE_NUMBER_OFFSET2;
2230
+ }
2231
+ break;
2232
+ }
2233
+ }
2234
+ return void 0;
2235
+ }
2236
+ function checkMarkerDescriptions(examples, xmlContent) {
2237
+ const issues = [];
2238
+ const lines = xmlContent.split("\n");
2239
+ const markerDescriptions = /* @__PURE__ */ new Map();
2240
+ for (const example of examples) {
2241
+ const exampleLines = example.content.split("\n");
2242
+ for (const marker of example.violationMarkers) {
2243
+ const markerLineIndex = marker.lineNumber - LINE_NUMBER_OFFSET2;
2244
+ if (markerLineIndex >= ZERO_COUNT3 && markerLineIndex < exampleLines.length) {
2245
+ const markerLine = exampleLines[markerLineIndex];
2246
+ let desc = "";
2247
+ if (markerLine.includes("// \u274C")) {
2248
+ desc = extractTextAfterMarker(markerLine, "// \u274C").trim();
2249
+ } else if (markerLine.includes("// Violation:")) {
2250
+ desc = extractTextAfterMarker(
2251
+ markerLine,
2252
+ "// Violation:"
2253
+ ).trim();
2254
+ } else {
2255
+ desc = marker.description.trim();
2256
+ if (desc === INLINE_VIOLATION_MARKER) {
2257
+ desc = "";
2258
+ }
2259
+ }
2260
+ if (desc.length === MIN_STRING_LENGTH7) {
2261
+ const lineNum = findMarkerLineNumber({
2262
+ exampleIndex: example.exampleIndex,
2263
+ lines,
2264
+ markerLineInExample: marker.lineNumber
2265
+ });
2266
+ issues.push(
2267
+ lineNum !== void 0 ? `Line ${String(lineNum)}: violation has no description` : `Example ${String(example.exampleIndex)}: violation has no description`
2268
+ );
2269
+ } else {
2270
+ if (!markerDescriptions.has(desc)) {
2271
+ markerDescriptions.set(desc, []);
2272
+ }
2273
+ const locations = markerDescriptions.get(desc);
2274
+ const lineNum = findMarkerLineNumber({
2275
+ exampleIndex: example.exampleIndex,
2276
+ lines,
2277
+ markerLineInExample: marker.lineNumber
2278
+ });
2279
+ locations.push({
2280
+ example: example.exampleIndex,
2281
+ line: lineNum ?? ZERO_COUNT3
2282
+ });
2283
+ }
2284
+ }
2285
+ }
2286
+ for (const marker of example.validMarkers) {
2287
+ const markerLineIndex = marker.lineNumber - LINE_NUMBER_OFFSET2;
2288
+ if (markerLineIndex >= ZERO_COUNT3 && markerLineIndex < exampleLines.length) {
2289
+ const markerLine = exampleLines[markerLineIndex];
2290
+ let desc = "";
2291
+ if (markerLine.includes("// \u2705")) {
2292
+ desc = extractTextAfterMarker(markerLine, "// \u2705").trim();
2293
+ } else if (markerLine.includes("// Valid:")) {
2294
+ desc = extractTextAfterMarker(
2295
+ markerLine,
2296
+ "// Valid:"
2297
+ ).trim();
2298
+ } else {
2299
+ desc = marker.description.trim();
2300
+ if (desc === INLINE_VALID_MARKER) {
2301
+ desc = "";
2302
+ }
2303
+ }
2304
+ if (desc.length === MIN_STRING_LENGTH7) {
2305
+ const lineNum = findMarkerLineNumber({
2306
+ exampleIndex: example.exampleIndex,
2307
+ lines,
2308
+ markerLineInExample: marker.lineNumber
2309
+ });
2310
+ issues.push(
2311
+ lineNum !== void 0 ? `Line ${String(lineNum)}: valid has no description` : `Example ${String(example.exampleIndex)}: valid has no description`
2312
+ );
2313
+ } else {
2314
+ if (!markerDescriptions.has(desc)) {
2315
+ markerDescriptions.set(desc, []);
2316
+ }
2317
+ const locations = markerDescriptions.get(desc);
2318
+ const lineNum = findMarkerLineNumber({
2319
+ exampleIndex: example.exampleIndex,
2320
+ lines,
2321
+ markerLineInExample: marker.lineNumber
2322
+ });
2323
+ locations.push({
2324
+ example: example.exampleIndex,
2325
+ line: lineNum ?? ZERO_COUNT3
2326
+ });
2327
+ }
2328
+ }
2329
+ }
2330
+ }
2331
+ for (const [desc, locations] of markerDescriptions.entries()) {
2332
+ if (hasAtLeastTwoItems(locations)) {
2333
+ const [firstLocation, ...restLocations] = locations;
2334
+ const originalLine = firstLocation.line;
2335
+ for (const loc of restLocations) {
2336
+ issues.push(
2337
+ `Line ${String(loc.line)}: duplicate description '${desc}' (line ${String(originalLine)})`
2338
+ );
2339
+ }
2340
+ }
2341
+ }
2342
+ return issues;
2343
+ }
2344
+ function checkViolationExists(examples, xmlContent) {
2345
+ const hasViolation = examples.some(
2346
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Callback parameter
2347
+ (example) => example.violationMarkers.length > MIN_STRING_LENGTH7
2348
+ );
2349
+ if (!hasViolation) {
2350
+ const lines = xmlContent.split("\n");
2351
+ for (let i = 0; i < lines.length; i++) {
2352
+ const line = lines[i];
2353
+ if (line.includes("<example>")) {
2354
+ return `Line ${String(i + LINE_NUMBER_OFFSET2)}: at least one violation marker (// Violation or // \u274C) required`;
2355
+ }
2356
+ }
2357
+ return "at least one violation marker (// Violation or // \u274C) required";
2358
+ }
2359
+ return null;
2360
+ }
2361
+ function checkQualityChecks(ruleFilePath, ruleMetadata, examples) {
2362
+ const issues = [];
2363
+ try {
2364
+ const xmlContent = readFileSync3(ruleFilePath, "utf-8");
2365
+ const messageLine = findMessageLineNumber(xmlContent);
2366
+ const messageIssue = checkMessageLength(
2367
+ ruleMetadata.message,
2368
+ messageLine
2369
+ );
2370
+ if (messageIssue !== null) {
2371
+ issues.push(messageIssue);
2372
+ }
2373
+ const descriptionLine = findDescriptionLineNumber(xmlContent);
2374
+ const versionIssue = checkVersionLine(
2375
+ ruleMetadata.description,
2376
+ descriptionLine
2377
+ );
2378
+ if (versionIssue !== null) {
2379
+ issues.push(versionIssue);
2380
+ }
2381
+ const xpathIssues = checkXPathHardcodedValues(
2382
+ ruleMetadata.xpath,
2383
+ xmlContent
2384
+ );
2385
+ issues.push(...xpathIssues);
2386
+ const varDocIssues = checkVariableDocumentation(
2387
+ ruleMetadata.xpath,
2388
+ ruleMetadata.description,
2389
+ xmlContent
2390
+ );
2391
+ issues.push(...varDocIssues);
2392
+ const markerIssues = checkMarkerDescriptions(examples, xmlContent);
2393
+ issues.push(...markerIssues);
2394
+ const violationIssue = checkViolationExists(examples, xmlContent);
2395
+ if (violationIssue !== null) {
2396
+ issues.push(violationIssue);
2397
+ }
2398
+ } catch (error) {
2399
+ const errorMessage = error instanceof Error ? error.message : String(error);
2400
+ issues.push(`Error reading rule file: ${errorMessage}`);
2401
+ }
2402
+ const MIN_ISSUES_COUNT2 = 0;
2403
+ return {
2404
+ issues,
2405
+ passed: issues.length === MIN_ISSUES_COUNT2,
2406
+ warnings: []
2407
+ };
2408
+ }
2409
+
1756
2410
  // src/tester/RuleTester.ts
1757
2411
  var MIN_EXAMPLES_COUNT2 = 0;
1758
2412
  var MIN_VIOLATIONS_COUNT = 0;
@@ -1808,7 +2462,7 @@ var RuleTester = class {
1808
2462
  * @public
1809
2463
  */
1810
2464
  extractRuleMetadata() {
1811
- const content = readFileSync3(this.ruleFilePath, "utf-8");
2465
+ const content = readFileSync4(this.ruleFilePath, "utf-8");
1812
2466
  const parser = new DOMParser3();
1813
2467
  const doc = parser.parseFromString(content, "text/xml");
1814
2468
  const ruleElement = doc.getElementsByTagName("rule")[MIN_EXAMPLES_COUNT2];
@@ -1847,7 +2501,7 @@ var RuleTester = class {
1847
2501
  * @public
1848
2502
  */
1849
2503
  extractExamples() {
1850
- const content = readFileSync3(this.ruleFilePath, "utf-8");
2504
+ const content = readFileSync4(this.ruleFilePath, "utf-8");
1851
2505
  const parser = new DOMParser3();
1852
2506
  const doc = parser.parseFromString(content, "text/xml");
1853
2507
  const exampleNodes = doc.getElementsByTagName("example");
@@ -1906,13 +2560,9 @@ var RuleTester = class {
1906
2560
  */
1907
2561
  async runCoverageTest(skipPMDValidation = false, maxConcurrency = DEFAULT_CONCURRENCY) {
1908
2562
  this.extractExamples();
1909
- const qualityResult = runQualityChecks(
1910
- this.ruleMetadata,
1911
- this.examples
1912
- );
1913
2563
  const INDEX_OFFSET3 = 1;
1914
2564
  const ZERO_VIOLATIONS = 0;
1915
- const exampleResults = skipPMDValidation ? (
2565
+ const pmdValidationPromise = skipPMDValidation ? Promise.resolve(
1916
2566
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Callback parameter for map
1917
2567
  this.examples.map((_example, i) => ({
1918
2568
  actualViolations: ZERO_VIOLATIONS,
@@ -1922,7 +2572,34 @@ var RuleTester = class {
1922
2572
  passed: true,
1923
2573
  testCaseResults: []
1924
2574
  }))
1925
- ) : await this.validateExamplesWithPMD(maxConcurrency);
2575
+ ) : this.validateExamplesWithPMD(maxConcurrency);
2576
+ const qualityChecksPromise = Promise.resolve(
2577
+ runQualityChecks(
2578
+ this.ruleFilePath,
2579
+ this.ruleMetadata,
2580
+ this.examples
2581
+ )
2582
+ );
2583
+ const newQualityChecksPromise = Promise.resolve(
2584
+ checkQualityChecks(
2585
+ this.ruleFilePath,
2586
+ this.ruleMetadata,
2587
+ this.examples
2588
+ )
2589
+ );
2590
+ const xpathCoveragePromise = Promise.resolve(
2591
+ checkXPathCoverage(
2592
+ this.ruleMetadata.xpath,
2593
+ this.examples,
2594
+ this.ruleFilePath
2595
+ )
2596
+ );
2597
+ const [exampleResults, qualityResult, newQualityChecks, xpathCoverage] = await Promise.all([
2598
+ pmdValidationPromise,
2599
+ qualityChecksPromise,
2600
+ newQualityChecksPromise,
2601
+ xpathCoveragePromise
2602
+ ]);
1926
2603
  this.results.examplesTested = this.examples.length;
1927
2604
  this.results.examplesPassed = exampleResults.filter(
1928
2605
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Callback parameter
@@ -1941,12 +2618,8 @@ var RuleTester = class {
1941
2618
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Callback parameter
1942
2619
  (result) => result.testCaseResults
1943
2620
  );
1944
- const xpathCoverage = checkXPathCoverage(
1945
- this.ruleMetadata.xpath,
1946
- this.examples,
1947
- this.ruleFilePath
1948
- );
1949
2621
  this.results.xpathCoverage = xpathCoverage;
2622
+ this.results.qualityChecks = newQualityChecks;
1950
2623
  this.results.success = qualityResult.passed && this.examples.length > MIN_EXAMPLES_COUNT2 && this.results.examplesPassed === this.results.examplesTested;
1951
2624
  return Promise.resolve(this.results);
1952
2625
  }
@@ -2103,11 +2776,11 @@ var RuleTester = class {
2103
2776
  */
2104
2777
  findTestCaseLineNumber(exampleIndex, testType) {
2105
2778
  try {
2106
- const content = readFileSync3(this.ruleFilePath, "utf-8");
2779
+ const content = readFileSync4(this.ruleFilePath, "utf-8");
2107
2780
  const lines = content.split("\n");
2108
- const NOT_FOUND_INDEX2 = -1;
2109
- let exampleStart = NOT_FOUND_INDEX2;
2110
- let exampleEnd = NOT_FOUND_INDEX2;
2781
+ const NOT_FOUND_INDEX4 = -1;
2782
+ let exampleStart = NOT_FOUND_INDEX4;
2783
+ let exampleEnd = NOT_FOUND_INDEX4;
2111
2784
  let currentExampleIndex = 0;
2112
2785
  for (let i = 0; i < lines.length; i++) {
2113
2786
  const line = lines[i];
@@ -2119,7 +2792,7 @@ var RuleTester = class {
2119
2792
  break;
2120
2793
  }
2121
2794
  }
2122
- if (exampleStart === NOT_FOUND_INDEX2 || exampleEnd === NOT_FOUND_INDEX2) {
2795
+ if (exampleStart === NOT_FOUND_INDEX4 || exampleEnd === NOT_FOUND_INDEX4) {
2123
2796
  return void 0;
2124
2797
  }
2125
2798
  const hasInlineMarkers = () => {
@@ -2131,24 +2804,24 @@ var RuleTester = class {
2131
2804
  }
2132
2805
  return false;
2133
2806
  };
2134
- const LINE_NUMBER_OFFSET2 = 1;
2807
+ const LINE_NUMBER_OFFSET3 = 1;
2135
2808
  const hasInline = hasInlineMarkers();
2136
2809
  for (let i = exampleStart; i <= exampleEnd; i++) {
2137
2810
  const line = lines[i];
2138
2811
  if (hasInline) {
2139
2812
  const inlineMarkerText = testType === "violation" ? "// \u274C" : "// \u2705";
2140
2813
  if (line.includes(inlineMarkerText)) {
2141
- return i + LINE_NUMBER_OFFSET2;
2814
+ return i + LINE_NUMBER_OFFSET3;
2142
2815
  }
2143
2816
  } else {
2144
2817
  const sectionMarkerText = testType === "violation" ? "// Violation:" : "// Valid:";
2145
2818
  if (line.includes(sectionMarkerText)) {
2146
- const NEXT_LINE_OFFSET = 1;
2147
- for (let j = i + NEXT_LINE_OFFSET; j <= exampleEnd; j++) {
2819
+ const NEXT_LINE_OFFSET2 = 1;
2820
+ for (let j = i + NEXT_LINE_OFFSET2; j <= exampleEnd; j++) {
2148
2821
  const nextLineRaw = lines[j];
2149
2822
  const nextLine = nextLineRaw.trim();
2150
2823
  if (nextLine && !nextLine.startsWith("//") && !nextLine.startsWith("*/") && !nextLine.startsWith("/*") && !nextLine.startsWith("</") && !nextLine.startsWith("<")) {
2151
- return j + LINE_NUMBER_OFFSET2;
2824
+ return j + LINE_NUMBER_OFFSET3;
2152
2825
  }
2153
2826
  }
2154
2827
  }
@@ -2165,11 +2838,11 @@ var RuleTester = class {
2165
2838
  * @private
2166
2839
  */
2167
2840
  findExampleLineNumber(exampleIndex) {
2168
- const content = readFileSync3(this.ruleFilePath, "utf-8");
2841
+ const content = readFileSync4(this.ruleFilePath, "utf-8");
2169
2842
  const lines = content.split("\n");
2170
2843
  let currentExampleIndex = 0;
2171
- const LINE_NUMBER_OFFSET2 = 1;
2172
- const NOT_FOUND_INDEX2 = -1;
2844
+ const LINE_NUMBER_OFFSET3 = 1;
2845
+ const NOT_FOUND_INDEX4 = -1;
2173
2846
  const foundIndex = lines.findIndex((line) => {
2174
2847
  if (line.includes("<example>")) {
2175
2848
  currentExampleIndex++;
@@ -2177,7 +2850,7 @@ var RuleTester = class {
2177
2850
  }
2178
2851
  return false;
2179
2852
  });
2180
- return foundIndex === NOT_FOUND_INDEX2 ? void 0 : foundIndex + LINE_NUMBER_OFFSET2;
2853
+ return foundIndex === NOT_FOUND_INDEX4 ? void 0 : foundIndex + LINE_NUMBER_OFFSET3;
2181
2854
  }
2182
2855
  /**
2183
2856
  * Initializes an empty results object for a new test run.
@@ -2295,6 +2968,8 @@ function generateLcovReport(coverageData, outputPath) {
2295
2968
  // src/cli/main.ts
2296
2969
  var EXIT_CODE_SUCCESS = 0;
2297
2970
  var EXIT_CODE_ERROR = 1;
2971
+ var PARSE_INT_RADIX = 10;
2972
+ var LINE_NUMBER_MATCH_GROUP_INDEX = 1;
2298
2973
  var ARGV_SLICE_INDEX = 2;
2299
2974
  var MIN_ARGS_COUNT = 0;
2300
2975
  var MAX_ARGS_COUNT = 2;
@@ -2320,11 +2995,8 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2320
2995
  try {
2321
2996
  const tester = new RuleTester(ruleFilePath);
2322
2997
  const result = await tester.runCoverageTest(false, maxConcurrency);
2323
- const hasCoverageTracker = coverageTracker !== null;
2324
- const MIN_COVERED_LINES_COUNT = 0;
2325
- const coveredLines = result.xpathCoverage.coveredLineNumbers;
2326
- if (hasCoverageTracker && coveredLines && coveredLines.length > MIN_COVERED_LINES_COUNT) {
2327
- for (const lineNumber of coveredLines) {
2998
+ if (coverageTracker && result.xpathCoverage.coveredLineNumbers) {
2999
+ for (const lineNumber of result.xpathCoverage.coveredLineNumbers) {
2328
3000
  coverageTracker.recordXPathLine(lineNumber);
2329
3001
  }
2330
3002
  }
@@ -2358,12 +3030,42 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2358
3030
  ` Rule triggers violations: ${result.ruleTriggersViolations ? "\u2705 Yes" : "\u274C No"}`
2359
3031
  );
2360
3032
  }
3033
+ if (result.qualityChecks) {
3034
+ console.log("\n\u2B50 Quality Checks:");
3035
+ if (result.qualityChecks.passed) {
3036
+ console.log(" Status: \u2705 Passed");
3037
+ } else {
3038
+ console.log(" Status: \u26A0\uFE0F Incomplete");
3039
+ const sortedIssues = [...result.qualityChecks.issues].sort(
3040
+ (a, b) => {
3041
+ const lineMatchA = /^Line\s+(\d+):/.exec(a);
3042
+ const lineMatchB = /^Line\s+(\d+):/.exec(b);
3043
+ const lineNumberA = lineMatchA?.[LINE_NUMBER_MATCH_GROUP_INDEX];
3044
+ const lineNumberB = lineMatchB?.[LINE_NUMBER_MATCH_GROUP_INDEX];
3045
+ const lineNumA = lineMatchA ? Number.parseInt(
3046
+ lineNumberA ?? "",
3047
+ PARSE_INT_RADIX
3048
+ ) : Number.MAX_SAFE_INTEGER;
3049
+ const lineNumB = lineMatchB ? Number.parseInt(
3050
+ lineNumberB ?? "",
3051
+ PARSE_INT_RADIX
3052
+ ) : Number.MAX_SAFE_INTEGER;
3053
+ if (lineNumA !== lineNumB) {
3054
+ return lineNumA - lineNumB;
3055
+ }
3056
+ return a.localeCompare(b);
3057
+ }
3058
+ );
3059
+ for (const issue of sortedIssues) {
3060
+ console.log(` - ${issue}`);
3061
+ }
3062
+ }
3063
+ }
2361
3064
  console.log("\n\u{1F50D} XPath Coverage:");
2362
3065
  if (result.xpathCoverage.overallSuccess) {
2363
3066
  console.log(" Status: \u2705 Complete");
2364
3067
  } else {
2365
- const INCOMPLETE_STATUS_MESSAGE = " Status: \u26A0\uFE0F Incomplete";
2366
- console.log(INCOMPLETE_STATUS_MESSAGE);
3068
+ console.log(" Status: \u26A0\uFE0F Incomplete");
2367
3069
  }
2368
3070
  if (result.xpathCoverage.coverage.length > MIN_COUNT4) {
2369
3071
  console.log(
@@ -2425,7 +3127,7 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2425
3127
  console.log("\n\u274C Tests failed, incomplete coverage");
2426
3128
  }
2427
3129
  tester.cleanup();
2428
- const coverageData = coverageTracker !== null ? coverageTracker.getCoverageData() : void 0;
3130
+ const coverageData = coverageTracker ? coverageTracker.getCoverageData() : void 0;
2429
3131
  return {
2430
3132
  coverageData,
2431
3133
  filePath: ruleFilePath,
@@ -2462,8 +3164,7 @@ async function main() {
2462
3164
  console.error("\u274C Invalid path argument");
2463
3165
  process.exit(EXIT_CODE_ERROR);
2464
3166
  }
2465
- const COVERAGE_FLAG = "--coverage";
2466
- const hasCoverageFlag = args.length > SECOND_ARG_INDEX && args[SECOND_ARG_INDEX] === COVERAGE_FLAG;
3167
+ const hasCoverageFlag = args.length > SECOND_ARG_INDEX && args[SECOND_ARG_INDEX] === "--coverage";
2467
3168
  if (!existsSync2(pathArg)) {
2468
3169
  console.error(`\u274C Path not found: ${pathArg}`);
2469
3170
  process.exit(EXIT_CODE_ERROR);
@@ -2501,8 +3202,8 @@ async function main() {
2501
3202
  const coverageTrackers = hasCoverageFlag ? /* @__PURE__ */ new Map() : null;
2502
3203
  const tasks = xmlFiles.map(
2503
3204
  (filePath) => async () => {
2504
- const tracker = coverageTrackers !== null ? coverageTrackers.get(filePath) ?? new CoverageTracker(filePath) : null;
2505
- if (tracker !== null && coverageTrackers !== null) {
3205
+ const tracker = coverageTrackers ? coverageTrackers.get(filePath) ?? new CoverageTracker(filePath) : null;
3206
+ if (tracker && coverageTrackers) {
2506
3207
  coverageTrackers.set(filePath, tracker);
2507
3208
  }
2508
3209
  return testRuleFile(filePath, tracker, maxExampleConcurrency);
@@ -2532,19 +3233,13 @@ async function main() {
2532
3233
  console.log(` - ${result.filePath}${errorSuffix}`);
2533
3234
  });
2534
3235
  }
2535
- if (hasCoverageFlag && coverageTrackers !== null) {
2536
- const coverageData = Array.from(
2537
- coverageTrackers.values()
2538
- ).map(
3236
+ if (hasCoverageFlag && coverageTrackers) {
3237
+ const coverageData = Array.from(coverageTrackers.values()).map(
2539
3238
  (tracker) => tracker.getCoverageData()
2540
3239
  );
2541
- const COVERAGE_REPORT_PATH = "coverage/lcov.info";
2542
3240
  try {
2543
- generateLcovReport(coverageData, COVERAGE_REPORT_PATH);
2544
- console.log(
2545
- `
2546
- \u{1F4CA} Coverage report generated: ${COVERAGE_REPORT_PATH}`
2547
- );
3241
+ generateLcovReport(coverageData, "coverage/lcov.info");
3242
+ console.log("\n\u{1F4CA} Coverage report generated: coverage/lcov.info");
2548
3243
  } catch (error) {
2549
3244
  const errorMessage = error instanceof Error ? error.message : String(error);
2550
3245
  console.error(