@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/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
- const headerMatch = lines[0]?.match(/a\/(.+?) b\/(.+)/);
94
- if (!headerMatch) return null;
95
- const filePath = headerMatch[2];
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 tryBlock = extractBlock(lines, i);
361
+ const tryCol = line.indexOf("try");
362
+ const tryBlock = extractBraceBlock(lines, i, tryCol);
215
363
  if (tryBlock) {
216
- const { bodyLines, catchBodyLines, endLine } = tryBlock;
217
- const nonEmptyBody = bodyLines.filter((l) => l.trim().length > 0);
218
- const nonEmptyCatch = catchBodyLines.filter((l) => l.trim().length > 0);
219
- const isSimpleBody = nonEmptyBody.length <= 2;
220
- const isGenericCatch = nonEmptyCatch.length <= 2 && nonEmptyCatch.some(
221
- (l) => /console\.(log|error|warn)/.test(l) || /throw\s+(new\s+)?Error/.test(l) || l.trim() === ""
222
- );
223
- const bodyHasOnlyAssignments = nonEmptyBody.every(
224
- (l) => /^\s*(const|let|var)\s+/.test(l) || /^\s*\w+(\.\w+)*\s*=\s*/.test(l) || /^\s*return\s+/.test(l)
225
- );
226
- if (isSimpleBody && isGenericCatch && bodyHasOnlyAssignments) {
227
- issues.push({
228
- ruleId: "logic/unnecessary-try-catch",
229
- severity: "medium",
230
- category: "logic",
231
- file: context.filePath,
232
- startLine: i + 1,
233
- endLine: endLine + 1,
234
- message: t(
235
- "Unnecessary try-catch wrapping a simple statement with generic error handling. This is likely AI-hallucinated error handling.",
236
- "\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"
237
- ),
238
- suggestion: t(
239
- "Remove the try-catch block or add meaningful error recovery logic.",
240
- "\u79FB\u9664 try-catch \u5757\uFF0C\u6216\u6DFB\u52A0\u6709\u610F\u4E49\u7684\u9519\u8BEF\u6062\u590D\u903B\u8F91\u3002"
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
- for (const ch of trimmed) {
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 lineStartOffset(content, lineNumber) {
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 lines = content.split("\n");
691
+ function lineRange(content, lineNumber, offsets) {
692
+ const table = offsets ?? buildLineOffsets(content);
570
693
  const lineIndex = lineNumber - 1;
571
- if (lineIndex < 0 || lineIndex >= lines.length) return [0, 0];
572
- const start = lineStartOffset(content, lineNumber);
573
- const end = start + lines[lineIndex].length + (lineIndex < lines.length - 1 ? 1 : 0);
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
- name = "<arrow>";
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.Identifier && parentType !== "VariableDeclarator" && parentType !== "FunctionDeclaration") {
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 blockContent = extractCatchBody(lines, i);
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 typeRefPattern = /\b([A-Z][A-Za-z0-9]*)\b/g;
1584
- let match;
1585
- while ((match = typeRefPattern.exec(context.fileContent)) !== null) {
1586
- references.add(match[1]);
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
- score -= SEVERITY_PENALTY[issue.severity] ?? 0;
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 = "1";
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 locationComponent = `${issue.startLine}:${issue.endLine}`;
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
- locationComponent
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 categories = [
3439
- "security",
3440
- "logic",
3441
- "structure",
3442
- "style",
3443
- "coverage"
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 categories) {
3447
- const catIssues = issues.filter((issue) => issue.category === cat);
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