test-pmd-rule 1.0.1 → 1.0.3

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.
@@ -220,8 +220,229 @@ function analyzeXPath(xpath) {
220
220
  };
221
221
  }
222
222
 
223
- // src/xpath/checkCoverage.ts
223
+ // src/xpath/coverage/conditional/checkComparison.ts
224
+ var MIN_EXPRESSION_LENGTH = 0;
224
225
  var MIN_COUNT = 0;
226
+ var MIN_REQUIRED_COUNT = 1;
227
+ var MIN_DEMONSTRATION_COUNT = 1;
228
+ var MIN_UNIQUE_VALUES = 1;
229
+ var ATTR_PREFIX_LENGTH = 1;
230
+ function checkComparisonDemonstration(attributes, content) {
231
+ const attrValues = {};
232
+ for (const attr of attributes) {
233
+ const valueMatches = content.match(
234
+ new RegExp(`${attr}\\s*:\\s*([^\\s\\n]+)`, "gi")
235
+ );
236
+ if (valueMatches !== null) {
237
+ attrValues[attr] = valueMatches.map((match) => {
238
+ const parts = match.split(":");
239
+ return parts[ATTR_PREFIX_LENGTH].trim();
240
+ });
241
+ }
242
+ }
243
+ const uniqueValues = new Set(Object.values(attrValues).flat());
244
+ return uniqueValues.size > MIN_UNIQUE_VALUES;
245
+ }
246
+ function checkComparisonCoverage(conditional, content) {
247
+ if (conditional.expression.length === MIN_EXPRESSION_LENGTH) {
248
+ return {
249
+ details: [],
250
+ evidence: [],
251
+ message: "No expression to check",
252
+ success: false
253
+ };
254
+ }
255
+ const attrMatches = conditional.expression.match(/@([A-Za-z][A-Za-z0-9]*)/g);
256
+ if (attrMatches === null || attrMatches.length === MIN_COUNT) {
257
+ return {
258
+ details: [],
259
+ evidence: [],
260
+ message: "No attributes found in comparison",
261
+ success: false
262
+ };
263
+ }
264
+ const attributes = attrMatches.map((match) => match.slice(ATTR_PREFIX_LENGTH));
265
+ const demonstratesComparison = checkComparisonDemonstration(attributes, content);
266
+ const demonstrationCount = demonstratesComparison ? MIN_DEMONSTRATION_COUNT : MIN_COUNT;
267
+ const expressionStr = conditional.expression;
268
+ return {
269
+ details: [],
270
+ evidence: [
271
+ {
272
+ count: demonstrationCount,
273
+ description: `Comparison ${expressionStr} coverage`,
274
+ required: MIN_REQUIRED_COUNT,
275
+ type: "valid"
276
+ }
277
+ ],
278
+ message: demonstratesComparison ? `Comparison ${expressionStr} is demonstrated` : `Comparison ${expressionStr} not demonstrated in content`,
279
+ success: demonstratesComparison
280
+ };
281
+ }
282
+
283
+ // src/xpath/coverage/conditional/strategies.ts
284
+ function checkAndOperatorCoverage(conditional, content) {
285
+ const MIN_EXPRESSION_LENGTH2 = 0;
286
+ const MIN_COUNT4 = 0;
287
+ const MIN_REQUIRED_COUNT2 = 1;
288
+ if (conditional.expression.length === MIN_EXPRESSION_LENGTH2) {
289
+ return {
290
+ details: [],
291
+ evidence: [],
292
+ message: "No expression to check",
293
+ success: false
294
+ };
295
+ }
296
+ const contentLower = content.toLowerCase();
297
+ const expressionLower = conditional.expression.toLowerCase();
298
+ if (expressionLower.includes("@final") || expressionLower.includes("final")) {
299
+ const hasFinal = contentLower.includes("final");
300
+ return {
301
+ details: [],
302
+ evidence: [
303
+ {
304
+ count: hasFinal ? MIN_REQUIRED_COUNT2 : MIN_COUNT4,
305
+ description: `AND condition "${conditional.expression}" coverage`,
306
+ required: MIN_REQUIRED_COUNT2,
307
+ type: "valid"
308
+ }
309
+ ],
310
+ message: hasFinal ? `AND condition "${conditional.expression}" is covered` : `AND condition "${conditional.expression}" not covered - missing 'final' keyword`,
311
+ success: hasFinal
312
+ };
313
+ }
314
+ if (expressionLower.includes("@static") || expressionLower.includes("static")) {
315
+ const hasStatic = contentLower.includes("static");
316
+ return {
317
+ details: [],
318
+ evidence: [
319
+ {
320
+ count: hasStatic ? MIN_REQUIRED_COUNT2 : MIN_COUNT4,
321
+ description: `AND condition "${conditional.expression}" coverage`,
322
+ required: MIN_REQUIRED_COUNT2,
323
+ type: "valid"
324
+ }
325
+ ],
326
+ message: hasStatic ? `AND condition "${conditional.expression}" is covered` : `AND condition "${conditional.expression}" not covered - missing 'static' keyword`,
327
+ success: hasStatic
328
+ };
329
+ }
330
+ const keywords = conditional.expression.split(/[=<>!()\[\]]+/).map((s) => s.trim()).filter((s) => s.length > MIN_EXPRESSION_LENGTH2 && !s.startsWith("@"));
331
+ const hasKeywords = keywords.some(
332
+ (keyword) => contentLower.includes(keyword.toLowerCase())
333
+ );
334
+ return {
335
+ details: [],
336
+ evidence: [
337
+ {
338
+ count: hasKeywords ? MIN_REQUIRED_COUNT2 : MIN_COUNT4,
339
+ description: `AND condition "${conditional.expression}" coverage`,
340
+ required: MIN_REQUIRED_COUNT2,
341
+ type: "valid"
342
+ }
343
+ ],
344
+ message: hasKeywords ? `AND condition "${conditional.expression}" is covered` : `AND condition "${conditional.expression}" not covered`,
345
+ success: hasKeywords
346
+ };
347
+ }
348
+ function checkNotConditionCoverage(conditional, content) {
349
+ const MIN_EXPRESSION_LENGTH2 = 0;
350
+ const MIN_COUNT4 = 0;
351
+ const MIN_REQUIRED_COUNT2 = 1;
352
+ if (conditional.expression.length === MIN_EXPRESSION_LENGTH2) {
353
+ return {
354
+ details: [],
355
+ evidence: [],
356
+ message: "No expression to check",
357
+ success: false
358
+ };
359
+ }
360
+ const contentLower = content.toLowerCase();
361
+ const expressionLower = conditional.expression.toLowerCase();
362
+ if (expressionLower.includes("fielddeclarationstatements") || expressionLower.includes("field[") || expressionLower.includes("ancestor::field")) {
363
+ const hasStaticFinal = /static\s+final/.test(contentLower);
364
+ const hasFieldDeclaration = contentLower.includes("field") || /(private|public|protected)\s+(static\s+)?(final\s+)?\w+\s+\w+\s*=/.test(
365
+ contentLower
366
+ );
367
+ const isCovered = hasStaticFinal && hasFieldDeclaration;
368
+ return {
369
+ details: [],
370
+ evidence: [
371
+ {
372
+ count: isCovered ? MIN_REQUIRED_COUNT2 : MIN_COUNT4,
373
+ description: `NOT condition "${conditional.expression}" coverage - static final fields present`,
374
+ required: MIN_REQUIRED_COUNT2,
375
+ type: "valid"
376
+ }
377
+ ],
378
+ message: isCovered ? `NOT condition "${conditional.expression}" is covered - static final fields found` : `NOT condition "${conditional.expression}" not covered - missing static final field declarations`,
379
+ success: isCovered
380
+ };
381
+ }
382
+ const keywords = conditional.expression.split(/[=<>!()\[\]:]+/).map((s) => s.trim()).filter(
383
+ (s) => s.length > MIN_EXPRESSION_LENGTH2 && !s.startsWith("@") && s !== "ancestor" && s !== "not"
384
+ );
385
+ const hasKeywords = keywords.some(
386
+ (keyword) => contentLower.includes(keyword.toLowerCase())
387
+ );
388
+ return {
389
+ details: [],
390
+ evidence: [
391
+ {
392
+ count: hasKeywords ? MIN_REQUIRED_COUNT2 : MIN_COUNT4,
393
+ description: `NOT condition "${conditional.expression}" coverage`,
394
+ required: MIN_REQUIRED_COUNT2,
395
+ type: "valid"
396
+ }
397
+ ],
398
+ message: hasKeywords ? `NOT condition "${conditional.expression}" is covered` : `NOT condition "${conditional.expression}" not covered`,
399
+ success: hasKeywords
400
+ };
401
+ }
402
+ function checkOrBranchCoverage(_conditional, _content) {
403
+ return {
404
+ details: [],
405
+ evidence: [],
406
+ message: "Or branch coverage check not implemented",
407
+ success: false
408
+ };
409
+ }
410
+ function checkIfConditionCoverage(_conditional, _content) {
411
+ return {
412
+ details: [],
413
+ evidence: [],
414
+ message: "If condition coverage check not implemented",
415
+ success: false
416
+ };
417
+ }
418
+ function checkQuantifiedCoverage(_conditional, _content) {
419
+ return {
420
+ details: [],
421
+ evidence: [],
422
+ message: "Quantified condition coverage check not implemented",
423
+ success: false
424
+ };
425
+ }
426
+ function checkBooleanFunctionCoverage(_conditional, _content) {
427
+ return {
428
+ details: [],
429
+ evidence: [],
430
+ message: "Boolean function coverage check not implemented",
431
+ success: false
432
+ };
433
+ }
434
+ var conditionalCheckers = {
435
+ and_operator: checkAndOperatorCoverage,
436
+ boolean_function: checkBooleanFunctionCoverage,
437
+ comparison: checkComparisonCoverage,
438
+ if_condition: checkIfConditionCoverage,
439
+ not_condition: checkNotConditionCoverage,
440
+ or_branch: checkOrBranchCoverage,
441
+ quantified: checkQuantifiedCoverage
442
+ };
443
+
444
+ // src/xpath/checkCoverage.ts
445
+ var MIN_COUNT2 = 0;
225
446
  var NOT_FOUND_INDEX = -1;
