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.
- package/README.md +1 -1
- package/dist/test-pmd-rule.js +497 -101
- package/dist/test-pmd-rule.js.map +4 -4
- package/package.json +1 -1
package/dist/test-pmd-rule.js
CHANGED
|
@@ -220,8 +220,229 @@ function analyzeXPath(xpath) {
|
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
// src/xpath/
|
|
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
|
-
|
|
481
|
+
MIN_COUNT2,
|
|
261
482
|
xpathIndex
|
|
262
483
|
);
|
|
263
484
|
const newlineMatches = xpathBeforeAttribute.match(/\n/g);
|
|
264
|
-
const newlineCount = newlineMatches ? newlineMatches.length :
|
|
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
|
-
|
|
527
|
+
MIN_COUNT2,
|
|
307
528
|
xpathIndex
|
|
308
529
|
);
|
|
309
530
|
const newlineMatches = xpathBeforeNodeType.match(/\n/g);
|
|
310
|
-
const newlineCount = newlineMatches ? newlineMatches.length :
|
|
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 >
|
|
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 >
|
|
621
|
+
const missingText = missingNodeTypes.length > MIN_COUNT2 ? `Missing:
|
|
401
622
|
${missingList}` : "";
|
|
402
|
-
const description = missingText.length >
|
|
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(
|
|
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
|
|
652
|
+
const checkerKey = mapConditionalTypeToCheckerKey(conditional.type);
|
|
653
|
+
const checker = conditionalCheckers[checkerKey];
|
|
424
654
|
let isCovered = false;
|
|
425
|
-
|
|
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 >
|
|
441
|
-
const missingText = missingList.length >
|
|
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 >
|
|
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 >
|
|
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 >
|
|
755
|
+
const missingText = missingAttributes.length > MIN_COUNT2 ? `Missing:
|
|
516
756
|
${missingList}` : "";
|
|
517
|
-
const description = missingText.length >
|
|
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 >
|
|
538
|
-
const missingList = missingOperators.length >
|
|
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 >
|
|
780
|
+
const missingText = missingOperators.length > MIN_COUNT2 ? `Missing:
|
|
541
781
|
${missingList}` : "";
|
|
542
|
-
const hasFound = foundText.length >
|
|
543
|
-
const hasMissing = missingText.length >
|
|
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 >
|
|
561
|
-
if (!hasXPath || examples.length ===
|
|
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 >
|
|
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 >
|
|
577
|
-
const hasXpathValue = xpathValue.length >
|
|
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 >
|
|
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 >
|
|
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 >
|
|
625
|
-
const hasXpathValue = xpathValue.length >
|
|
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 >
|
|
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 ===
|
|
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
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
|
986
|
-
|
|
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.
|
|
989
|
-
|
|
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
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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 (
|
|
1000
|
-
const
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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 (
|
|
1376
|
+
if (classBraceDepth <= ZERO_BRACE_DEPTH) {
|
|
1377
|
+
insideClass = false;
|
|
1378
|
+
classBraceDepth = ZERO_BRACE_DEPTH;
|
|
1007
1379
|
insideMethod = false;
|
|
1008
|
-
|
|
1380
|
+
methodDeclaration = null;
|
|
1381
|
+
methodDeclarationOriginal = null;
|
|
1009
1382
|
}
|
|
1010
|
-
} else if (
|
|
1011
|
-
|
|
1383
|
+
} else if (shouldInclude) {
|
|
1384
|
+
const codeLine = removeInlineMarkers(line).trim();
|
|
1385
|
+
extractedCode.push(codeLine);
|
|
1012
1386
|
}
|
|
1013
1387
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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 += `
|
|
1410
|
+
classContent += ` ${line}
|
|
1021
1411
|
`;
|
|
1022
1412
|
});
|
|
1023
|
-
|
|
1413
|
+
const helperMethods = extractHelperMethods(codeToInclude);
|
|
1414
|
+
for (const method of helperMethods) {
|
|
1415
|
+
classContent += ` ${method}
|
|
1024
1416
|
`;
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
classContent
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1982
|
-
|
|
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
|
|
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
|
-
|
|
2024
|
-
console.log(INCOMPLETE_STATUS_MESSAGE);
|
|
2427
|
+
console.log(" Status: \u26A0\uFE0F Incomplete");
|
|
2025
2428
|
}
|
|
2026
|
-
if (result.xpathCoverage.coverage.length >
|
|
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 >
|
|
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 >
|
|
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 >
|
|
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 >
|
|
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
|
|
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
|
|
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
|
|
2163
|
-
if (tracker
|
|
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
|
|
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,
|
|
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(
|