@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/index.js
CHANGED
|
@@ -90,9 +90,18 @@ var DiffParser = class {
|
|
|
90
90
|
}
|
|
91
91
|
parseFileDiff(fileDiff) {
|
|
92
92
|
const lines = fileDiff.split("\n");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
let filePath = null;
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (line.startsWith("+++ b/")) {
|
|
96
|
+
filePath = line.slice(6);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!filePath) {
|
|
101
|
+
const headerMatch = lines[0]?.match(/a\/(.+?) b\/(.+)/);
|
|
102
|
+
if (!headerMatch) return null;
|
|
103
|
+
filePath = headerMatch[2];
|
|
104
|
+
}
|
|
96
105
|
let status = "modified";
|
|
97
106
|
let additions = 0;
|
|
98
107
|
let deletions = 0;
|
|
@@ -196,6 +205,144 @@ function t(en, zh) {
|
|
|
196
205
|
return isZhLocale() ? zh : en;
|
|
197
206
|
}
|
|
198
207
|
|
|
208
|
+
// src/rules/brace-utils.ts
|
|
209
|
+
function countBracesInLine(line, startJ, initialDepth, inBlockComment) {
|
|
210
|
+
let depth = initialDepth;
|
|
211
|
+
let i = startJ;
|
|
212
|
+
while (i < line.length) {
|
|
213
|
+
if (inBlockComment.value) {
|
|
214
|
+
const closeIdx = line.indexOf("*/", i);
|
|
215
|
+
if (closeIdx === -1) return depth;
|
|
216
|
+
inBlockComment.value = false;
|
|
217
|
+
i = closeIdx + 2;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const ch = line[i];
|
|
221
|
+
if (ch === "/" && line[i + 1] === "/") return depth;
|
|
222
|
+
if (ch === "/" && line[i + 1] === "*") {
|
|
223
|
+
inBlockComment.value = true;
|
|
224
|
+
i += 2;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (ch === "'" || ch === '"') {
|
|
228
|
+
i = skipStringLiteral(line, i, ch);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (ch === "`") {
|
|
232
|
+
i = skipTemplateLiteral(line, i);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (ch === "/" && i > 0) {
|
|
236
|
+
const prevNonSpace = findPrevNonSpace(line, i);
|
|
237
|
+
if (prevNonSpace !== -1 && "=(!|&:,;[{?+->~%^".includes(line[prevNonSpace])) {
|
|
238
|
+
i = skipRegexpLiteral(line, i);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (ch === "{") depth++;
|
|
243
|
+
if (ch === "}") depth--;
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
return depth;
|
|
247
|
+
}
|
|
248
|
+
function skipStringLiteral(line, start, quote) {
|
|
249
|
+
let i = start + 1;
|
|
250
|
+
while (i < line.length) {
|
|
251
|
+
if (line[i] === "\\") {
|
|
252
|
+
i += 2;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (line[i] === quote) return i + 1;
|
|
256
|
+
i++;
|
|
257
|
+
}
|
|
258
|
+
return i;
|
|
259
|
+
}
|
|
260
|
+
function skipTemplateLiteral(line, start) {
|
|
261
|
+
let i = start + 1;
|
|
262
|
+
while (i < line.length) {
|
|
263
|
+
if (line[i] === "\\") {
|
|
264
|
+
i += 2;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (line[i] === "`") return i + 1;
|
|
268
|
+
i++;
|
|
269
|
+
}
|
|
270
|
+
return i;
|
|
271
|
+
}
|
|
272
|
+
function skipRegexpLiteral(line, start) {
|
|
273
|
+
let i = start + 1;
|
|
274
|
+
while (i < line.length) {
|
|
275
|
+
if (line[i] === "\\") {
|
|
276
|
+
i += 2;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (line[i] === "/") return i + 1;
|
|
280
|
+
i++;
|
|
281
|
+
}
|
|
282
|
+
return i;
|
|
283
|
+
}
|
|
284
|
+
function findPrevNonSpace(line, pos) {
|
|
285
|
+
for (let i = pos - 1; i >= 0; i--) {
|
|
286
|
+
if (line[i] !== " " && line[i] !== " ") return i;
|
|
287
|
+
}
|
|
288
|
+
return -1;
|
|
289
|
+
}
|
|
290
|
+
function extractBraceBlock(lines, startLineIndex, startCol) {
|
|
291
|
+
let depth = 0;
|
|
292
|
+
let started = false;
|
|
293
|
+
let bodyStart = -1;
|
|
294
|
+
const blockComment = { value: false };
|
|
295
|
+
for (let i = startLineIndex; i < lines.length; i++) {
|
|
296
|
+
const startJ = i === startLineIndex ? startCol : 0;
|
|
297
|
+
const line = lines[i];
|
|
298
|
+
let j = startJ;
|
|
299
|
+
while (j < line.length) {
|
|
300
|
+
if (blockComment.value) {
|
|
301
|
+
const closeIdx = line.indexOf("*/", j);
|
|
302
|
+
if (closeIdx === -1) {
|
|
303
|
+
j = line.length;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
blockComment.value = false;
|
|
307
|
+
j = closeIdx + 2;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const ch = line[j];
|
|
311
|
+
if (ch === "/" && line[j + 1] === "/") break;
|
|
312
|
+
if (ch === "/" && line[j + 1] === "*") {
|
|
313
|
+
blockComment.value = true;
|
|
314
|
+
j += 2;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (ch === "'" || ch === '"') {
|
|
318
|
+
j = skipStringLiteral(line, j, ch);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (ch === "`") {
|
|
322
|
+
j = skipTemplateLiteral(line, j);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (ch === "{") {
|
|
326
|
+
depth++;
|
|
327
|
+
if (!started) {
|
|
328
|
+
started = true;
|
|
329
|
+
bodyStart = i;
|
|
330
|
+
}
|
|
331
|
+
} else if (ch === "}") {
|
|
332
|
+
depth--;
|
|
333
|
+
if (started && depth === 0) {
|
|
334
|
+
return {
|
|
335
|
+
bodyLines: lines.slice(bodyStart + 1, i),
|
|
336
|
+
endLine: i
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
j++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
199
346
|
// src/rules/builtin/unnecessary-try-catch.ts
|
|
200
347
|
var unnecessaryTryCatchRule = {
|
|
201
348
|
id: "logic/unnecessary-try-catch",
|
|
@@ -211,38 +358,53 @@ var unnecessaryTryCatchRule = {
|
|
|
211
358
|
const line = lines[i];
|
|
212
359
|
const trimmed = line.trim();
|
|
213
360
|
if (trimmed.startsWith("try") && trimmed.includes("{")) {
|
|
214
|
-
const
|
|
361
|
+
const tryCol = line.indexOf("try");
|
|
362
|
+
const tryBlock = extractBraceBlock(lines, i, tryCol);
|
|
215
363
|
if (tryBlock) {
|
|
216
|
-
const { bodyLines
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
364
|
+
const { bodyLines: tryBodyLines, endLine: tryEndLine } = tryBlock;
|
|
365
|
+
let catchLineIdx = -1;
|
|
366
|
+
for (let k = tryEndLine; k < Math.min(tryEndLine + 2, lines.length); k++) {
|
|
367
|
+
if (lines[k].includes("catch")) {
|
|
368
|
+
catchLineIdx = k;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (catchLineIdx !== -1) {
|
|
373
|
+
const catchCol = lines[catchLineIdx].indexOf("catch");
|
|
374
|
+
const catchBlock = extractBraceBlock(lines, catchLineIdx, catchCol);
|
|
375
|
+
if (catchBlock) {
|
|
376
|
+
const { bodyLines: catchBodyLines, endLine: catchEndLine } = catchBlock;
|
|
377
|
+
const nonEmptyBody = tryBodyLines.filter((l) => l.trim().length > 0);
|
|
378
|
+
const nonEmptyCatch = catchBodyLines.filter((l) => l.trim().length > 0);
|
|
379
|
+
const isSimpleBody = nonEmptyBody.length <= 2;
|
|
380
|
+
const isGenericCatch = nonEmptyCatch.length <= 2 && nonEmptyCatch.some(
|
|
381
|
+
(l) => /console\.(log|error|warn)/.test(l) || /throw\s+(new\s+)?Error/.test(l) || l.trim() === ""
|
|
382
|
+
);
|
|
383
|
+
const bodyHasOnlyAssignments = nonEmptyBody.every(
|
|
384
|
+
(l) => /^\s*(const|let|var)\s+/.test(l) || /^\s*\w+(\.\w+)*\s*=\s*/.test(l) || /^\s*return\s+/.test(l)
|
|
385
|
+
);
|
|
386
|
+
if (isSimpleBody && isGenericCatch && bodyHasOnlyAssignments) {
|
|
387
|
+
issues.push({
|
|
388
|
+
ruleId: "logic/unnecessary-try-catch",
|
|
389
|
+
severity: "medium",
|
|
390
|
+
category: "logic",
|
|
391
|
+
file: context.filePath,
|
|
392
|
+
startLine: i + 1,
|
|
393
|
+
endLine: catchEndLine + 1,
|
|
394
|
+
message: t(
|
|
395
|
+
"Unnecessary try-catch wrapping a simple statement with generic error handling. This is likely AI-hallucinated error handling.",
|
|
396
|
+
"\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"
|
|
397
|
+
),
|
|
398
|
+
suggestion: t(
|
|
399
|
+
"Remove the try-catch block or add meaningful error recovery logic.",
|
|
400
|
+
"\u79FB\u9664 try-catch \u5757\uFF0C\u6216\u6DFB\u52A0\u6709\u610F\u4E49\u7684\u9519\u8BEF\u6062\u590D\u903B\u8F91\u3002"
|
|
401
|
+
)
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
i = catchEndLine + 1;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
243
407
|
}
|
|
244
|
-
i = endLine + 1;
|
|
245
|
-
continue;
|
|
246
408
|
}
|
|
247
409
|
}
|
|
248
410
|
i++;
|
|
@@ -250,55 +412,6 @@ var unnecessaryTryCatchRule = {
|
|
|
250
412
|
return issues;
|
|
251
413
|
}
|
|
252
414
|
};
|
|
253
|
-
function extractBlock(lines, tryLineIndex) {
|
|
254
|
-
let braceCount = 0;
|
|
255
|
-
let foundTryOpen = false;
|
|
256
|
-
let tryBodyStart = -1;
|
|
257
|
-
let tryBodyEnd = -1;
|
|
258
|
-
let catchStart = -1;
|
|
259
|
-
let catchBodyStart = -1;
|
|
260
|
-
let catchBodyEnd = -1;
|
|
261
|
-
for (let i = tryLineIndex; i < lines.length; i++) {
|
|
262
|
-
const line = lines[i];
|
|
263
|
-
for (const ch of line) {
|
|
264
|
-
if (ch === "{") {
|
|
265
|
-
braceCount++;
|
|
266
|
-
if (!foundTryOpen) {
|
|
267
|
-
foundTryOpen = true;
|
|
268
|
-
tryBodyStart = i;
|
|
269
|
-
}
|
|
270
|
-
} else if (ch === "}") {
|
|
271
|
-
braceCount--;
|
|
272
|
-
if (braceCount === 0 && tryBodyEnd === -1) {
|
|
273
|
-
tryBodyEnd = i;
|
|
274
|
-
} else if (braceCount === 0 && catchBodyEnd === -1 && catchBodyStart !== -1) {
|
|
275
|
-
catchBodyEnd = i;
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (tryBodyEnd !== -1 && catchStart === -1) {
|
|
281
|
-
if (line.includes("catch")) {
|
|
282
|
-
catchStart = i;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (catchStart !== -1 && catchBodyStart === -1 && line.includes("{")) {
|
|
286
|
-
catchBodyStart = i;
|
|
287
|
-
}
|
|
288
|
-
if (catchBodyEnd !== -1) break;
|
|
289
|
-
}
|
|
290
|
-
if (tryBodyStart === -1 || tryBodyEnd === -1 || catchBodyStart === -1 || catchBodyEnd === -1) {
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
const bodyLines = lines.slice(tryBodyStart + 1, tryBodyEnd);
|
|
294
|
-
const catchBodyLines = lines.slice(catchBodyStart + 1, catchBodyEnd);
|
|
295
|
-
return {
|
|
296
|
-
bodyLines,
|
|
297
|
-
catchStart,
|
|
298
|
-
catchBodyLines,
|
|
299
|
-
endLine: catchBodyEnd
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
415
|
|
|
303
416
|
// src/rules/builtin/over-defensive.ts
|
|
304
417
|
var overDefensiveRule = {
|
|
@@ -487,18 +600,10 @@ function detectCodeAfterReturn(context, lines, issues) {
|
|
|
487
600
|
let braceDepth = 0;
|
|
488
601
|
let lastReturnDepth = -1;
|
|
489
602
|
let lastReturnLine = -1;
|
|
603
|
+
const blockComment = { value: false };
|
|
490
604
|
for (let i = 0; i < lines.length; i++) {
|
|
491
605
|
const trimmed = lines[i].trim();
|
|
492
|
-
|
|
493
|
-
if (ch === "{") braceDepth++;
|
|
494
|
-
if (ch === "}") {
|
|
495
|
-
if (braceDepth === lastReturnDepth) {
|
|
496
|
-
lastReturnDepth = -1;
|
|
497
|
-
lastReturnLine = -1;
|
|
498
|
-
}
|
|
499
|
-
braceDepth--;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
606
|
+
braceDepth = countBracesInLine(lines[i], 0, braceDepth, blockComment);
|
|
502
607
|
if (/^(return|throw)\b/.test(trimmed) && !trimmed.includes("=>")) {
|
|
503
608
|
const endsOpen = /[[{(,]$/.test(trimmed) || /^(return|throw)\s*$/.test(trimmed);
|
|
504
609
|
if (endsOpen) continue;
|
|
@@ -524,6 +629,10 @@ function detectCodeAfterReturn(context, lines, issues) {
|
|
|
524
629
|
lastReturnDepth = -1;
|
|
525
630
|
lastReturnLine = -1;
|
|
526
631
|
}
|
|
632
|
+
if (braceDepth < lastReturnDepth) {
|
|
633
|
+
lastReturnDepth = -1;
|
|
634
|
+
lastReturnLine = -1;
|
|
635
|
+
}
|
|
527
636
|
}
|
|
528
637
|
}
|
|
529
638
|
function detectImmediateReassign(context, lines, issues) {
|
|
@@ -557,7 +666,21 @@ function detectImmediateReassign(context, lines, issues) {
|
|
|
557
666
|
}
|
|
558
667
|
|
|
559
668
|
// src/rules/fix-utils.ts
|
|
560
|
-
function
|
|
669
|
+
function buildLineOffsets(content) {
|
|
670
|
+
const offsets = [0];
|
|
671
|
+
for (let i = 0; i < content.length; i++) {
|
|
672
|
+
if (content[i] === "\n") {
|
|
673
|
+
offsets.push(i + 1);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return offsets;
|
|
677
|
+
}
|
|
678
|
+
function lineStartOffset(content, lineNumber, offsets) {
|
|
679
|
+
if (offsets) {
|
|
680
|
+
const idx = lineNumber - 1;
|
|
681
|
+
if (idx < 0 || idx >= offsets.length) return content.length;
|
|
682
|
+
return offsets[idx];
|
|
683
|
+
}
|
|
561
684
|
let offset = 0;
|
|
562
685
|
const lines = content.split("\n");
|
|
563
686
|
for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
|
|
@@ -565,12 +688,13 @@ function lineStartOffset(content, lineNumber) {
|
|
|
565
688
|
}
|
|
566
689
|
return offset;
|
|
567
690
|
}
|
|
568
|
-
function lineRange(content, lineNumber) {
|
|
569
|
-
const
|
|
691
|
+
function lineRange(content, lineNumber, offsets) {
|
|
692
|
+
const table = offsets ?? buildLineOffsets(content);
|
|
570
693
|
const lineIndex = lineNumber - 1;
|
|
571
|
-
if (lineIndex < 0 || lineIndex >=
|
|
572
|
-
const start =
|
|
573
|
-
const
|
|
694
|
+
if (lineIndex < 0 || lineIndex >= table.length) return [0, 0];
|
|
695
|
+
const start = table[lineIndex];
|
|
696
|
+
const nextLineStart = lineIndex + 1 < table.length ? table[lineIndex + 1] : content.length;
|
|
697
|
+
const end = lineIndex + 1 < table.length ? nextLineStart : nextLineStart;
|
|
574
698
|
return [start, end];
|
|
575
699
|
}
|
|
576
700
|
|
|
@@ -636,12 +760,12 @@ function extractFunctions(parsed) {
|
|
|
636
760
|
}
|
|
637
761
|
function visitNode(root, functions) {
|
|
638
762
|
const methodBodies = /* @__PURE__ */ new WeakSet();
|
|
639
|
-
walkAST(root, (node) => {
|
|
763
|
+
walkAST(root, (node, parent) => {
|
|
640
764
|
if (node.type === AST_NODE_TYPES.FunctionExpression && methodBodies.has(node)) {
|
|
641
765
|
return false;
|
|
642
766
|
}
|
|
643
767
|
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) {
|
|
644
|
-
const info = analyzeFunctionNode(node);
|
|
768
|
+
const info = analyzeFunctionNode(node, parent);
|
|
645
769
|
if (info) functions.push(info);
|
|
646
770
|
if (node.type === AST_NODE_TYPES.MethodDefinition) {
|
|
647
771
|
methodBodies.add(node.value);
|
|
@@ -650,7 +774,7 @@ function visitNode(root, functions) {
|
|
|
650
774
|
return;
|
|
651
775
|
});
|
|
652
776
|
}
|
|
653
|
-
function analyzeFunctionNode(node) {
|
|
777
|
+
function analyzeFunctionNode(node, parent) {
|
|
654
778
|
let name = "<anonymous>";
|
|
655
779
|
let params = [];
|
|
656
780
|
let body = null;
|
|
@@ -663,7 +787,11 @@ function analyzeFunctionNode(node) {
|
|
|
663
787
|
params = node.params;
|
|
664
788
|
body = node.body;
|
|
665
789
|
} else if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
666
|
-
|
|
790
|
+
if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier) {
|
|
791
|
+
name = parent.id.name;
|
|
792
|
+
} else {
|
|
793
|
+
name = "<arrow>";
|
|
794
|
+
}
|
|
667
795
|
params = node.params;
|
|
668
796
|
body = node.body;
|
|
669
797
|
} else if (node.type === AST_NODE_TYPES.MethodDefinition) {
|
|
@@ -853,7 +981,24 @@ function collectDeclarationsAndReferences(root, declarations, references) {
|
|
|
853
981
|
kind: exportedNames.has(node.id.name) ? "export" : "local"
|
|
854
982
|
});
|
|
855
983
|
}
|
|
856
|
-
if (node.type === AST_NODE_TYPES.
|
|
984
|
+
if (node.type === AST_NODE_TYPES.ClassDeclaration && node.id) {
|
|
985
|
+
declarations.set(node.id.name, {
|
|
986
|
+
line: node.loc?.start.line ?? 0,
|
|
987
|
+
kind: exportedNames.has(node.id.name) ? "export" : "local"
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
const declarationParentTypes = /* @__PURE__ */ new Set([
|
|
991
|
+
"VariableDeclarator",
|
|
992
|
+
"FunctionDeclaration",
|
|
993
|
+
"ClassDeclaration",
|
|
994
|
+
"MethodDefinition",
|
|
995
|
+
"TSEnumDeclaration",
|
|
996
|
+
"TSEnumMember",
|
|
997
|
+
"TSTypeAliasDeclaration",
|
|
998
|
+
"TSInterfaceDeclaration",
|
|
999
|
+
"TSModuleDeclaration"
|
|
1000
|
+
]);
|
|
1001
|
+
if (node.type === AST_NODE_TYPES.Identifier && !declarationParentTypes.has(parentType ?? "")) {
|
|
857
1002
|
references.add(node.name);
|
|
858
1003
|
}
|
|
859
1004
|
return;
|
|
@@ -940,8 +1085,25 @@ function stringifyCondition(node) {
|
|
|
940
1085
|
}
|
|
941
1086
|
case AST_NODE_TYPES.ConditionalExpression:
|
|
942
1087
|
return `${stringifyCondition(node.test)} ? ${stringifyCondition(node.consequent)} : ${stringifyCondition(node.alternate)}`;
|
|
1088
|
+
case AST_NODE_TYPES.TemplateLiteral:
|
|
1089
|
+
return `\`template@${node.loc?.start.line}:${node.loc?.start.column}\``;
|
|
1090
|
+
case AST_NODE_TYPES.ArrayExpression:
|
|
1091
|
+
return `[${node.elements.map((e) => e ? stringifyCondition(e) : "empty").join(", ")}]`;
|
|
1092
|
+
case AST_NODE_TYPES.ObjectExpression:
|
|
1093
|
+
return `{obj@${node.loc?.start.line}:${node.loc?.start.column}}`;
|
|
1094
|
+
case AST_NODE_TYPES.AssignmentExpression:
|
|
1095
|
+
return `${stringifyCondition(node.left)} ${node.operator} ${stringifyCondition(node.right)}`;
|
|
1096
|
+
case AST_NODE_TYPES.NewExpression:
|
|
1097
|
+
return `new ${stringifyCondition(node.callee)}(${node.arguments.map((a) => stringifyCondition(a)).join(", ")})`;
|
|
1098
|
+
case AST_NODE_TYPES.TSAsExpression:
|
|
1099
|
+
case AST_NODE_TYPES.TSNonNullExpression:
|
|
1100
|
+
return stringifyCondition(node.expression);
|
|
1101
|
+
case AST_NODE_TYPES.AwaitExpression:
|
|
1102
|
+
return `await ${stringifyCondition(node.argument)}`;
|
|
1103
|
+
case AST_NODE_TYPES.ChainExpression:
|
|
1104
|
+
return stringifyCondition(node.expression);
|
|
943
1105
|
default:
|
|
944
|
-
return `[${node.type}]`;
|
|
1106
|
+
return `[${node.type}@${node.loc?.start.line}:${node.loc?.start.column}]`;
|
|
945
1107
|
}
|
|
946
1108
|
}
|
|
947
1109
|
function truncate(s, maxLen) {
|
|
@@ -1153,7 +1315,8 @@ var emptyCatchRule = {
|
|
|
1153
1315
|
const catchMatch = trimmed.match(/\bcatch\s*\(\s*(\w+)?\s*\)\s*\{/);
|
|
1154
1316
|
if (!catchMatch) continue;
|
|
1155
1317
|
const catchVarName = catchMatch[1] || "";
|
|
1156
|
-
const
|
|
1318
|
+
const catchIdx = lines[i].indexOf("catch");
|
|
1319
|
+
const blockContent = extractBraceBlock(lines, i, catchIdx);
|
|
1157
1320
|
if (!blockContent) continue;
|
|
1158
1321
|
const { bodyLines, endLine } = blockContent;
|
|
1159
1322
|
const meaningful = bodyLines.filter(
|
|
@@ -1203,37 +1366,6 @@ var emptyCatchRule = {
|
|
|
1203
1366
|
return issues;
|
|
1204
1367
|
}
|
|
1205
1368
|
};
|
|
1206
|
-
function extractCatchBody(lines, catchLineIndex) {
|
|
1207
|
-
const catchLine = lines[catchLineIndex];
|
|
1208
|
-
const catchIdx = catchLine.indexOf("catch");
|
|
1209
|
-
if (catchIdx === -1) return null;
|
|
1210
|
-
let braceCount = 0;
|
|
1211
|
-
let started = false;
|
|
1212
|
-
let bodyStart = -1;
|
|
1213
|
-
for (let i = catchLineIndex; i < lines.length; i++) {
|
|
1214
|
-
const line = lines[i];
|
|
1215
|
-
const startJ = i === catchLineIndex ? catchIdx : 0;
|
|
1216
|
-
for (let j = startJ; j < line.length; j++) {
|
|
1217
|
-
const ch = line[j];
|
|
1218
|
-
if (ch === "{") {
|
|
1219
|
-
braceCount++;
|
|
1220
|
-
if (!started) {
|
|
1221
|
-
started = true;
|
|
1222
|
-
bodyStart = i;
|
|
1223
|
-
}
|
|
1224
|
-
} else if (ch === "}") {
|
|
1225
|
-
braceCount--;
|
|
1226
|
-
if (started && braceCount === 0) {
|
|
1227
|
-
return {
|
|
1228
|
-
bodyLines: lines.slice(bodyStart + 1, i),
|
|
1229
|
-
endLine: i
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
return null;
|
|
1236
|
-
}
|
|
1237
1369
|
|
|
1238
1370
|
// src/rules/builtin/identical-branches.ts
|
|
1239
1371
|
var identicalBranchesRule = {
|
|
@@ -1580,10 +1712,25 @@ var unusedImportRule = {
|
|
|
1580
1712
|
}
|
|
1581
1713
|
return;
|
|
1582
1714
|
});
|
|
1583
|
-
const
|
|
1584
|
-
let
|
|
1585
|
-
|
|
1586
|
-
|
|
1715
|
+
const codeLines = context.fileContent.split("\n");
|
|
1716
|
+
let inBlock = false;
|
|
1717
|
+
for (const codeLine of codeLines) {
|
|
1718
|
+
const trimmedCode = codeLine.trim();
|
|
1719
|
+
if (inBlock) {
|
|
1720
|
+
if (trimmedCode.includes("*/")) inBlock = false;
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
if (trimmedCode.startsWith("/*")) {
|
|
1724
|
+
if (!trimmedCode.includes("*/")) inBlock = true;
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
if (trimmedCode.startsWith("//") || trimmedCode.startsWith("*")) continue;
|
|
1728
|
+
const cleaned = codeLine.replace(/\/\/.*$/, "").replace(/\/\*.*?\*\//g, "").replace(/'(?:[^'\\]|\\.)*'/g, "").replace(/"(?:[^"\\]|\\.)*"/g, "").replace(/`(?:[^`\\]|\\.)*`/g, "");
|
|
1729
|
+
const typeRefPattern = /\b([A-Z][A-Za-z0-9]*)\b/g;
|
|
1730
|
+
let match;
|
|
1731
|
+
while ((match = typeRefPattern.exec(cleaned)) !== null) {
|
|
1732
|
+
references.add(match[1]);
|
|
1733
|
+
}
|
|
1587
1734
|
}
|
|
1588
1735
|
for (const imp of imports) {
|
|
1589
1736
|
if (!references.has(imp.local)) {
|
|
@@ -2709,13 +2856,20 @@ var SEVERITY_PENALTY = {
|
|
|
2709
2856
|
low: 3,
|
|
2710
2857
|
info: 0
|
|
2711
2858
|
};
|
|
2859
|
+
var DIMINISHING_FACTOR = 0.7;
|
|
2712
2860
|
function calculateDimensionScore(issues) {
|
|
2713
2861
|
let score = 100;
|
|
2862
|
+
const severityCounts = {};
|
|
2714
2863
|
for (const issue of issues) {
|
|
2715
|
-
|
|
2864
|
+
const base = SEVERITY_PENALTY[issue.severity] ?? 0;
|
|
2865
|
+
if (base === 0) continue;
|
|
2866
|
+
const n = severityCounts[issue.severity] ?? 0;
|
|
2867
|
+
severityCounts[issue.severity] = n + 1;
|
|
2868
|
+
const penalty = base * Math.pow(DIMINISHING_FACTOR, n);
|
|
2869
|
+
score -= penalty;
|
|
2716
2870
|
}
|
|
2717
2871
|
return {
|
|
2718
|
-
score: Math.max(0, Math.min(100, score)),
|
|
2872
|
+
score: Math.round(Math.max(0, Math.min(100, score)) * 10) / 10,
|
|
2719
2873
|
issues
|
|
2720
2874
|
};
|
|
2721
2875
|
}
|
|
@@ -3068,7 +3222,7 @@ var PKG_VERSION = (() => {
|
|
|
3068
3222
|
}
|
|
3069
3223
|
})();
|
|
3070
3224
|
var REPORT_SCHEMA_VERSION = "1.0.0";
|
|
3071
|
-
var FINGERPRINT_VERSION = "
|
|
3225
|
+
var FINGERPRINT_VERSION = "2";
|
|
3072
3226
|
var ScanEngine = class {
|
|
3073
3227
|
config;
|
|
3074
3228
|
diffParser;
|
|
@@ -3320,13 +3474,14 @@ var ScanEngine = class {
|
|
|
3320
3474
|
const occurrenceCounts = /* @__PURE__ */ new Map();
|
|
3321
3475
|
return issues.map((issue) => {
|
|
3322
3476
|
const normalizedFile = this.normalizeRelativePath(issue.file);
|
|
3323
|
-
const
|
|
3477
|
+
const contentSource = issue.codeSnippet ? issue.codeSnippet : `${issue.startLine}:${issue.endLine}`;
|
|
3478
|
+
const contentDigest = createHash("sha256").update(contentSource).digest("hex").slice(0, 16);
|
|
3324
3479
|
const baseKey = [
|
|
3325
3480
|
issue.ruleId,
|
|
3326
3481
|
normalizedFile,
|
|
3327
3482
|
issue.category,
|
|
3328
3483
|
issue.severity,
|
|
3329
|
-
|
|
3484
|
+
contentDigest
|
|
3330
3485
|
].join("|");
|
|
3331
3486
|
const occurrenceIndex = occurrenceCounts.get(baseKey) ?? 0;
|
|
3332
3487
|
occurrenceCounts.set(baseKey, occurrenceIndex + 1);
|
|
@@ -3435,17 +3590,19 @@ var ScanEngine = class {
|
|
|
3435
3590
|
return ["security", "logic", "structure", "style", "coverage"].includes(value);
|
|
3436
3591
|
}
|
|
3437
3592
|
groupByDimension(issues) {
|
|
3438
|
-
const
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3593
|
+
const buckets = {
|
|
3594
|
+
security: [],
|
|
3595
|
+
logic: [],
|
|
3596
|
+
structure: [],
|
|
3597
|
+
style: [],
|
|
3598
|
+
coverage: []
|
|
3599
|
+
};
|
|
3600
|
+
for (const issue of issues) {
|
|
3601
|
+
buckets[issue.category]?.push(issue);
|
|
3602
|
+
}
|
|
3445
3603
|
const grouped = {};
|
|
3446
|
-
for (const cat of
|
|
3447
|
-
|
|
3448
|
-
grouped[cat] = calculateDimensionScore(catIssues);
|
|
3604
|
+
for (const cat of Object.keys(buckets)) {
|
|
3605
|
+
grouped[cat] = calculateDimensionScore(buckets[cat]);
|
|
3449
3606
|
}
|
|
3450
3607
|
return grouped;
|
|
3451
3608
|
}
|
|
@@ -3549,6 +3706,7 @@ thresholds:
|
|
|
3549
3706
|
min-score: 70
|
|
3550
3707
|
max-function-length: 40
|
|
3551
3708
|
max-cyclomatic-complexity: 10
|
|
3709
|
+
max-cognitive-complexity: 20
|
|
3552
3710
|
max-nesting-depth: 4
|
|
3553
3711
|
max-params: 5
|
|
3554
3712
|
|