@gulu9527/code-trust 0.3.1 → 0.3.2
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-CN.md +15 -0
- package/README.md +15 -0
- package/action.yml +5 -1
- package/dist/cli/index.js +319 -156
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +313 -155
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -104,6 +104,7 @@ thresholds:
|
|
|
104
104
|
min-score: 70
|
|
105
105
|
max-function-length: 40
|
|
106
106
|
max-cyclomatic-complexity: 10
|
|
107
|
+
max-cognitive-complexity: 20
|
|
107
108
|
max-nesting-depth: 4
|
|
108
109
|
max-params: 5
|
|
109
110
|
|
|
@@ -211,9 +212,18 @@ var DiffParser = class {
|
|
|
211
212
|
}
|
|
212
213
|
parseFileDiff(fileDiff) {
|
|
213
214
|
const lines = fileDiff.split("\n");
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
let filePath = null;
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
if (line.startsWith("+++ b/")) {
|
|
218
|
+
filePath = line.slice(6);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (!filePath) {
|
|
223
|
+
const headerMatch = lines[0]?.match(/a\/(.+?) b\/(.+)/);
|
|
224
|
+
if (!headerMatch) return null;
|
|
225
|
+
filePath = headerMatch[2];
|
|
226
|
+
}
|
|
217
227
|
let status = "modified";
|
|
218
228
|
let additions = 0;
|
|
219
229
|
let deletions = 0;
|
|
@@ -317,6 +327,144 @@ function t(en2, zh2) {
|
|
|
317
327
|
return isZhLocale() ? zh2 : en2;
|
|
318
328
|
}
|
|
319
329
|
|
|
330
|
+
// src/rules/brace-utils.ts
|
|
331
|
+
function countBracesInLine(line, startJ, initialDepth, inBlockComment) {
|
|
332
|
+
let depth = initialDepth;
|
|
333
|
+
let i = startJ;
|
|
334
|
+
while (i < line.length) {
|
|
335
|
+
if (inBlockComment.value) {
|
|
336
|
+
const closeIdx = line.indexOf("*/", i);
|
|
337
|
+
if (closeIdx === -1) return depth;
|
|
338
|
+
inBlockComment.value = false;
|
|
339
|
+
i = closeIdx + 2;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const ch = line[i];
|
|
343
|
+
if (ch === "/" && line[i + 1] === "/") return depth;
|
|
344
|
+
if (ch === "/" && line[i + 1] === "*") {
|
|
345
|
+
inBlockComment.value = true;
|
|
346
|
+
i += 2;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (ch === "'" || ch === '"') {
|
|
350
|
+
i = skipStringLiteral(line, i, ch);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (ch === "`") {
|
|
354
|
+
i = skipTemplateLiteral(line, i);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (ch === "/" && i > 0) {
|
|
358
|
+
const prevNonSpace = findPrevNonSpace(line, i);
|
|
359
|
+
if (prevNonSpace !== -1 && "=(!|&:,;[{?+->~%^".includes(line[prevNonSpace])) {
|
|
360
|
+
i = skipRegexpLiteral(line, i);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (ch === "{") depth++;
|
|
365
|
+
if (ch === "}") depth--;
|
|
366
|
+
i++;
|
|
367
|
+
}
|
|
368
|
+
return depth;
|
|
369
|
+
}
|
|
370
|
+
function skipStringLiteral(line, start, quote) {
|
|
371
|
+
let i = start + 1;
|
|
372
|
+
while (i < line.length) {
|
|
373
|
+
if (line[i] === "\\") {
|
|
374
|
+
i += 2;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (line[i] === quote) return i + 1;
|
|
378
|
+
i++;
|
|
379
|
+
}
|
|
380
|
+
return i;
|
|
381
|
+
}
|
|
382
|
+
function skipTemplateLiteral(line, start) {
|
|
383
|
+
let i = start + 1;
|
|
384
|
+
while (i < line.length) {
|
|
385
|
+
if (line[i] === "\\") {
|
|
386
|
+
i += 2;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (line[i] === "`") return i + 1;
|
|
390
|
+
i++;
|
|
391
|
+
}
|
|
392
|
+
return i;
|
|
393
|
+
}
|
|
394
|
+
function skipRegexpLiteral(line, start) {
|
|
395
|
+
let i = start + 1;
|
|
396
|
+
while (i < line.length) {
|
|
397
|
+
if (line[i] === "\\") {
|
|
398
|
+
i += 2;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (line[i] === "/") return i + 1;
|
|
402
|
+
i++;
|
|
403
|
+
}
|
|
404
|
+
return i;
|
|
405
|
+
}
|
|
406
|
+
function findPrevNonSpace(line, pos) {
|
|
407
|
+
for (let i = pos - 1; i >= 0; i--) {
|
|
408
|
+
if (line[i] !== " " && line[i] !== " ") return i;
|
|
409
|
+
}
|
|
410
|
+
return -1;
|
|
411
|
+
}
|
|
412
|
+
function extractBraceBlock(lines, startLineIndex, startCol) {
|
|
413
|
+
let depth = 0;
|
|
414
|
+
let started = false;
|
|
415
|
+
let bodyStart = -1;
|
|
416
|
+
const blockComment = { value: false };
|
|
417
|
+
for (let i = startLineIndex; i < lines.length; i++) {
|
|
418
|
+
const startJ = i === startLineIndex ? startCol : 0;
|
|
419
|
+
const line = lines[i];
|
|
420
|
+
let j = startJ;
|
|
421
|
+
while (j < line.length) {
|
|
422
|
+
if (blockComment.value) {
|
|
423
|
+
const closeIdx = line.indexOf("*/", j);
|
|
424
|
+
if (closeIdx === -1) {
|
|
425
|
+
j = line.length;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
blockComment.value = false;
|
|
429
|
+
j = closeIdx + 2;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const ch = line[j];
|
|
433
|
+
if (ch === "/" && line[j + 1] === "/") break;
|
|
434
|
+
if (ch === "/" && line[j + 1] === "*") {
|
|
435
|
+
blockComment.value = true;
|
|
436
|
+
j += 2;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (ch === "'" || ch === '"') {
|
|
440
|
+
j = skipStringLiteral(line, j, ch);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (ch === "`") {
|
|
444
|
+
j = skipTemplateLiteral(line, j);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (ch === "{") {
|
|
448
|
+
depth++;
|
|
449
|
+
if (!started) {
|
|
450
|
+
started = true;
|
|
451
|
+
bodyStart = i;
|
|
452
|
+
}
|
|
453
|
+
} else if (ch === "}") {
|
|
454
|
+
depth--;
|
|
455
|
+
if (started && depth === 0) {
|
|
456
|
+
return {
|
|
457
|
+
bodyLines: lines.slice(bodyStart + 1, i),
|
|
458
|
+
endLine: i
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
j++;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
|
|
320
468
|
// src/rules/builtin/unnecessary-try-catch.ts
|
|
321
469
|
var unnecessaryTryCatchRule = {
|
|
322
470
|
id: "logic/unnecessary-try-catch",
|
|
@@ -332,38 +480,53 @@ var unnecessaryTryCatchRule = {
|
|
|
332
480
|
const line = lines[i];
|
|
333
481
|
const trimmed = line.trim();
|
|
334
482
|
if (trimmed.startsWith("try") && trimmed.includes("{")) {
|
|
335
|
-
const
|
|
483
|
+
const tryCol = line.indexOf("try");
|
|
484
|
+
const tryBlock = extractBraceBlock(lines, i, tryCol);
|
|
336
485
|
if (tryBlock) {
|
|
337
|
-
const { bodyLines
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
486
|
+
const { bodyLines: tryBodyLines, endLine: tryEndLine } = tryBlock;
|
|
487
|
+
let catchLineIdx = -1;
|
|
488
|
+
for (let k = tryEndLine; k < Math.min(tryEndLine + 2, lines.length); k++) {
|
|
489
|
+
if (lines[k].includes("catch")) {
|
|
490
|
+
catchLineIdx = k;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (catchLineIdx !== -1) {
|
|
495
|
+
const catchCol = lines[catchLineIdx].indexOf("catch");
|
|
496
|
+
const catchBlock = extractBraceBlock(lines, catchLineIdx, catchCol);
|
|
497
|
+
if (catchBlock) {
|
|
498
|
+
const { bodyLines: catchBodyLines, endLine: catchEndLine } = catchBlock;
|
|
499
|
+
const nonEmptyBody = tryBodyLines.filter((l) => l.trim().length > 0);
|
|
500
|
+
const nonEmptyCatch = catchBodyLines.filter((l) => l.trim().length > 0);
|
|
501
|
+
const isSimpleBody = nonEmptyBody.length <= 2;
|
|
502
|
+
const isGenericCatch = nonEmptyCatch.length <= 2 && nonEmptyCatch.some(
|
|
503
|
+
(l) => /console\.(log|error|warn)/.test(l) || /throw\s+(new\s+)?Error/.test(l) || l.trim() === ""
|
|
504
|
+
);
|
|
505
|
+
const bodyHasOnlyAssignments = nonEmptyBody.every(
|
|
506
|
+
(l) => /^\s*(const|let|var)\s+/.test(l) || /^\s*\w+(\.\w+)*\s*=\s*/.test(l) || /^\s*return\s+/.test(l)
|
|
507
|
+
);
|
|
508
|
+
if (isSimpleBody && isGenericCatch && bodyHasOnlyAssignments) {
|
|
509
|
+
issues.push({
|
|
510
|
+
ruleId: "logic/unnecessary-try-catch",
|
|
511
|
+
severity: "medium",
|
|
512
|
+
category: "logic",
|
|
513
|
+
file: context.filePath,
|
|
514
|
+
startLine: i + 1,
|
|
515
|
+
endLine: catchEndLine + 1,
|
|
516
|
+
message: t(
|
|
517
|
+
"Unnecessary try-catch wrapping a simple statement with generic error handling. This is likely AI-hallucinated error handling.",
|
|
518
|
+
"\u4E0D\u5FC5\u8981\u7684 try-catch \u5305\u88F9\u4E86\u7B80\u5355\u8BED\u53E5\uFF0Ccatch \u4E2D\u53EA\u6709\u901A\u7528\u7684\u9519\u8BEF\u65E5\u5FD7\u3002\u8FD9\u5F88\u53EF\u80FD\u662F AI \u5E7B\u89C9\u751F\u6210\u7684\u9519\u8BEF\u5904\u7406\u3002"
|
|
519
|
+
),
|
|
520
|
+
suggestion: t(
|
|
521
|
+
"Remove the try-catch block or add meaningful error recovery logic.",
|
|
522
|
+
"\u79FB\u9664 try-catch \u5757\uFF0C\u6216\u6DFB\u52A0\u6709\u610F\u4E49\u7684\u9519\u8BEF\u6062\u590D\u903B\u8F91\u3002"
|
|
523
|
+
)
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
i = catchEndLine + 1;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
364
529
|
}
|
|
365
|
-
i = endLine + 1;
|
|
366
|
-
continue;
|
|
367
530
|
}
|
|
368
531
|
}
|
|
369
532
|
i++;
|
|
@@ -371,55 +534,6 @@ var unnecessaryTryCatchRule = {
|
|
|
371
534
|
return issues;
|
|
372
535
|
}
|
|
373
536
|
};
|
|
374
|
-
function extractBlock(lines, tryLineIndex) {
|
|
375
|
-
let braceCount = 0;
|
|
376
|
-
let foundTryOpen = false;
|
|
377
|
-
let tryBodyStart = -1;
|
|
378
|
-
let tryBodyEnd = -1;
|
|
379
|
-
let catchStart = -1;
|
|
380
|
-
let catchBodyStart = -1;
|
|
381
|
-
let catchBodyEnd = -1;
|
|
382
|
-
for (let i = tryLineIndex; i < lines.length; i++) {
|
|
383
|
-
const line = lines[i];
|
|
384
|
-
for (const ch of line) {
|
|
385
|
-
if (ch === "{") {
|
|
386
|
-
braceCount++;
|
|
387
|
-
if (!foundTryOpen) {
|
|
388
|
-
foundTryOpen = true;
|
|
389
|
-
tryBodyStart = i;
|
|
390
|
-
}
|
|
391
|
-
} else if (ch === "}") {
|
|
392
|
-
braceCount--;
|
|
393
|
-
if (braceCount === 0 && tryBodyEnd === -1) {
|
|
394
|
-
tryBodyEnd = i;
|
|
395
|
-
} else if (braceCount === 0 && catchBodyEnd === -1 && catchBodyStart !== -1) {
|
|
396
|
-
catchBodyEnd = i;
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
if (tryBodyEnd !== -1 && catchStart === -1) {
|
|
402
|
-
if (line.includes("catch")) {
|
|
403
|
-
catchStart = i;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (catchStart !== -1 && catchBodyStart === -1 && line.includes("{")) {
|
|
407
|
-
catchBodyStart = i;
|
|
408
|
-
}
|
|
409
|
-
if (catchBodyEnd !== -1) break;
|
|
410
|
-
}
|
|
411
|
-
if (tryBodyStart === -1 || tryBodyEnd === -1 || catchBodyStart === -1 || catchBodyEnd === -1) {
|
|
412
|
-
return null;
|
|
413
|
-
}
|
|
414
|
-
const bodyLines = lines.slice(tryBodyStart + 1, tryBodyEnd);
|
|
415
|
-
const catchBodyLines = lines.slice(catchBodyStart + 1, catchBodyEnd);
|
|
416
|
-
return {
|
|
417
|
-
bodyLines,
|
|
418
|
-
catchStart,
|
|
419
|
-
catchBodyLines,
|
|
420
|
-
endLine: catchBodyEnd
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
537
|
|
|
424
538
|
// src/rules/builtin/over-defensive.ts
|
|
425
539
|
var overDefensiveRule = {
|
|
@@ -608,18 +722,10 @@ function detectCodeAfterReturn(context, lines, issues) {
|
|
|
608
722
|
let braceDepth = 0;
|
|
609
723
|
let lastReturnDepth = -1;
|
|
610
724
|
let lastReturnLine = -1;
|
|
725
|
+
const blockComment = { value: false };
|
|
611
726
|
for (let i = 0; i < lines.length; i++) {
|
|
612
727
|
const trimmed = lines[i].trim();
|
|
613
|
-
|
|
614
|
-
if (ch === "{") braceDepth++;
|
|
615
|
-
if (ch === "}") {
|
|
616
|
-
if (braceDepth === lastReturnDepth) {
|
|
617
|
-
lastReturnDepth = -1;
|
|
618
|
-
lastReturnLine = -1;
|
|
619
|
-
}
|
|
620
|
-
braceDepth--;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
728
|
+
braceDepth = countBracesInLine(lines[i], 0, braceDepth, blockComment);
|
|
623
729
|
if (/^(return|throw)\b/.test(trimmed) && !trimmed.includes("=>")) {
|
|
624
730
|
const endsOpen = /[[{(,]$/.test(trimmed) || /^(return|throw)\s*$/.test(trimmed);
|
|
625
731
|
if (endsOpen) continue;
|
|
@@ -645,6 +751,10 @@ function detectCodeAfterReturn(context, lines, issues) {
|
|
|
645
751
|
lastReturnDepth = -1;
|
|
646
752
|
lastReturnLine = -1;
|
|
647
753
|
}
|
|
754
|
+
if (braceDepth < lastReturnDepth) {
|
|
755
|
+
lastReturnDepth = -1;
|
|
756
|
+
lastReturnLine = -1;
|
|
757
|
+
}
|
|
648
758
|
}
|
|
649
759
|
}
|
|
650
760
|
function detectImmediateReassign(context, lines, issues) {
|
|
@@ -678,7 +788,21 @@ function detectImmediateReassign(context, lines, issues) {
|
|
|
678
788
|
}
|
|
679
789
|
|
|
680
790
|
// src/rules/fix-utils.ts
|
|
681
|
-
function
|
|
791
|
+
function buildLineOffsets(content) {
|
|
792
|
+
const offsets = [0];
|
|
793
|
+
for (let i = 0; i < content.length; i++) {
|
|
794
|
+
if (content[i] === "\n") {
|
|
795
|
+
offsets.push(i + 1);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return offsets;
|
|
799
|
+
}
|
|
800
|
+
function lineStartOffset(content, lineNumber, offsets) {
|
|
801
|
+
if (offsets) {
|
|
802
|
+
const idx = lineNumber - 1;
|
|
803
|
+
if (idx < 0 || idx >= offsets.length) return content.length;
|
|
804
|
+
return offsets[idx];
|
|
805
|
+
}
|
|
682
806
|
let offset = 0;
|
|
683
807
|
const lines = content.split("\n");
|
|
684
808
|
for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
|
|
@@ -686,12 +810,13 @@ function lineStartOffset(content, lineNumber) {
|
|
|
686
810
|
}
|
|
687
811
|
return offset;
|
|
688
812
|
}
|
|
689
|
-
function lineRange(content, lineNumber) {
|
|
690
|
-
const
|
|
813
|
+
function lineRange(content, lineNumber, offsets) {
|
|
814
|
+
const table = offsets ?? buildLineOffsets(content);
|
|
691
815
|
const lineIndex = lineNumber - 1;
|
|
692
|
-
if (lineIndex < 0 || lineIndex >=
|
|
693
|
-
const start =
|
|
694
|
-
const
|
|
816
|
+
if (lineIndex < 0 || lineIndex >= table.length) return [0, 0];
|
|
817
|
+
const start = table[lineIndex];
|
|
818
|
+
const nextLineStart = lineIndex + 1 < table.length ? table[lineIndex + 1] : content.length;
|
|
819
|
+
const end = lineIndex + 1 < table.length ? nextLineStart : nextLineStart;
|
|
695
820
|
return [start, end];
|
|
696
821
|
}
|
|
697
822
|
|
|
@@ -757,12 +882,12 @@ function extractFunctions(parsed) {
|
|
|
757
882
|
}
|
|
758
883
|
function visitNode(root, functions) {
|
|
759
884
|
const methodBodies = /* @__PURE__ */ new WeakSet();
|
|
760
|
-
walkAST(root, (node) => {
|
|
885
|
+
walkAST(root, (node, parent) => {
|
|
761
886
|
if (node.type === AST_NODE_TYPES.FunctionExpression && methodBodies.has(node)) {
|
|
762
887
|
return false;
|
|
763
888
|
}
|
|
764
889
|
if (node.type === AST_NODE_TYPES.FunctionDeclaration || node.type === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.MethodDefinition) {
|
|
765
|
-
const info = analyzeFunctionNode(node);
|
|
890
|
+
const info = analyzeFunctionNode(node, parent);
|
|
766
891
|
if (info) functions.push(info);
|
|
767
892
|
if (node.type === AST_NODE_TYPES.MethodDefinition) {
|
|
768
893
|
methodBodies.add(node.value);
|
|
@@ -771,7 +896,7 @@ function visitNode(root, functions) {
|
|
|
771
896
|
return;
|
|
772
897
|
});
|
|
773
898
|
}
|
|
774
|
-
function analyzeFunctionNode(node) {
|
|
899
|
+
function analyzeFunctionNode(node, parent) {
|
|
775
900
|
let name = "<anonymous>";
|
|
776
901
|
let params = [];
|
|
777
902
|
let body = null;
|
|
@@ -784,7 +909,11 @@ function analyzeFunctionNode(node) {
|
|
|
784
909
|
params = node.params;
|
|
785
910
|
body = node.body;
|
|
786
911
|
} else if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
787
|
-
|
|
912
|
+
if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier) {
|
|
913
|
+
name = parent.id.name;
|
|
914
|
+
} else {
|
|
915
|
+
name = "<arrow>";
|
|
916
|
+
}
|
|
788
917
|
params = node.params;
|
|
789
918
|
body = node.body;
|
|
790
919
|
} else if (node.type === AST_NODE_TYPES.MethodDefinition) {
|
|
@@ -974,7 +1103,24 @@ function collectDeclarationsAndReferences(root, declarations, references) {
|
|
|
974
1103
|
kind: exportedNames.has(node.id.name) ? "export" : "local"
|
|
975
1104
|
});
|
|
976
1105
|
}
|
|
977
|
-
if (node.type === AST_NODE_TYPES.
|
|
1106
|
+
if (node.type === AST_NODE_TYPES.ClassDeclaration && node.id) {
|
|
1107
|
+
declarations.set(node.id.name, {
|
|
1108
|
+
line: node.loc?.start.line ?? 0,
|
|
1109
|
+
kind: exportedNames.has(node.id.name) ? "export" : "local"
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
const declarationParentTypes = /* @__PURE__ */ new Set([
|
|
1113
|
+
"VariableDeclarator",
|
|
1114
|
+
"FunctionDeclaration",
|
|
1115
|
+
"ClassDeclaration",
|
|
1116
|
+
"MethodDefinition",
|
|
1117
|
+
"TSEnumDeclaration",
|
|
1118
|
+
"TSEnumMember",
|
|
1119
|
+
"TSTypeAliasDeclaration",
|
|
1120
|
+
"TSInterfaceDeclaration",
|
|
1121
|
+
"TSModuleDeclaration"
|
|
1122
|
+
]);
|
|
1123
|
+
if (node.type === AST_NODE_TYPES.Identifier && !declarationParentTypes.has(parentType ?? "")) {
|
|
978
1124
|
references.add(node.name);
|
|
979
1125
|
}
|
|
980
1126
|
return;
|
|
@@ -1061,8 +1207,25 @@ function stringifyCondition(node) {
|
|
|
1061
1207
|
}
|
|
1062
1208
|
case AST_NODE_TYPES.ConditionalExpression:
|
|
1063
1209
|
return `${stringifyCondition(node.test)} ? ${stringifyCondition(node.consequent)} : ${stringifyCondition(node.alternate)}`;
|
|
1210
|
+
case AST_NODE_TYPES.TemplateLiteral:
|
|
1211
|
+
return `\`template@${node.loc?.start.line}:${node.loc?.start.column}\``;
|
|
1212
|
+
case AST_NODE_TYPES.ArrayExpression:
|
|
1213
|
+
return `[${node.elements.map((e) => e ? stringifyCondition(e) : "empty").join(", ")}]`;
|
|
1214
|
+
case AST_NODE_TYPES.ObjectExpression:
|
|
1215
|
+
return `{obj@${node.loc?.start.line}:${node.loc?.start.column}}`;
|
|
1216
|
+
case AST_NODE_TYPES.AssignmentExpression:
|
|
1217
|
+
return `${stringifyCondition(node.left)} ${node.operator} ${stringifyCondition(node.right)}`;
|
|
1218
|
+
case AST_NODE_TYPES.NewExpression:
|
|
1219
|
+
return `new ${stringifyCondition(node.callee)}(${node.arguments.map((a) => stringifyCondition(a)).join(", ")})`;
|
|
1220
|
+
case AST_NODE_TYPES.TSAsExpression:
|
|
1221
|
+
case AST_NODE_TYPES.TSNonNullExpression:
|
|
1222
|
+
return stringifyCondition(node.expression);
|
|
1223
|
+
case AST_NODE_TYPES.AwaitExpression:
|
|
1224
|
+
return `await ${stringifyCondition(node.argument)}`;
|
|
1225
|
+
case AST_NODE_TYPES.ChainExpression:
|
|
1226
|
+
return stringifyCondition(node.expression);
|
|
1064
1227
|
default:
|
|
1065
|
-
return `[${node.type}]`;
|
|
1228
|
+
return `[${node.type}@${node.loc?.start.line}:${node.loc?.start.column}]`;
|
|
1066
1229
|
}
|
|
1067
1230
|
}
|
|
1068
1231
|
function truncate(s, maxLen) {
|
|
@@ -1274,7 +1437,8 @@ var emptyCatchRule = {
|
|
|
1274
1437
|
const catchMatch = trimmed.match(/\bcatch\s*\(\s*(\w+)?\s*\)\s*\{/);
|
|
1275
1438
|
if (!catchMatch) continue;
|
|
1276
1439
|
const catchVarName = catchMatch[1] || "";
|
|
1277
|
-
const
|
|
1440
|
+
const catchIdx = lines[i].indexOf("catch");
|
|
1441
|
+
const blockContent = extractBraceBlock(lines, i, catchIdx);
|
|
1278
1442
|
if (!blockContent) continue;
|
|
1279
1443
|
const { bodyLines, endLine } = blockContent;
|
|
1280
1444
|
const meaningful = bodyLines.filter(
|
|
@@ -1324,37 +1488,6 @@ var emptyCatchRule = {
|
|
|
1324
1488
|
return issues;
|
|
1325
1489
|
}
|
|
1326
1490
|
};
|
|
1327
|
-
function extractCatchBody(lines, catchLineIndex) {
|
|
1328
|
-
const catchLine = lines[catchLineIndex];
|
|
1329
|
-
const catchIdx = catchLine.indexOf("catch");
|
|
1330
|
-
if (catchIdx === -1) return null;
|
|
1331
|
-
let braceCount = 0;
|
|
1332
|
-
let started = false;
|
|
1333
|
-
let bodyStart = -1;
|
|
1334
|
-
for (let i = catchLineIndex; i < lines.length; i++) {
|
|
1335
|
-
const line = lines[i];
|
|
1336
|
-
const startJ = i === catchLineIndex ? catchIdx : 0;
|
|
1337
|
-
for (let j = startJ; j < line.length; j++) {
|
|
1338
|
-
const ch = line[j];
|
|
1339
|
-
if (ch === "{") {
|
|
1340
|
-
braceCount++;
|
|
1341
|
-
if (!started) {
|
|
1342
|
-
started = true;
|
|
1343
|
-
bodyStart = i;
|
|
1344
|
-
}
|
|
1345
|
-
} else if (ch === "}") {
|
|
1346
|
-
braceCount--;
|
|
1347
|
-
if (started && braceCount === 0) {
|
|
1348
|
-
return {
|
|
1349
|
-
bodyLines: lines.slice(bodyStart + 1, i),
|
|
1350
|
-
endLine: i
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
return null;
|
|
1357
|
-
}
|
|
1358
1491
|
|
|
1359
1492
|
// src/rules/builtin/identical-branches.ts
|
|
1360
1493
|
var identicalBranchesRule = {
|
|
@@ -1701,10 +1834,25 @@ var unusedImportRule = {
|
|
|
1701
1834
|
}
|
|
1702
1835
|
return;
|
|
1703
1836
|
});
|
|
1704
|
-
const
|
|
1705
|
-
let
|
|
1706
|
-
|
|
1707
|
-
|
|
1837
|
+
const codeLines = context.fileContent.split("\n");
|
|
1838
|
+
let inBlock = false;
|
|
1839
|
+
for (const codeLine of codeLines) {
|
|
1840
|
+
const trimmedCode = codeLine.trim();
|
|
1841
|
+
if (inBlock) {
|
|
1842
|
+
if (trimmedCode.includes("*/")) inBlock = false;
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
if (trimmedCode.startsWith("/*")) {
|
|
1846
|
+
if (!trimmedCode.includes("*/")) inBlock = true;
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
if (trimmedCode.startsWith("//") || trimmedCode.startsWith("*")) continue;
|
|
1850
|
+
const cleaned = codeLine.replace(/\/\/.*$/, "").replace(/\/\*.*?\*\//g, "").replace(/'(?:[^'\\]|\\.)*'/g, "").replace(/"(?:[^"\\]|\\.)*"/g, "").replace(/`(?:[^`\\]|\\.)*`/g, "");
|
|
1851
|
+
const typeRefPattern = /\b([A-Z][A-Za-z0-9]*)\b/g;
|
|
1852
|
+
let match;
|
|
1853
|
+
while ((match = typeRefPattern.exec(cleaned)) !== null) {
|
|
1854
|
+
references.add(match[1]);
|
|
1855
|
+
}
|
|
1708
1856
|
}
|
|
1709
1857
|
for (const imp of imports) {
|
|
1710
1858
|
if (!references.has(imp.local)) {
|
|
@@ -2830,13 +2978,20 @@ var SEVERITY_PENALTY = {
|
|
|
2830
2978
|
low: 3,
|
|
2831
2979
|
info: 0
|
|
2832
2980
|
};
|
|
2981
|
+
var DIMINISHING_FACTOR = 0.7;
|
|
2833
2982
|
function calculateDimensionScore(issues) {
|
|
2834
2983
|
let score = 100;
|
|
2984
|
+
const severityCounts = {};
|
|
2835
2985
|
for (const issue of issues) {
|
|
2836
|
-
|
|
2986
|
+
const base = SEVERITY_PENALTY[issue.severity] ?? 0;
|
|
2987
|
+
if (base === 0) continue;
|
|
2988
|
+
const n = severityCounts[issue.severity] ?? 0;
|
|
2989
|
+
severityCounts[issue.severity] = n + 1;
|
|
2990
|
+
const penalty = base * Math.pow(DIMINISHING_FACTOR, n);
|
|
2991
|
+
score -= penalty;
|
|
2837
2992
|
}
|
|
2838
2993
|
return {
|
|
2839
|
-
score: Math.max(0, Math.min(100, score)),
|
|
2994
|
+
score: Math.round(Math.max(0, Math.min(100, score)) * 10) / 10,
|
|
2840
2995
|
issues
|
|
2841
2996
|
};
|
|
2842
2997
|
}
|
|
@@ -3189,7 +3344,7 @@ var PKG_VERSION = (() => {
|
|
|
3189
3344
|
}
|
|
3190
3345
|
})();
|
|
3191
3346
|
var REPORT_SCHEMA_VERSION = "1.0.0";
|
|
3192
|
-
var FINGERPRINT_VERSION = "
|
|
3347
|
+
var FINGERPRINT_VERSION = "2";
|
|
3193
3348
|
var ScanEngine = class {
|
|
3194
3349
|
config;
|
|
3195
3350
|
diffParser;
|
|
@@ -3441,13 +3596,14 @@ var ScanEngine = class {
|
|
|
3441
3596
|
const occurrenceCounts = /* @__PURE__ */ new Map();
|
|
3442
3597
|
return issues.map((issue) => {
|
|
3443
3598
|
const normalizedFile = this.normalizeRelativePath(issue.file);
|
|
3444
|
-
const
|
|
3599
|
+
const contentSource = issue.codeSnippet ? issue.codeSnippet : `${issue.startLine}:${issue.endLine}`;
|
|
3600
|
+
const contentDigest = createHash("sha256").update(contentSource).digest("hex").slice(0, 16);
|
|
3445
3601
|
const baseKey = [
|
|
3446
3602
|
issue.ruleId,
|
|
3447
3603
|
normalizedFile,
|
|
3448
3604
|
issue.category,
|
|
3449
3605
|
issue.severity,
|
|
3450
|
-
|
|
3606
|
+
contentDigest
|
|
3451
3607
|
].join("|");
|
|
3452
3608
|
const occurrenceIndex = occurrenceCounts.get(baseKey) ?? 0;
|
|
3453
3609
|
occurrenceCounts.set(baseKey, occurrenceIndex + 1);
|
|
@@ -3556,17 +3712,19 @@ var ScanEngine = class {
|
|
|
3556
3712
|
return ["security", "logic", "structure", "style", "coverage"].includes(value);
|
|
3557
3713
|
}
|
|
3558
3714
|
groupByDimension(issues) {
|
|
3559
|
-
const
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3715
|
+
const buckets = {
|
|
3716
|
+
security: [],
|
|
3717
|
+
logic: [],
|
|
3718
|
+
structure: [],
|
|
3719
|
+
style: [],
|
|
3720
|
+
coverage: []
|
|
3721
|
+
};
|
|
3722
|
+
for (const issue of issues) {
|
|
3723
|
+
buckets[issue.category]?.push(issue);
|
|
3724
|
+
}
|
|
3566
3725
|
const grouped = {};
|
|
3567
|
-
for (const cat of
|
|
3568
|
-
|
|
3569
|
-
grouped[cat] = calculateDimensionScore(catIssues);
|
|
3726
|
+
for (const cat of Object.keys(buckets)) {
|
|
3727
|
+
grouped[cat] = calculateDimensionScore(buckets[cat]);
|
|
3570
3728
|
}
|
|
3571
3729
|
return grouped;
|
|
3572
3730
|
}
|
|
@@ -3767,12 +3925,17 @@ function createScanCommand() {
|
|
|
3767
3925
|
try {
|
|
3768
3926
|
const config = await loadConfig();
|
|
3769
3927
|
const engine = new ScanEngine(config);
|
|
3928
|
+
const parsedMinScore = parseInt(opts.minScore, 10);
|
|
3929
|
+
if (isNaN(parsedMinScore)) {
|
|
3930
|
+
console.error(`Error: Invalid --min-score value "${opts.minScore}". Must be a number.`);
|
|
3931
|
+
process.exit(1);
|
|
3932
|
+
}
|
|
3770
3933
|
const scanOptions = {
|
|
3771
3934
|
staged: opts.staged,
|
|
3772
3935
|
diff: opts.diff,
|
|
3773
3936
|
files: files.length > 0 ? files : void 0,
|
|
3774
3937
|
format: opts.format,
|
|
3775
|
-
minScore:
|
|
3938
|
+
minScore: parsedMinScore,
|
|
3776
3939
|
baseline: opts.baseline
|
|
3777
3940
|
};
|
|
3778
3941
|
const report = await engine.scan(scanOptions);
|