slopbrick 0.18.3 → 0.18.5
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/engine/worker.cjs +426 -5
- package/dist/engine/worker.js +426 -5
- package/dist/index.cjs +735 -76
- package/dist/index.d.cts +611 -511
- package/dist/index.d.ts +611 -511
- package/dist/index.js +734 -75
- package/package.json +1 -1
package/dist/engine/worker.cjs
CHANGED
|
@@ -6335,6 +6335,78 @@ function extractStateBinding(node, lineOffsets) {
|
|
|
6335
6335
|
setterReferenced: false
|
|
6336
6336
|
};
|
|
6337
6337
|
}
|
|
6338
|
+
function findUnreachableStatements(ast, source, lineOffsets) {
|
|
6339
|
+
const out = [];
|
|
6340
|
+
function isTerminator(node) {
|
|
6341
|
+
if (node.type === "ReturnStatement") return "return";
|
|
6342
|
+
if (node.type === "ThrowStatement") return "throw";
|
|
6343
|
+
if (node.type === "BreakStatement") return "break";
|
|
6344
|
+
if (node.type === "ContinueStatement") return "continue";
|
|
6345
|
+
return null;
|
|
6346
|
+
}
|
|
6347
|
+
function snippetFor(node, src) {
|
|
6348
|
+
const span = node.span;
|
|
6349
|
+
if (!span || typeof span.start !== "number" || typeof span.end !== "number") {
|
|
6350
|
+
return "<unreachable>";
|
|
6351
|
+
}
|
|
6352
|
+
const text = src.slice(span.start, Math.min(span.end, span.start + 60));
|
|
6353
|
+
return text.replace(/\s+/g, " ").trim() || "<unreachable>";
|
|
6354
|
+
}
|
|
6355
|
+
function lineColumn(offset) {
|
|
6356
|
+
let lo = 0;
|
|
6357
|
+
let hi = lineOffsets.length - 1;
|
|
6358
|
+
while (lo < hi) {
|
|
6359
|
+
const mid = lo + hi + 1 >>> 1;
|
|
6360
|
+
const midVal = lineOffsets[mid];
|
|
6361
|
+
const loVal = lineOffsets[lo];
|
|
6362
|
+
if (midVal === void 0) break;
|
|
6363
|
+
if (midVal <= offset) lo = mid;
|
|
6364
|
+
else hi = mid - 1;
|
|
6365
|
+
void loVal;
|
|
6366
|
+
}
|
|
6367
|
+
const baseOffset = lineOffsets[lo] ?? 0;
|
|
6368
|
+
return { line: lo + 1, column: offset - baseOffset };
|
|
6369
|
+
}
|
|
6370
|
+
function visitBody(body) {
|
|
6371
|
+
if (!Array.isArray(body)) return;
|
|
6372
|
+
let lastTerminator = null;
|
|
6373
|
+
for (const stmt of body) {
|
|
6374
|
+
if (!isObject(stmt)) continue;
|
|
6375
|
+
if (lastTerminator && stmt.type !== "EmptyStatement") {
|
|
6376
|
+
const span = stmt.span;
|
|
6377
|
+
const offset = typeof span?.start === "number" ? span.start : 0;
|
|
6378
|
+
const { line, column } = lineColumn(offset);
|
|
6379
|
+
out.push({
|
|
6380
|
+
terminator: lastTerminator,
|
|
6381
|
+
line,
|
|
6382
|
+
column,
|
|
6383
|
+
snippet: snippetFor(stmt, source)
|
|
6384
|
+
});
|
|
6385
|
+
}
|
|
6386
|
+
const t = isTerminator(stmt);
|
|
6387
|
+
if (t) lastTerminator = t;
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
function walk3(node) {
|
|
6391
|
+
if (!isObject(node)) return;
|
|
6392
|
+
if (node.type === "BlockStatement") {
|
|
6393
|
+
visitBody(node.stmts);
|
|
6394
|
+
} else if (node.type === "Module") {
|
|
6395
|
+
visitBody(node.body);
|
|
6396
|
+
}
|
|
6397
|
+
for (const key of Object.keys(node)) {
|
|
6398
|
+
if (key === "parent" || key === "span" || key === "ctxt") continue;
|
|
6399
|
+
const child = node[key];
|
|
6400
|
+
if (Array.isArray(child)) {
|
|
6401
|
+
for (const c of child) walk3(c);
|
|
6402
|
+
} else {
|
|
6403
|
+
walk3(child);
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
walk3(ast);
|
|
6408
|
+
return out;
|
|
6409
|
+
}
|
|
6338
6410
|
|
|
6339
6411
|
// src/engine/visitors/dispatch.ts
|
|
6340
6412
|
function nearestComponent(vctx) {
|
|
@@ -6411,15 +6483,42 @@ function handleImportDeclaration(node, _parent, _path, vctx) {
|
|
|
6411
6483
|
if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
|
|
6412
6484
|
const local = specifier.local;
|
|
6413
6485
|
if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
|
|
6414
|
-
|
|
6486
|
+
const name = local.value;
|
|
6487
|
+
importedNames.push(name);
|
|
6488
|
+
vctx.facts.deadCode.bindings.push({
|
|
6489
|
+
name,
|
|
6490
|
+
kind: specifier.type === "ImportDefaultSpecifier" ? "import-default" : "import-namespace",
|
|
6491
|
+
line,
|
|
6492
|
+
column,
|
|
6493
|
+
source,
|
|
6494
|
+
isReferenced: false
|
|
6495
|
+
});
|
|
6415
6496
|
}
|
|
6416
6497
|
} else if (specifier.type === "ImportSpecifier") {
|
|
6417
6498
|
const imported = specifier.imported;
|
|
6418
6499
|
const local = specifier.local;
|
|
6419
6500
|
if (isObject(imported) && typeof imported.value === "string" && imported.value.length > 0) {
|
|
6420
|
-
|
|
6501
|
+
const name = imported.value;
|
|
6502
|
+
importedNames.push(name);
|
|
6503
|
+
vctx.facts.deadCode.bindings.push({
|
|
6504
|
+
name,
|
|
6505
|
+
kind: "import-specifier",
|
|
6506
|
+
line,
|
|
6507
|
+
column,
|
|
6508
|
+
source,
|
|
6509
|
+
isReferenced: false
|
|
6510
|
+
});
|
|
6421
6511
|
} else if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
|
|
6422
|
-
|
|
6512
|
+
const name = local.value;
|
|
6513
|
+
importedNames.push(name);
|
|
6514
|
+
vctx.facts.deadCode.bindings.push({
|
|
6515
|
+
name,
|
|
6516
|
+
kind: "import-specifier",
|
|
6517
|
+
line,
|
|
6518
|
+
column,
|
|
6519
|
+
source,
|
|
6520
|
+
isReferenced: false
|
|
6521
|
+
});
|
|
6423
6522
|
}
|
|
6424
6523
|
}
|
|
6425
6524
|
}
|
|
@@ -6509,6 +6608,13 @@ function isBindingSite(node, parent) {
|
|
|
6509
6608
|
if (params.some((param) => containsNode(param, node))) return true;
|
|
6510
6609
|
}
|
|
6511
6610
|
}
|
|
6611
|
+
if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") {
|
|
6612
|
+
if (parent.type === "ImportSpecifier") {
|
|
6613
|
+
if (parent.imported === node || parent.local === node) return true;
|
|
6614
|
+
} else {
|
|
6615
|
+
if (parent.local === node) return true;
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6512
6618
|
return false;
|
|
6513
6619
|
}
|
|
6514
6620
|
function isNonComputedMemberProperty(node, parent) {
|
|
@@ -6575,6 +6681,9 @@ function handleIdentifier(node, parent, path, vctx) {
|
|
|
6575
6681
|
if (typeof node.value === "string" && !isBindingSite(node, parent) && !isNonComputedMemberProperty(node, parent)) {
|
|
6576
6682
|
markStateReference(node.value, vctx);
|
|
6577
6683
|
trackPropUsage(node, parent, path, vctx);
|
|
6684
|
+
vctx.facts.referencedNames.add(node.value);
|
|
6685
|
+
const top = vctx.ctx.stack[vctx.ctx.stack.length - 1];
|
|
6686
|
+
if (top) top.references.add(node.value);
|
|
6578
6687
|
}
|
|
6579
6688
|
return false;
|
|
6580
6689
|
}
|
|
@@ -6715,6 +6824,20 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
|
|
|
6715
6824
|
frame.bindings.add(bindingName);
|
|
6716
6825
|
}
|
|
6717
6826
|
}
|
|
6827
|
+
if (bindingNames.length > 0) {
|
|
6828
|
+
const parent = node.parent;
|
|
6829
|
+
const kind = isObject(parent) && parent.type === "VariableDeclaration" ? String(parent.kind ?? "var") : "var";
|
|
6830
|
+
const { line, column } = positionFrom(id, vctx.lineOffsets);
|
|
6831
|
+
for (const bindingName of bindingNames) {
|
|
6832
|
+
vctx.facts.deadCode.bindings.push({
|
|
6833
|
+
name: bindingName,
|
|
6834
|
+
kind,
|
|
6835
|
+
line,
|
|
6836
|
+
column,
|
|
6837
|
+
isReferenced: false
|
|
6838
|
+
});
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6718
6841
|
if (isUseStateDeclarator(node)) {
|
|
6719
6842
|
const binding = extractStateBinding(node, vctx.lineOffsets);
|
|
6720
6843
|
if (binding) {
|
|
@@ -6734,6 +6857,36 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
|
|
|
6734
6857
|
}
|
|
6735
6858
|
return true;
|
|
6736
6859
|
}
|
|
6860
|
+
function handleIfStatement(node, _parent, _path, vctx) {
|
|
6861
|
+
if (!isObject(node)) return false;
|
|
6862
|
+
const test = node.test;
|
|
6863
|
+
if (!isObject(test)) return false;
|
|
6864
|
+
if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
|
|
6865
|
+
const { line, column } = positionFrom(test, vctx.lineOffsets);
|
|
6866
|
+
vctx.facts.deadCode.constantConditions.push({
|
|
6867
|
+
kind: test.value ? "if-true" : "if-false",
|
|
6868
|
+
condition: String(test.value),
|
|
6869
|
+
line,
|
|
6870
|
+
column
|
|
6871
|
+
});
|
|
6872
|
+
}
|
|
6873
|
+
return false;
|
|
6874
|
+
}
|
|
6875
|
+
function handleWhileStatement(node, _parent, _path, vctx) {
|
|
6876
|
+
if (!isObject(node)) return false;
|
|
6877
|
+
const test = node.test;
|
|
6878
|
+
if (!isObject(test)) return false;
|
|
6879
|
+
if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
|
|
6880
|
+
const { line, column } = positionFrom(test, vctx.lineOffsets);
|
|
6881
|
+
vctx.facts.deadCode.constantConditions.push({
|
|
6882
|
+
kind: test.value ? "while-true" : "while-false",
|
|
6883
|
+
condition: String(test.value),
|
|
6884
|
+
line,
|
|
6885
|
+
column
|
|
6886
|
+
});
|
|
6887
|
+
}
|
|
6888
|
+
return false;
|
|
6889
|
+
}
|
|
6737
6890
|
var HANDLERS = {
|
|
6738
6891
|
ExpressionStatement: handleExpressionStatement,
|
|
6739
6892
|
ImportDeclaration: handleImportDeclaration,
|
|
@@ -6743,7 +6896,9 @@ var HANDLERS = {
|
|
|
6743
6896
|
MemberExpression: handleMemberExpression,
|
|
6744
6897
|
JSXAttribute: handleJSXAttribute,
|
|
6745
6898
|
JSXOpeningElement: handleJSXOpeningElement,
|
|
6746
|
-
VariableDeclarator: handleVariableDeclarator
|
|
6899
|
+
VariableDeclarator: handleVariableDeclarator,
|
|
6900
|
+
IfStatement: handleIfStatement,
|
|
6901
|
+
WhileStatement: handleWhileStatement
|
|
6747
6902
|
};
|
|
6748
6903
|
function dispatchNode(node, parent, path, vctx) {
|
|
6749
6904
|
if (!isObject(node)) return false;
|
|
@@ -6908,6 +7063,17 @@ function buildV2Facts(facts, source, ext, framework, config, templateClassNames
|
|
|
6908
7063
|
},
|
|
6909
7064
|
logic: buildLogicBlock(facts),
|
|
6910
7065
|
designTokens: scanDesignTokens(facts.staticClassNames),
|
|
7066
|
+
// dead-code detector. Copy the internal accumulator
|
|
7067
|
+
// into the v2 shape, marking each binding as referenced
|
|
7068
|
+
// iff the file-level referenced-name set contains its name.
|
|
7069
|
+
deadCode: {
|
|
7070
|
+
bindings: facts.deadCode.bindings.map((b) => ({
|
|
7071
|
+
...b,
|
|
7072
|
+
isReferenced: facts.referencedNames.has(b.name)
|
|
7073
|
+
})),
|
|
7074
|
+
constantConditions: facts.deadCode.constantConditions,
|
|
7075
|
+
unreachableStatements: facts.deadCode.unreachableStatements
|
|
7076
|
+
},
|
|
6911
7077
|
componentSizes: facts.componentSizes.map((cs) => ({
|
|
6912
7078
|
name: cs.name,
|
|
6913
7079
|
lineCount: cs.lineCount,
|
|
@@ -6975,7 +7141,17 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
6975
7141
|
componentSizes: [],
|
|
6976
7142
|
astroComponents: [],
|
|
6977
7143
|
fetchCalls: [],
|
|
6978
|
-
optimisticUpdates: []
|
|
7144
|
+
optimisticUpdates: [],
|
|
7145
|
+
// dead-code detector. The visitor's identifier walk + import/
|
|
7146
|
+
// branch/return handlers populate these. The v2 builder at the
|
|
7147
|
+
// bottom of extractFacts() reads them and produces
|
|
7148
|
+
// `facts.v2.deadCode`.
|
|
7149
|
+
deadCode: {
|
|
7150
|
+
bindings: [],
|
|
7151
|
+
constantConditions: [],
|
|
7152
|
+
unreachableStatements: []
|
|
7153
|
+
},
|
|
7154
|
+
referencedNames: /* @__PURE__ */ new Set()
|
|
6979
7155
|
};
|
|
6980
7156
|
const ctx = {
|
|
6981
7157
|
stack: [],
|
|
@@ -7062,6 +7238,16 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
7062
7238
|
propBindingSet.add(bindingName);
|
|
7063
7239
|
}
|
|
7064
7240
|
}
|
|
7241
|
+
const { line: pLine, column: pCol } = positionFrom(param, lineOffsets);
|
|
7242
|
+
for (const bindingName of collectBindingNames2(param)) {
|
|
7243
|
+
facts.deadCode.bindings.push({
|
|
7244
|
+
name: bindingName,
|
|
7245
|
+
kind: "parameter",
|
|
7246
|
+
line: pLine,
|
|
7247
|
+
column: pCol,
|
|
7248
|
+
isReferenced: false
|
|
7249
|
+
});
|
|
7250
|
+
}
|
|
7065
7251
|
}
|
|
7066
7252
|
}
|
|
7067
7253
|
ctx.stack.push({
|
|
@@ -7077,6 +7263,12 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
7077
7263
|
propUsages: [],
|
|
7078
7264
|
isComponent,
|
|
7079
7265
|
bindings,
|
|
7266
|
+
// dead-code detector: per-frame referenced-name set.
|
|
7267
|
+
// Identifiers encountered inside the frame are added to this
|
|
7268
|
+
// set; the deadCode builder unions it with parent frames at
|
|
7269
|
+
// pop time so a binding is considered used if any reachable
|
|
7270
|
+
// scope references it.
|
|
7271
|
+
references: /* @__PURE__ */ new Set(),
|
|
7080
7272
|
propBindingSet,
|
|
7081
7273
|
propUsageSet: /* @__PURE__ */ new Set(),
|
|
7082
7274
|
node
|
|
@@ -7172,6 +7364,11 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
7172
7364
|
mergeTemplateClassNames(filePath, source, facts, templateClassNames);
|
|
7173
7365
|
const { ext } = splitFilePath(filePath);
|
|
7174
7366
|
facts._source = source;
|
|
7367
|
+
facts.deadCode.unreachableStatements = findUnreachableStatements(
|
|
7368
|
+
ast,
|
|
7369
|
+
source,
|
|
7370
|
+
lineOffsets
|
|
7371
|
+
);
|
|
7175
7372
|
const v2 = buildV2Facts(facts, source, ext, framework, config, templateClassNames);
|
|
7176
7373
|
return envelopeScanFacts(filePath, v2);
|
|
7177
7374
|
}
|
|
@@ -34306,6 +34503,170 @@ var sqlConcatRule = createRule({
|
|
|
34306
34503
|
}
|
|
34307
34504
|
});
|
|
34308
34505
|
|
|
34506
|
+
// src/rules/dead/dead-branch.ts
|
|
34507
|
+
var deadBranchRule = createRule({
|
|
34508
|
+
id: "dead/dead-branch",
|
|
34509
|
+
category: "logic",
|
|
34510
|
+
severity: "medium",
|
|
34511
|
+
aiSpecific: true,
|
|
34512
|
+
description: "Literal boolean condition makes one branch statically dead",
|
|
34513
|
+
create(_context) {
|
|
34514
|
+
return {};
|
|
34515
|
+
},
|
|
34516
|
+
analyze(_context, facts) {
|
|
34517
|
+
const issues = [];
|
|
34518
|
+
if (!facts.v2) return issues;
|
|
34519
|
+
for (const cond of facts.v2.deadCode.constantConditions) {
|
|
34520
|
+
const isWhileTrue = cond.kind === "while-true";
|
|
34521
|
+
issues.push({
|
|
34522
|
+
ruleId: "dead/dead-branch",
|
|
34523
|
+
category: "logic",
|
|
34524
|
+
severity: isWhileTrue ? "low" : "medium",
|
|
34525
|
+
aiSpecific: true,
|
|
34526
|
+
message: isWhileTrue ? `Infinite loop with literal condition (${cond.kind})` : `Dead branch: condition is always ${cond.condition}`,
|
|
34527
|
+
line: cond.line,
|
|
34528
|
+
column: cond.column,
|
|
34529
|
+
advice: isWhileTrue ? `If this is an intentional infinite loop (event loop, hot loop with explicit \`break\`), add a \`// slopbrick-disable\` comment. Otherwise, replace the literal with a real condition.` : `Replace the literal with a real condition, or remove the dead branch entirely. This is the AI-iteration signature: the model toggled a feature flag to a constant or left a wrapper from a previous refactor.`
|
|
34530
|
+
});
|
|
34531
|
+
}
|
|
34532
|
+
return issues;
|
|
34533
|
+
}
|
|
34534
|
+
});
|
|
34535
|
+
|
|
34536
|
+
// src/rules/dead/unreachable.ts
|
|
34537
|
+
var unreachableRule = createRule({
|
|
34538
|
+
id: "dead/unreachable",
|
|
34539
|
+
category: "logic",
|
|
34540
|
+
severity: "high",
|
|
34541
|
+
aiSpecific: true,
|
|
34542
|
+
description: "Statement is unreachable after an unconditional return/throw/break/continue",
|
|
34543
|
+
create(_context) {
|
|
34544
|
+
return {};
|
|
34545
|
+
},
|
|
34546
|
+
analyze(_context, facts) {
|
|
34547
|
+
const issues = [];
|
|
34548
|
+
if (!facts.v2) return issues;
|
|
34549
|
+
for (const u of facts.v2.deadCode.unreachableStatements) {
|
|
34550
|
+
if (u.snippet === "<unreachable>") continue;
|
|
34551
|
+
issues.push({
|
|
34552
|
+
ruleId: "dead/unreachable",
|
|
34553
|
+
category: "logic",
|
|
34554
|
+
severity: "high",
|
|
34555
|
+
aiSpecific: true,
|
|
34556
|
+
message: `Unreachable after ${u.terminator}: ${u.snippet}`,
|
|
34557
|
+
line: u.line,
|
|
34558
|
+
column: u.column,
|
|
34559
|
+
advice: `Remove this statement \u2014 code after a ${u.terminator} is unreachable. This is the AI-iteration signature: the model added an early ${u.terminator} for a new error path, then forgot the rest of the function body was still sitting below it.`
|
|
34560
|
+
});
|
|
34561
|
+
}
|
|
34562
|
+
return issues;
|
|
34563
|
+
}
|
|
34564
|
+
});
|
|
34565
|
+
|
|
34566
|
+
// src/rules/dead/unused-import.ts
|
|
34567
|
+
var unusedImportRule = createRule({
|
|
34568
|
+
id: "dead/unused-import",
|
|
34569
|
+
category: "logic",
|
|
34570
|
+
severity: "low",
|
|
34571
|
+
aiSpecific: true,
|
|
34572
|
+
description: "ES module import is never referenced in the file",
|
|
34573
|
+
create(_context) {
|
|
34574
|
+
return {};
|
|
34575
|
+
},
|
|
34576
|
+
analyze(_context, facts) {
|
|
34577
|
+
const issues = [];
|
|
34578
|
+
if (!facts.v2) return issues;
|
|
34579
|
+
for (const binding of facts.v2.deadCode.bindings) {
|
|
34580
|
+
if (binding.kind !== "import-specifier" && binding.kind !== "import-default" && binding.kind !== "import-namespace") {
|
|
34581
|
+
continue;
|
|
34582
|
+
}
|
|
34583
|
+
if (binding.isReferenced) continue;
|
|
34584
|
+
if (!binding.name) continue;
|
|
34585
|
+
const source = binding.source ? ` from '${binding.source}'` : "";
|
|
34586
|
+
issues.push({
|
|
34587
|
+
ruleId: "dead/unused-import",
|
|
34588
|
+
category: "logic",
|
|
34589
|
+
severity: "low",
|
|
34590
|
+
aiSpecific: true,
|
|
34591
|
+
message: `Unused import: '${binding.name}'${source}`,
|
|
34592
|
+
line: binding.line,
|
|
34593
|
+
column: binding.column,
|
|
34594
|
+
advice: `Remove the import or use '${binding.name}' somewhere in the file. This is the most common AI-iteration rot \u2014 the model added the import when it introduced a feature, then rewrote the function without cleaning up.`
|
|
34595
|
+
});
|
|
34596
|
+
}
|
|
34597
|
+
return issues;
|
|
34598
|
+
}
|
|
34599
|
+
});
|
|
34600
|
+
|
|
34601
|
+
// src/rules/dead/unused-local.ts
|
|
34602
|
+
var SKIP_NAMES = /* @__PURE__ */ new Set(["React", "_"]);
|
|
34603
|
+
var unusedLocalRule = createRule({
|
|
34604
|
+
id: "dead/unused-local",
|
|
34605
|
+
category: "logic",
|
|
34606
|
+
severity: "low",
|
|
34607
|
+
aiSpecific: true,
|
|
34608
|
+
description: "Variable is declared but never read after declaration",
|
|
34609
|
+
create(_context) {
|
|
34610
|
+
return {};
|
|
34611
|
+
},
|
|
34612
|
+
analyze(_context, facts) {
|
|
34613
|
+
const issues = [];
|
|
34614
|
+
if (!facts.v2) return issues;
|
|
34615
|
+
for (const binding of facts.v2.deadCode.bindings) {
|
|
34616
|
+
if (binding.kind !== "var" && binding.kind !== "let" && binding.kind !== "const" && binding.kind !== "function" && binding.kind !== "class" && binding.kind !== "type" && binding.kind !== "interface" && binding.kind !== "enum") {
|
|
34617
|
+
continue;
|
|
34618
|
+
}
|
|
34619
|
+
if (binding.isReferenced) continue;
|
|
34620
|
+
if (SKIP_NAMES.has(binding.name)) continue;
|
|
34621
|
+
if (binding.name.startsWith("_")) continue;
|
|
34622
|
+
issues.push({
|
|
34623
|
+
ruleId: "dead/unused-local",
|
|
34624
|
+
category: "logic",
|
|
34625
|
+
severity: "low",
|
|
34626
|
+
aiSpecific: true,
|
|
34627
|
+
message: `Unused ${binding.kind}: '${binding.name}'`,
|
|
34628
|
+
line: binding.line,
|
|
34629
|
+
column: binding.column,
|
|
34630
|
+
advice: `Remove the declaration or use '${binding.name}' somewhere in the file. This is the second-most-common AI-iteration signature \u2014 the model declared the binding when it introduced a feature, then rewrote the function without cleaning up.`
|
|
34631
|
+
});
|
|
34632
|
+
}
|
|
34633
|
+
return issues;
|
|
34634
|
+
}
|
|
34635
|
+
});
|
|
34636
|
+
|
|
34637
|
+
// src/rules/dead/unused-parameter.ts
|
|
34638
|
+
var unusedParameterRule = createRule({
|
|
34639
|
+
id: "dead/unused-parameter",
|
|
34640
|
+
category: "logic",
|
|
34641
|
+
severity: "low",
|
|
34642
|
+
aiSpecific: true,
|
|
34643
|
+
description: "Function parameter is declared but never read",
|
|
34644
|
+
create(_context) {
|
|
34645
|
+
return {};
|
|
34646
|
+
},
|
|
34647
|
+
analyze(_context, facts) {
|
|
34648
|
+
const issues = [];
|
|
34649
|
+
if (!facts.v2) return issues;
|
|
34650
|
+
for (const binding of facts.v2.deadCode.bindings) {
|
|
34651
|
+
if (binding.kind !== "parameter") continue;
|
|
34652
|
+
if (binding.isReferenced) continue;
|
|
34653
|
+
if (binding.name.startsWith("_")) continue;
|
|
34654
|
+
if (binding.name === "props") continue;
|
|
34655
|
+
issues.push({
|
|
34656
|
+
ruleId: "dead/unused-parameter",
|
|
34657
|
+
category: "logic",
|
|
34658
|
+
severity: "low",
|
|
34659
|
+
aiSpecific: true,
|
|
34660
|
+
message: `Unused parameter: '${binding.name}'`,
|
|
34661
|
+
line: binding.line,
|
|
34662
|
+
column: binding.column,
|
|
34663
|
+
advice: `Remove the parameter (and update every call site) or use '${binding.name}' in the function body. This is the AI-iteration signature: the model added the parameter when it introduced a feature, then rewrote the function without removing parameters the new code does not need.`
|
|
34664
|
+
});
|
|
34665
|
+
}
|
|
34666
|
+
return issues;
|
|
34667
|
+
}
|
|
34668
|
+
});
|
|
34669
|
+
|
|
34309
34670
|
// src/rules/docs/broken-link.ts
|
|
34310
34671
|
var import_node_fs6 = require("fs");
|
|
34311
34672
|
var import_node_path6 = require("path");
|
|
@@ -39598,6 +39959,11 @@ var builtinRules = [
|
|
|
39598
39959
|
missingNotNullRule,
|
|
39599
39960
|
namingInconsistencyRule,
|
|
39600
39961
|
sqlConcatRule,
|
|
39962
|
+
deadBranchRule,
|
|
39963
|
+
unreachableRule,
|
|
39964
|
+
unusedImportRule,
|
|
39965
|
+
unusedLocalRule,
|
|
39966
|
+
unusedParameterRule,
|
|
39601
39967
|
brokenLinkRule,
|
|
39602
39968
|
expiredCodeExampleRule,
|
|
39603
39969
|
staleFunctionReferenceRule,
|
|
@@ -40605,6 +40971,61 @@ var signal_strength_default = {
|
|
|
40605
40971
|
_calibrationNote: "v0.17.0 ship \u2014 not in v7 per-rule table. Default-off until calibration data lands. Backed by: OWASP Foundation (2021), *OWASP Top 10 \u2014 A03:2021 Injection*, https://owasp.org/Top10/A03_2021-Injection/; OWASP Foundation (2017), *SQL Injection Prevention Cheat Sheet*. (Template-literal SQL with ${...} interpolation is the #1 SQL injection vector in AI-generated TypeScript code.)",
|
|
40606
40972
|
aiSpecific: true
|
|
40607
40973
|
},
|
|
40974
|
+
"dead/unused-import": {
|
|
40975
|
+
recall: 0,
|
|
40976
|
+
fpRate: 0,
|
|
40977
|
+
ratio: 0,
|
|
40978
|
+
precision: 0,
|
|
40979
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
40980
|
+
verdict: "DORMANT",
|
|
40981
|
+
defaultOff: true,
|
|
40982
|
+
_calibrationNote: "v0.18.5 ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The first of 5 planned `dead/*` rules (this one + dead/unused-local + dead/unused-parameter + dead/dead-branch + dead/unreachable). The pattern is the canonical AI-iteration rot: the model adds an import when introducing a feature, then rewrites the function later without cleaning up. Most real-world tsconfig.json files have `noUnusedLocals: false`, so tsc never fires.",
|
|
40983
|
+
aiSpecific: true
|
|
40984
|
+
},
|
|
40985
|
+
"dead/unused-local": {
|
|
40986
|
+
recall: 0,
|
|
40987
|
+
fpRate: 0,
|
|
40988
|
+
ratio: 0,
|
|
40989
|
+
precision: 0,
|
|
40990
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
40991
|
+
verdict: "DORMANT",
|
|
40992
|
+
defaultOff: true,
|
|
40993
|
+
_calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The second of 5 planned `dead/*` rules. Muchnick 1997 Ch. 13 'liveness analysis' (textbook compiler optimization).",
|
|
40994
|
+
aiSpecific: true
|
|
40995
|
+
},
|
|
40996
|
+
"dead/unused-parameter": {
|
|
40997
|
+
recall: 0,
|
|
40998
|
+
fpRate: 0,
|
|
40999
|
+
ratio: 0,
|
|
41000
|
+
precision: 0,
|
|
41001
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41002
|
+
verdict: "DORMANT",
|
|
41003
|
+
defaultOff: true,
|
|
41004
|
+
_calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The third of 5 planned `dead/*` rules. AI agents add parameters when introducing features, then rewrite the function without removing parameters the new code does not need.",
|
|
41005
|
+
aiSpecific: true
|
|
41006
|
+
},
|
|
41007
|
+
"dead/dead-branch": {
|
|
41008
|
+
recall: 0,
|
|
41009
|
+
fpRate: 0,
|
|
41010
|
+
ratio: 0,
|
|
41011
|
+
precision: 0,
|
|
41012
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41013
|
+
verdict: "DORMANT",
|
|
41014
|
+
defaultOff: true,
|
|
41015
|
+
_calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The fourth of 5 planned `dead/*` rules. AI-iteration signature: feature flag toggled to a constant, or wrapper from a previous refactor.",
|
|
41016
|
+
aiSpecific: true
|
|
41017
|
+
},
|
|
41018
|
+
"dead/unreachable": {
|
|
41019
|
+
recall: 0,
|
|
41020
|
+
fpRate: 0,
|
|
41021
|
+
ratio: 0,
|
|
41022
|
+
precision: 0,
|
|
41023
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41024
|
+
verdict: "DORMANT",
|
|
41025
|
+
defaultOff: true,
|
|
41026
|
+
_calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The fifth of 5 planned `dead/*` rules. AI-iteration signature: model added an early return for a new error path, then forgot the rest of the function body was still sitting below it.",
|
|
41027
|
+
aiSpecific: true
|
|
41028
|
+
},
|
|
40608
41029
|
"docs/stale-package-reference": {
|
|
40609
41030
|
recall: 0,
|
|
40610
41031
|
fpRate: 0,
|