226
447
  var LINE_OFFSET = 1;
227
448
  function findAttributeLineNumber(ruleFilePath, xpath, attribute) {
@@ -257,11 +478,11 @@ function findAttributeLineNumber(ruleFilePath, xpath, attribute) {
257
478
  const line = lines[i];
258
479
  if (line.includes("<value>")) {
259
480
  const xpathBeforeAttribute = xpath.substring(
260
- MIN_COUNT,
481
+ MIN_COUNT2,
261
482
  xpathIndex
262
483
  );
263
484
  const newlineMatches = xpathBeforeAttribute.match(/\n/g);
264
- const newlineCount = newlineMatches ? newlineMatches.length : MIN_COUNT;
485
+ const newlineCount = newlineMatches ? newlineMatches.length : MIN_COUNT2;
265
486
  return i + LINE_OFFSET + newlineCount;
266
487
  }
267
488
  }
@@ -303,11 +524,11 @@ function findNodeTypeLineNumber(ruleFilePath, xpath, nodeType) {
303
524
  const line = lines[i];
304
525
  if (line.includes("<value>")) {
305
526
  const xpathBeforeNodeType = xpath.substring(
306
- MIN_COUNT,
527
+ MIN_COUNT2,
307
528
  xpathIndex
308
529
  );
309
530
  const newlineMatches = xpathBeforeNodeType.match(/\n/g);
310
- const newlineCount = newlineMatches ? newlineMatches.length : MIN_COUNT;
531
+ const newlineCount = newlineMatches ? newlineMatches.length : MIN_COUNT2;
311
532
  return i + LINE_OFFSET + newlineCount;
312
533
  }
313
534
  }
@@ -381,7 +602,7 @@ function checkNodeTypeCoverage(nodeTypes, content, options) {
381
602
  missingNodeTypes.push(nodeType);
382
603
  }
383
604
  }
384
- const missingList = missingNodeTypes.length > MIN_COUNT ? missingNodeTypes.map((item) => {
605
+ const missingList = missingNodeTypes.length > MIN_COUNT2 ? missingNodeTypes.map((item) => {
385
606
  if (options !== void 0) {
386
607
  const ruleFilePathValue = options.ruleFilePath;
387
608
  const xpathValue = options.xpath;
@@ -397,9 +618,9 @@ function checkNodeTypeCoverage(nodeTypes, content, options) {
397
618
  }
398
619
  return ` - ${item}`;
399
620
  }).join("\n") : "";
400
- const missingText = missingNodeTypes.length > MIN_COUNT ? `Missing:
621
+ const missingText = missingNodeTypes.length > MIN_COUNT2 ? `Missing:
401
622
  ${missingList}` : "";
402
- const description = missingText.length > MIN_COUNT ? missingText : "";
623
+ const description = missingText.length > MIN_COUNT2 ? missingText : "";
403
624
  return {
404
625
  count: foundNodeTypes.length,
405
626
  description,
@@ -413,16 +634,35 @@ function truncateExpression(expression, maxLength) {
413
634
  if (normalized.length <= maxLength) {
414
635
  return normalized;
415
636
  }
416
- return `${normalized.substring(MIN_COUNT, maxLength)}...`;
637
+ return `${normalized.substring(MIN_COUNT2, maxLength)}...`;
638
+ }
639
+ function mapConditionalTypeToCheckerKey(type) {
640
+ const typeMap = {
641
+ and: "and_operator",
642
+ not: "not_condition",
643
+ or: "or_branch"
644
+ };
645
+ return typeMap[type] ?? type;
417
646
  }
418
647
  function checkConditionalCoverage(conditionals, content) {
419
648
  const lowerContent = content.toLowerCase();
420
649
  const foundConditionals = [];
421
650
  const missingConditionals = [];
422
651
  for (const conditional of conditionals) {
423
- const exprLower = conditional.expression.toLowerCase();
652
+ const checkerKey = mapConditionalTypeToCheckerKey(conditional.type);
653
+ const checker = conditionalCheckers[checkerKey];
424
654
  let isCovered = false;
425
- isCovered = lowerContent.includes(exprLower) || lowerContent.includes("if");
655
+ if (checker) {
656
+ const result = checker(conditional, content);
657
+ isCovered = result.success;
658
+ if (!isCovered) {
659
+ const exprLower = conditional.expression.toLowerCase();
660
+ isCovered = lowerContent.includes(exprLower) || lowerContent.includes("if");
661
+ }
662
+ } else {
663
+ const exprLower = conditional.expression.toLowerCase();
664
+ isCovered = lowerContent.includes(exprLower) || lowerContent.includes("if");
665
+ }
426
666
  if (isCovered) {
427
667
  const displayExpr = truncateExpression(
428
668
  conditional.expression,
@@ -437,10 +677,10 @@ function checkConditionalCoverage(conditionals, content) {
437
677
  missingConditionals.push(` - ${conditional.type}: ${displayExpr}`);
438
678
  }
439
679
  }
440
- const missingList = missingConditionals.length > MIN_COUNT ? missingConditionals : [];
441
- const missingText = missingList.length > MIN_COUNT ? `Missing:
680
+ const missingList = missingConditionals.length > MIN_COUNT2 ? missingConditionals : [];
681
+ const missingText = missingList.length > MIN_COUNT2 ? `Missing:
442
682
  ${missingList.join("\n")}` : "";
443
- const description = missingText.length > MIN_COUNT ? missingText : "";
683
+ const description = missingText.length > MIN_COUNT2 ? missingText : "";
444
684
  return {
445
685
  count: foundConditionals.length,
446
686
  description,
@@ -495,7 +735,7 @@ function checkAttributeCoverage(attributes, content, options) {
495
735
  missingAttributes.push(attr);
496
736
  }
497
737
  }
498
- const missingList = missingAttributes.length > MIN_COUNT ? missingAttributes.map((item) => {
738
+ const missingList = missingAttributes.length > MIN_COUNT2 ? missingAttributes.map((item) => {
499
739
  const ruleFilePath = options?.ruleFilePath;
500
740
  const xpathValue = options?.xpath;
501
741
  const hasOptions = ruleFilePath !== void 0 && xpathValue !== void 0;
@@ -512,9 +752,9 @@ function checkAttributeCoverage(attributes, content, options) {
512
752
  }
513
753
  return ` - ${item}`;
514
754
  }).join("\n") : "";
515
- const missingText = missingAttributes.length > MIN_COUNT ? `Missing:
755
+ const missingText = missingAttributes.length > MIN_COUNT2 ? `Missing:
516
756
  ${missingList}` : "";
517
- const description = missingText.length > MIN_COUNT ? missingText : "";
757
+ const description = missingText.length > MIN_COUNT2 ? missingText : "";
518
758
  return {
519
759
  count: foundAttributes.length,
520
760
  description,
@@ -534,13 +774,13 @@ function checkOperatorCoverage(operators, content) {
534
774
  missingOperators.push(op);
535
775
  }
536
776
  }
537
- const foundList = foundOperators.length > MIN_COUNT ? foundOperators.map((item) => ` - ${item}`).join("\n") : "";
538
- const missingList = missingOperators.length > MIN_COUNT ? missingOperators.map((item) => ` - ${item}`).join("\n") : "";
777
+ const foundList = foundOperators.length > MIN_COUNT2 ? foundOperators.map((item) => ` - ${item}`).join("\n") : "";
778
+ const missingList = missingOperators.length > MIN_COUNT2 ? missingOperators.map((item) => ` - ${item}`).join("\n") : "";
539
779
  const foundText = foundList;
540
- const missingText = missingOperators.length > MIN_COUNT ? `Missing:
780
+ const missingText = missingOperators.length > MIN_COUNT2 ? `Missing:
541
781
  ${missingList}` : "";
542
- const hasFound = foundText.length > MIN_COUNT;
543
- const hasMissing = missingText.length > MIN_COUNT;
782
+ const hasFound = foundText.length > MIN_COUNT2;
783
+ const hasMissing = missingText.length > MIN_COUNT2;
544
784
  let description = "";
545
785
  if (hasFound) {
546
786
  description = hasMissing ? `${foundText}
@@ -557,8 +797,8 @@ ${missingText}` : foundText;
557
797
  };
558
798
  }
559
799
  function checkXPathCoverage(xpath, examples, ruleFilePath) {
560
- const hasXPath = xpath !== null && xpath !== void 0 && xpath.length > MIN_COUNT;
561
- if (!hasXPath || examples.length === MIN_COUNT) {
800
+ const hasXPath = xpath !== null && xpath !== void 0 && xpath.length > MIN_COUNT2;
801
+ if (!hasXPath || examples.length === MIN_COUNT2) {
562
802
  return {
563
803
  coverage: [],
564
804
  overallSuccess: false,
@@ -570,11 +810,11 @@ function checkXPathCoverage(xpath, examples, ruleFilePath) {
570
810
  const coverageResults = [];
571
811
  const uncoveredBranches = [];
572
812
  const coveredLineNumbers = /* @__PURE__ */ new Set();
573
- if (analysis.nodeTypes.length > MIN_COUNT) {
813
+ if (analysis.nodeTypes.length > MIN_COUNT2) {
574
814
  const ruleFilePathValue = ruleFilePath;
575
815
  const xpathValue = xpath;
576
- const hasRuleFilePath = ruleFilePathValue !== void 0 && ruleFilePathValue.length > MIN_COUNT;
577
- const hasXpathValue = xpathValue.length > MIN_COUNT;
816
+ const hasRuleFilePath = ruleFilePathValue !== void 0 && ruleFilePathValue.length > MIN_COUNT2;
817
+ const hasXpathValue = xpathValue.length > MIN_COUNT2;
578
818
  const nodeTypeOptions = hasRuleFilePath && hasXpathValue ? {
579
819
  lineNumberCollector: (lineNumber) => {
580
820
  coveredLineNumbers.add(lineNumber);
@@ -600,7 +840,7 @@ function checkXPathCoverage(xpath, examples, ruleFilePath) {
600
840
  );
601
841
  }
602
842
  }
603
- if (analysis.conditionals.length > MIN_COUNT) {
843
+ if (analysis.conditionals.length > MIN_COUNT2) {
604
844
  const conditionalEvidence = checkConditionalCoverage(
605
845
  analysis.conditionals,
606
846
  allContent
@@ -618,11 +858,11 @@ function checkXPathCoverage(xpath, examples, ruleFilePath) {
618
858
  );
619
859
  }
620
860
  }
621
- if (analysis.attributes.length > MIN_COUNT) {
861
+ if (analysis.attributes.length > MIN_COUNT2) {
622
862
  const ruleFilePathValue = ruleFilePath;
623
863
  const xpathValue = xpath;
624
- const hasRuleFilePath = ruleFilePathValue !== void 0 && ruleFilePathValue.length > MIN_COUNT;
625
- const hasXpathValue = xpathValue.length > MIN_COUNT;
864
+ const hasRuleFilePath = ruleFilePathValue !== void 0 && ruleFilePathValue.length > MIN_COUNT2;
865
+ const hasXpathValue = xpathValue.length > MIN_COUNT2;
626
866
  const attributeOptions = hasRuleFilePath && hasXpathValue ? {
627
867
  lineNumberCollector: (lineNumber) => {
628
868
  coveredLineNumbers.add(lineNumber);
@@ -648,7 +888,7 @@ function checkXPathCoverage(xpath, examples, ruleFilePath) {
648
888
  );
649
889
  }
650
890
  }
651
- if (analysis.operators.length > MIN_COUNT) {
891
+ if (analysis.operators.length > MIN_COUNT2) {
652
892
  const operatorEvidence = checkOperatorCoverage(
653
893
  analysis.operators,
654
894
  allContent
@@ -666,7 +906,7 @@ function checkXPathCoverage(xpath, examples, ruleFilePath) {
666
906
  );
667
907
  }
668
908
  }
669
- const overallSuccess = coverageResults.length === MIN_COUNT || coverageResults.every(
909
+ const overallSuccess = coverageResults.length === MIN_COUNT2 || coverageResults.every(
670
910
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Callback parameter for every
671
911
  (result) => result.success
672
912
  );
@@ -782,6 +1022,34 @@ import tmp from "tmp";
782
1022
  var EMPTY_LENGTH = 0;
783
1023
  var FIRST_CAPTURE_GROUP_INDEX = 1;
784
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
+ }
785
1053
  function inferReturnType(code, methodName) {
786
1054
  if (new RegExp(`\\b${methodName}\\s*\\(\\s*\\)\\s*[><=!]+\\s*\\d+`).test(
787
1055
  code
@@ -966,8 +1234,6 @@ function createTestFile({
966
1234
  });
967
1235
  const tempFile = tmpFile.name;
968
1236
  const parsed = parseExample(exampleContent);
969
- let classContent = `public class TestClass${String(exampleIndex)} {
970
- `;
971
1237
  let codeToInclude = [];
972
1238
  if (includeViolations && !includeValids) {
973
1239
  codeToInclude = parsed.violations;
@@ -976,59 +1242,200 @@ function createTestFile({
976
1242
  } else {
977
1243
  codeToInclude = [...parsed.violations, ...parsed.valids];
978
1244
  }
979
- const hasClassDefinition = codeToInclude.some(
980
- (line) => line.trim().startsWith("public class ") || line.trim().startsWith("class ")
981
- );
982
- if (hasClassDefinition) {
1245
+ const TEST_CLASS_NAME = `TestClass${String(exampleIndex)}`;
1246
+ const fullExampleContent = parsed.content;
1247
+ const ZERO_BRACE_DEPTH = 0;
1248
+ const hasTopLevelClass = (() => {
1249
+ let foundTopLevelClass = false;
1250
+ let braceDepth = ZERO_BRACE_DEPTH;
1251
+ const exampleLines = fullExampleContent.split("\n");
1252
+ for (const line of exampleLines) {
1253
+ const trimmed = line.trim();
1254
+ const isClassDef = trimmed.startsWith("public class ") || trimmed.startsWith("class ") || trimmed.startsWith("private class ");
1255
+ if (isClassDef && braceDepth === ZERO_BRACE_DEPTH) {
1256
+ foundTopLevelClass = true;
1257
+ break;
1258
+ }
1259
+ const openBracesMatch = line.match(/{/g);
1260
+ const closeBracesMatch = line.match(/}/g);
1261
+ const openBraces = openBracesMatch ? openBracesMatch.length : ZERO_BRACE_DEPTH;
1262
+ const closeBraces = closeBracesMatch ? closeBracesMatch.length : ZERO_BRACE_DEPTH;
1263
+ braceDepth += openBraces - closeBraces;
1264
+ }
1265
+ return foundTopLevelClass;
1266
+ })();
1267
+ const hasClassLikeStructures = (() => {
1268
+ if (hasTopLevelClass) {
1269
+ return false;
1270
+ }
1271
+ const fieldDeclarationRegex = /^\s*(public|private|protected)\s+\w+\s+\w+\s*[;=]/;
1272
+ return codeToInclude.some((line) => {
1273
+ const trimmed = line.trim();
1274
+ const isMethodSignature = (trimmed.startsWith("public ") || trimmed.startsWith("private ") || trimmed.startsWith("protected ")) && trimmed.includes("() {") && !trimmed.includes("class ");
1275
+ const fieldMatch = fieldDeclarationRegex.exec(trimmed);
1276
+ const isFieldDeclaration = Boolean(fieldMatch);
1277
+ const isInnerClass = trimmed.includes("class ") && trimmed.includes("{");
1278
+ return isMethodSignature || isFieldDeclaration || isInnerClass;
1279
+ });
1280
+ })();
1281
+ let classContent = "";
1282
+ if (hasTopLevelClass) {
1283
+ const exampleLines = fullExampleContent.split("\n");
983
1284
  const extractedCode = [];
1285
+ let insideClass = false;
1286
+ let classBraceDepth = ZERO_BRACE_DEPTH;
1287
+ const INITIAL_BRACE_DEPTH = 1;
1288
+ let methodBraceDepth = ZERO_BRACE_DEPTH;
1289
+ let currentMode = null;
984
1290
  let insideMethod = false;
985
- let braceDepth = 0;
986
- for (const line of codeToInclude) {
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];
987
1296
  const trimmed = line.trim();
988
- if (trimmed.startsWith("public class ") || trimmed.startsWith("class ") || trimmed.startsWith("private class ")) {
989
- continue;
1297
+ if (trimmed.includes("// \u274C")) {
1298
+ currentMode = "violation";
1299
+ } else if (trimmed.includes("// \u2705")) {
1300
+ currentMode = "valid";
1301
+ } else if (trimmed.startsWith("// Violation:")) {
1302
+ currentMode = "violation";
1303
+ } else if (trimmed.startsWith("// Valid:")) {
1304
+ currentMode = "valid";
990
1305
  }
991
- const isMethodSignature = (trimmed.startsWith("public ") || trimmed.startsWith("private ") || trimmed.startsWith("protected ")) && trimmed.includes("() {") && !insideMethod;
992
- const INITIAL_BRACE_DEPTH = 1;
993
- const ZERO_BRACE_DEPTH = 0;
994
- if (isMethodSignature) {
995
- insideMethod = true;
996
- braceDepth = INITIAL_BRACE_DEPTH;
997
- continue;
1306
+ const shouldInclude = !trimmed.startsWith("//") && trimmed.length > EMPTY_LENGTH && (includeViolations && currentMode === "violation" || includeValids && currentMode === "valid");
1307
+ if (trimmed.startsWith("public class ") || trimmed.startsWith("class ") || trimmed.startsWith("private class ")) {
1308
+ const CLASS_PREFIX_GROUP_INDEX = 1;
1309
+ if (classBraceDepth === ZERO_BRACE_DEPTH) {
1310
+ const classRegex = /^(public\s+|private\s+)?class\s+(\w+)/;
1311
+ const classMatch = classRegex.exec(trimmed);
1312
+ if (classMatch) {
1313
+ const classPrefix = classMatch[CLASS_PREFIX_GROUP_INDEX] ?? "";
1314
+ const newClassLine = `${classPrefix}class ${TEST_CLASS_NAME} {`;
1315
+ extractedCode.push(newClassLine);
1316
+ insideClass = true;
1317
+ classBraceDepth = INITIAL_BRACE_DEPTH;
1318
+ continue;
1319
+ }
1320
+ }
998
1321
  }
999
- if (insideMethod) {
1000
- const openBraces = (line.match(/{/g) ?? []).length;
1001
- const closeBraces = (line.match(/}/g) ?? []).length;
1002
- braceDepth += openBraces - closeBraces;
1003
- if (braceDepth > ZERO_BRACE_DEPTH) {
1004
- extractedCode.push(line);
1322
+ if (insideClass || classBraceDepth > ZERO_BRACE_DEPTH) {
1323
+ const openBracesMatch = line.match(/{/g);
1324
+ const closeBracesMatch = line.match(/}/g);
1325
+ const openBraces = openBracesMatch ? openBracesMatch.length : ZERO_BRACE_DEPTH;
1326
+ const closeBraces = closeBracesMatch ? closeBracesMatch.length : ZERO_BRACE_DEPTH;
1327
+ const prevClassBraceDepth = classBraceDepth;
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
+ }
1349
+ if (shouldInclude || trimmed === "{" || trimmed === "}") {
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);
1374
+ }
1005
1375
  }
1006
- if (braceDepth <= ZERO_BRACE_DEPTH) {
1376
+ if (classBraceDepth <= ZERO_BRACE_DEPTH) {
1377
+ insideClass = false;
1378
+ classBraceDepth = ZERO_BRACE_DEPTH;
1007
1379
  insideMethod = false;
1008
- braceDepth = ZERO_BRACE_DEPTH;
1380
+ methodDeclaration = null;
1381
+ methodDeclarationOriginal = null;
1009
1382
  }
1010
- } else if (!trimmed.startsWith("}") && trimmed.length > EMPTY_LENGTH && !trimmed.startsWith("public ") && !trimmed.startsWith("private ")) {
1011
- extractedCode.push(line);
1383
+ } else if (shouldInclude) {
1384
+ const codeLine = removeInlineMarkers(line).trim();
1385
+ extractedCode.push(codeLine);
1012
1386
  }
1013
1387
  }
1014
- codeToInclude = extractedCode.length > EMPTY_LENGTH ? extractedCode : codeToInclude;
1015
- }
1016
- if (codeToInclude.length > EMPTY_LENGTH) {
1017
- classContent += ` public void testMethod${String(exampleIndex)}() {
1388
+ const helperMethods = extractHelperMethods(codeToInclude);
1389
+ const classContentStr = extractedCode.join("\n");
1390
+ const lastBraceIndex = classContentStr.lastIndexOf("}");
1391
+ if (helperMethods.length > EMPTY_LENGTH) {
1392
+ const beforeLastBrace = classContentStr.substring(
1393
+ ZERO_BRACE_DEPTH,
1394
+ lastBraceIndex
1395
+ );
1396
+ const afterLastBrace = classContentStr.substring(lastBraceIndex);
1397
+ classContent = beforeLastBrace + "\n";
1398
+ for (const method of helperMethods) {
1399
+ classContent += ` ${method}
1400
+ `;
1401
+ }
1402
+ classContent += afterLastBrace + "\n";
1403
+ } else {
1404
+ classContent = classContentStr + "\n";
1405
+ }
1406
+ } else if (hasClassLikeStructures) {
1407
+ classContent = `public class ${TEST_CLASS_NAME} {
1018
1408
  `;
1019
1409
  codeToInclude.forEach((line) => {
1020
- classContent += ` ${line}
1410
+ classContent += ` ${line}
1021
1411
  `;
1022
1412
  });
1023
- classContent += ` }
1413
+ const helperMethods = extractHelperMethods(codeToInclude);
1414
+ for (const method of helperMethods) {
1415
+ classContent += ` ${method}
1024
1416
  `;
1025
- }
1026
- const helperMethods = extractHelperMethods(codeToInclude);
1027
- for (const method of helperMethods) {
1028
- classContent += ` ${method}
1417
+ }
1418
+ classContent += "}\n";
1419
+ } else {
1420
+ classContent = `public class ${TEST_CLASS_NAME} {
1421
+ `;
1422
+ if (codeToInclude.length > EMPTY_LENGTH) {
1423
+ classContent += ` public void testMethod${String(exampleIndex)}() {
1424
+ `;
1425
+ codeToInclude.forEach((line) => {
1426
+ classContent += ` ${line}
1427
+ `;
1428
+ });
1429
+ classContent += ` }
1430
+ `;
1431
+ }
1432
+ const helperMethods = extractHelperMethods(codeToInclude);
1433
+ for (const method of helperMethods) {
1434
+ classContent += ` ${method}
1029
1435
  `;
1436
+ }
1437
+ classContent += "}\n";
1030
1438
  }
1031
- classContent += "}\n";
1032
1439
  writeFileSync(tempFile, classContent, "utf-8");
1033
1440
  return {
1034
1441
  filePath: tempFile,
@@ -1333,7 +1740,7 @@ var MIN_PATTERN_LENGTH = 10;
1333
1740
  var PATTERN_DISPLAY_LENGTH = 50;
1334
1741
  var INDEX_OFFSET2 = 1;
1335
1742
  var ZERO_ERRORS = 0;
1336
- var MIN_COUNT2 = 0;
1743
+ var MIN_COUNT3 = 0;
1337
1744
  function normalizeCode(code) {
1338
1745
  return code.replace(/\s+/g, " ").trim().toLowerCase();
1339
1746
  }
@@ -1341,7 +1748,7 @@ function checkPatternDuplicates(patterns, type, warnings) {
1341
1748
  for (const [pattern, exampleNumbers] of patterns.entries()) {
1342
1749
  if (exampleNumbers.length > MIN_DUPLICATE_COUNT && pattern.length > MIN_PATTERN_LENGTH) {
1343
1750
  const patternPreview = pattern.substring(
1344
- MIN_COUNT2,
1751
+ MIN_COUNT3,
1345
1752
  PATTERN_DISPLAY_LENGTH
1346
1753
  );
1347
1754
  warnings.push(
@@ -1978,11 +2385,8 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
1978
2385
  try {
1979
2386
  const tester = new RuleTester(ruleFilePath);
1980
2387
  const result = await tester.runCoverageTest(false, maxConcurrency);
1981
- const hasCoverageTracker = coverageTracker !== null;
1982
- const MIN_COVERED_LINES_COUNT = 0;
1983
- const coveredLines = result.xpathCoverage.coveredLineNumbers;
1984
- if (hasCoverageTracker && coveredLines && coveredLines.length > MIN_COVERED_LINES_COUNT) {
1985
- for (const lineNumber of coveredLines) {
2388
+ if (coverageTracker && result.xpathCoverage.coveredLineNumbers) {
2389
+ for (const lineNumber of result.xpathCoverage.coveredLineNumbers) {
1986
2390
  coverageTracker.recordXPathLine(lineNumber);
1987
2391
  }
1988
2392
  }
@@ -2003,7 +2407,7 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2003
2407
  );
2004
2408
  }
2005
2409
  }
2006
- const MIN_COUNT3 = 0;
2410
+ const MIN_COUNT4 = 0;
2007
2411
  const INDEX_OFFSET3 = 1;
2008
2412
  if (result.success) {
2009
2413
  console.log("\n\u{1F4CA} Test Summary:");
@@ -2020,10 +2424,9 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2020
2424
  if (result.xpathCoverage.overallSuccess) {
2021
2425
  console.log(" Status: \u2705 Complete");
2022
2426
  } else {
2023
- const INCOMPLETE_STATUS_MESSAGE = " Status: \u26A0\uFE0F Incomplete";
2024
- console.log(INCOMPLETE_STATUS_MESSAGE);
2427
+ console.log(" Status: \u26A0\uFE0F Incomplete");
2025
2428
  }
2026
- if (result.xpathCoverage.coverage.length > MIN_COUNT3) {
2429
+ if (result.xpathCoverage.coverage.length > MIN_COUNT4) {
2027
2430
  console.log(
2028
2431
  ` Coverage items: ${String(result.xpathCoverage.coverage.length)}`
2029
2432
  );
@@ -2033,17 +2436,17 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2033
2436
  const itemNumber = index + INDEX_OFFSET3;
2034
2437
  const status = coverage.success ? "\u2705" : coverage.evidence.some(
2035
2438
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Callback parameter for some
2036
- (evidence) => evidence.count > MIN_COUNT3 && evidence.count < evidence.required
2439
+ (evidence) => evidence.count > MIN_COUNT4 && evidence.count < evidence.required
2037
2440
  ) ? "\u26A0\uFE0F" : "\u274C";
2038
2441
  console.log(
2039
2442
  ` ${String(itemNumber)}. ${status} ${coverage.message}`
2040
2443
  );
2041
- if (coverage.evidence.length > MIN_COUNT3) {
2444
+ if (coverage.evidence.length > MIN_COUNT4) {
2042
2445
  coverage.evidence.forEach(
2043
2446
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Callback parameters for forEach
2044
2447
  (evidence) => {
2045
2448
  const { description } = evidence;
2046
- if (description.length > MIN_COUNT3) {
2449
+ if (description.length > MIN_COUNT4) {
2047
2450
  if (description.includes("\n")) {
2048
2451
  description.split("\n").forEach(
2049
2452
  (line) => {
@@ -2062,7 +2465,7 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2062
2465
  }
2063
2466
  );
2064
2467
  }
2065
- if (result.hardcodedValues.length > MIN_COUNT3) {
2468
+ if (result.hardcodedValues.length > MIN_COUNT4) {
2066
2469
  console.log(
2067
2470
  `
2068
2471
  \u26A0\uFE0F Hardcoded values found: ${String(result.hardcodedValues.length)}`
@@ -2083,7 +2486,7 @@ async function testRuleFile(ruleFilePath, coverageTracker, maxConcurrency) {
2083
2486
  console.log("\n\u274C Tests failed, incomplete coverage");
2084
2487
  }
2085
2488
  tester.cleanup();
2086
- const coverageData = coverageTracker !== null ? coverageTracker.getCoverageData() : void 0;
2489
+ const coverageData = coverageTracker ? coverageTracker.getCoverageData() : void 0;
2087
2490
  return {
2088
2491
  coverageData,
2089
2492
  filePath: ruleFilePath,
@@ -2120,8 +2523,7 @@ async function main() {
2120
2523
  console.error("\u274C Invalid path argument");
2121
2524
  process.exit(EXIT_CODE_ERROR);
2122
2525
  }
2123
- const COVERAGE_FLAG = "--coverage";
2124
- const hasCoverageFlag = args.length > SECOND_ARG_INDEX && args[SECOND_ARG_INDEX] === COVERAGE_FLAG;
2526
+ const hasCoverageFlag = args.length > SECOND_ARG_INDEX && args[SECOND_ARG_INDEX] === "--coverage";
2125
2527
  if (!existsSync2(pathArg)) {
2126
2528
  console.error(`\u274C Path not found: ${pathArg}`);
2127
2529
  process.exit(EXIT_CODE_ERROR);
@@ -2159,8 +2561,8 @@ async function main() {
2159
2561
  const coverageTrackers = hasCoverageFlag ? /* @__PURE__ */ new Map() : null;
2160
2562
  const tasks = xmlFiles.map(
2161
2563
  (filePath) => async () => {
2162
- const tracker = coverageTrackers !== null ? coverageTrackers.get(filePath) ?? new CoverageTracker(filePath) : null;
2163
- if (tracker !== null && coverageTrackers !== null) {
2564
+ const tracker = coverageTrackers ? coverageTrackers.get(filePath) ?? new CoverageTracker(filePath) : null;
2565
+ if (tracker && coverageTrackers) {
2164
2566
  coverageTrackers.set(filePath, tracker);
2165
2567
  }
2166
2568
  return testRuleFile(filePath, tracker, maxExampleConcurrency);
@@ -2190,19 +2592,13 @@ async function main() {
2190
2592
  console.log(` - ${result.filePath}${errorSuffix}`);
2191
2593
  });
2192
2594
  }
2193
- if (hasCoverageFlag && coverageTrackers !== null) {
2194
- const coverageData = Array.from(
2195
- coverageTrackers.values()
2196
- ).map(
2595
+ if (hasCoverageFlag && coverageTrackers) {
2596
+ const coverageData = Array.from(coverageTrackers.values()).map(
2197
2597
  (tracker) => tracker.getCoverageData()
2198
2598
  );
2199
- const COVERAGE_REPORT_PATH = "coverage/lcov.info";
2200
2599
  try {
2201
- generateLcovReport(coverageData, COVERAGE_REPORT_PATH);
2202
- console.log(
2203
- `
2204
- \u{1F4CA} Coverage report generated: ${COVERAGE_REPORT_PATH}`
2205
- );
2600
+ generateLcovReport(coverageData, "coverage/lcov.info");
2601
+ console.log("\n\u{1F4CA} Coverage report generated: coverage/lcov.info");
2206
2602
  } catch (error) {
2207
2603
  const errorMessage = error instanceof Error ? error.message : String(error);
2208
2604
  console.error(