slopbrick 0.18.4 → 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 +596 -76
- package/dist/index.d.cts +611 -511
- package/dist/index.d.ts +611 -511
- package/dist/index.js +595 -75
- package/package.json +1 -1
package/dist/engine/worker.js
CHANGED
|
@@ -6316,6 +6316,78 @@ function extractStateBinding(node, lineOffsets) {
|
|
|
6316
6316
|
setterReferenced: false
|
|
6317
6317
|
};
|
|
6318
6318
|
}
|
|
6319
|
+
function findUnreachableStatements(ast, source, lineOffsets) {
|
|
6320
|
+
const out = [];
|
|
6321
|
+
function isTerminator(node) {
|
|
6322
|
+
if (node.type === "ReturnStatement") return "return";
|
|
6323
|
+
if (node.type === "ThrowStatement") return "throw";
|
|
6324
|
+
if (node.type === "BreakStatement") return "break";
|
|
6325
|
+
if (node.type === "ContinueStatement") return "continue";
|
|
6326
|
+
return null;
|
|
6327
|
+
}
|
|
6328
|
+
function snippetFor(node, src) {
|
|
6329
|
+
const span = node.span;
|
|
6330
|
+
if (!span || typeof span.start !== "number" || typeof span.end !== "number") {
|
|
6331
|
+
return "<unreachable>";
|
|
6332
|
+
}
|
|
6333
|
+
const text = src.slice(span.start, Math.min(span.end, span.start + 60));
|
|
6334
|
+
return text.replace(/\s+/g, " ").trim() || "<unreachable>";
|
|
6335
|
+
}
|
|
6336
|
+
function lineColumn(offset) {
|
|
6337
|
+
let lo = 0;
|
|
6338
|
+
let hi = lineOffsets.length - 1;
|
|
6339
|
+
while (lo < hi) {
|
|
6340
|
+
const mid = lo + hi + 1 >>> 1;
|
|
6341
|
+
const midVal = lineOffsets[mid];
|
|
6342
|
+
const loVal = lineOffsets[lo];
|
|
6343
|
+
if (midVal === void 0) break;
|
|
6344
|
+
if (midVal <= offset) lo = mid;
|
|
6345
|
+
else hi = mid - 1;
|
|
6346
|
+
void loVal;
|
|
6347
|
+
}
|
|
6348
|
+
const baseOffset = lineOffsets[lo] ?? 0;
|
|
6349
|
+
return { line: lo + 1, column: offset - baseOffset };
|
|
6350
|
+
}
|
|
6351
|
+
function visitBody(body) {
|
|
6352
|
+
if (!Array.isArray(body)) return;
|
|
6353
|
+
let lastTerminator = null;
|
|
6354
|
+
for (const stmt of body) {
|
|
6355
|
+
if (!isObject(stmt)) continue;
|
|
6356
|
+
if (lastTerminator && stmt.type !== "EmptyStatement") {
|
|
6357
|
+
const span = stmt.span;
|
|
6358
|
+
const offset = typeof span?.start === "number" ? span.start : 0;
|
|
6359
|
+
const { line, column } = lineColumn(offset);
|
|
6360
|
+
out.push({
|
|
6361
|
+
terminator: lastTerminator,
|
|
6362
|
+
line,
|
|
6363
|
+
column,
|
|
6364
|
+
snippet: snippetFor(stmt, source)
|
|
6365
|
+
});
|
|
6366
|
+
}
|
|
6367
|
+
const t = isTerminator(stmt);
|
|
6368
|
+
if (t) lastTerminator = t;
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6371
|
+
function walk3(node) {
|
|
6372
|
+
if (!isObject(node)) return;
|
|
6373
|
+
if (node.type === "BlockStatement") {
|
|
6374
|
+
visitBody(node.stmts);
|
|
6375
|
+
} else if (node.type === "Module") {
|
|
6376
|
+
visitBody(node.body);
|
|
6377
|
+
}
|
|
6378
|
+
for (const key of Object.keys(node)) {
|
|
6379
|
+
if (key === "parent" || key === "span" || key === "ctxt") continue;
|
|
6380
|
+
const child = node[key];
|
|
6381
|
+
if (Array.isArray(child)) {
|
|
6382
|
+
for (const c of child) walk3(c);
|
|
6383
|
+
} else {
|
|
6384
|
+
walk3(child);
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
walk3(ast);
|
|
6389
|
+
return out;
|
|
6390
|
+
}
|
|
6319
6391
|
|
|
6320
6392
|
// src/engine/visitors/dispatch.ts
|
|
6321
6393
|
function nearestComponent(vctx) {
|
|
@@ -6392,15 +6464,42 @@ function handleImportDeclaration(node, _parent, _path, vctx) {
|
|
|
6392
6464
|
if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
|
|
6393
6465
|
const local = specifier.local;
|
|
6394
6466
|
if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
|
|
6395
|
-
|
|
6467
|
+
const name = local.value;
|
|
6468
|
+
importedNames.push(name);
|
|
6469
|
+
vctx.facts.deadCode.bindings.push({
|
|
6470
|
+
name,
|
|
6471
|
+
kind: specifier.type === "ImportDefaultSpecifier" ? "import-default" : "import-namespace",
|
|
6472
|
+
line,
|
|
6473
|
+
column,
|
|
6474
|
+
source,
|
|
6475
|
+
isReferenced: false
|
|
6476
|
+
});
|
|
6396
6477
|
}
|
|
6397
6478
|
} else if (specifier.type === "ImportSpecifier") {
|
|
6398
6479
|
const imported = specifier.imported;
|
|
6399
6480
|
const local = specifier.local;
|
|
6400
6481
|
if (isObject(imported) && typeof imported.value === "string" && imported.value.length > 0) {
|
|
6401
|
-
|
|
6482
|
+
const name = imported.value;
|
|
6483
|
+
importedNames.push(name);
|
|
6484
|
+
vctx.facts.deadCode.bindings.push({
|
|
6485
|
+
name,
|
|
6486
|
+
kind: "import-specifier",
|
|
6487
|
+
line,
|
|
6488
|
+
column,
|
|
6489
|
+
source,
|
|
6490
|
+
isReferenced: false
|
|
6491
|
+
});
|
|
6402
6492
|
} else if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
|
|
6403
|
-
|
|
6493
|
+
const name = local.value;
|
|
6494
|
+
importedNames.push(name);
|
|
6495
|
+
vctx.facts.deadCode.bindings.push({
|
|
6496
|
+
name,
|
|
6497
|
+
kind: "import-specifier",
|
|
6498
|
+
line,
|
|
6499
|
+
column,
|
|
6500
|
+
source,
|
|
6501
|
+
isReferenced: false
|
|
6502
|
+
});
|
|
6404
6503
|
}
|
|
6405
6504
|
}
|
|
6406
6505
|
}
|
|
@@ -6490,6 +6589,13 @@ function isBindingSite(node, parent) {
|
|
|
6490
6589
|
if (params.some((param) => containsNode(param, node))) return true;
|
|
6491
6590
|
}
|
|
6492
6591
|
}
|
|
6592
|
+
if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") {
|
|
6593
|
+
if (parent.type === "ImportSpecifier") {
|
|
6594
|
+
if (parent.imported === node || parent.local === node) return true;
|
|
6595
|
+
} else {
|
|
6596
|
+
if (parent.local === node) return true;
|
|
6597
|
+
}
|
|
6598
|
+
}
|
|
6493
6599
|
return false;
|
|
6494
6600
|
}
|
|
6495
6601
|
function isNonComputedMemberProperty(node, parent) {
|
|
@@ -6556,6 +6662,9 @@ function handleIdentifier(node, parent, path, vctx) {
|
|
|
6556
6662
|
if (typeof node.value === "string" && !isBindingSite(node, parent) && !isNonComputedMemberProperty(node, parent)) {
|
|
6557
6663
|
markStateReference(node.value, vctx);
|
|
6558
6664
|
trackPropUsage(node, parent, path, vctx);
|
|
6665
|
+
vctx.facts.referencedNames.add(node.value);
|
|
6666
|
+
const top = vctx.ctx.stack[vctx.ctx.stack.length - 1];
|
|
6667
|
+
if (top) top.references.add(node.value);
|
|
6559
6668
|
}
|
|
6560
6669
|
return false;
|
|
6561
6670
|
}
|
|
@@ -6696,6 +6805,20 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
|
|
|
6696
6805
|
frame.bindings.add(bindingName);
|
|
6697
6806
|
}
|
|
6698
6807
|
}
|
|
6808
|
+
if (bindingNames.length > 0) {
|
|
6809
|
+
const parent = node.parent;
|
|
6810
|
+
const kind = isObject(parent) && parent.type === "VariableDeclaration" ? String(parent.kind ?? "var") : "var";
|
|
6811
|
+
const { line, column } = positionFrom(id, vctx.lineOffsets);
|
|
6812
|
+
for (const bindingName of bindingNames) {
|
|
6813
|
+
vctx.facts.deadCode.bindings.push({
|
|
6814
|
+
name: bindingName,
|
|
6815
|
+
kind,
|
|
6816
|
+
line,
|
|
6817
|
+
column,
|
|
6818
|
+
isReferenced: false
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6699
6822
|
if (isUseStateDeclarator(node)) {
|
|
6700
6823
|
const binding = extractStateBinding(node, vctx.lineOffsets);
|
|
6701
6824
|
if (binding) {
|
|
@@ -6715,6 +6838,36 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
|
|
|
6715
6838
|
}
|
|
6716
6839
|
return true;
|
|
6717
6840
|
}
|
|
6841
|
+
function handleIfStatement(node, _parent, _path, vctx) {
|
|
6842
|
+
if (!isObject(node)) return false;
|
|
6843
|
+
const test = node.test;
|
|
6844
|
+
if (!isObject(test)) return false;
|
|
6845
|
+
if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
|
|
6846
|
+
const { line, column } = positionFrom(test, vctx.lineOffsets);
|
|
6847
|
+
vctx.facts.deadCode.constantConditions.push({
|
|
6848
|
+
kind: test.value ? "if-true" : "if-false",
|
|
6849
|
+
condition: String(test.value),
|
|
6850
|
+
line,
|
|
6851
|
+
column
|
|
6852
|
+
});
|
|
6853
|
+
}
|
|
6854
|
+
return false;
|
|
6855
|
+
}
|
|
6856
|
+
function handleWhileStatement(node, _parent, _path, vctx) {
|
|
6857
|
+
if (!isObject(node)) return false;
|
|
6858
|
+
const test = node.test;
|
|
6859
|
+
if (!isObject(test)) return false;
|
|
6860
|
+
if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
|
|
6861
|
+
const { line, column } = positionFrom(test, vctx.lineOffsets);
|
|
6862
|
+
vctx.facts.deadCode.constantConditions.push({
|
|
6863
|
+
kind: test.value ? "while-true" : "while-false",
|
|
6864
|
+
condition: String(test.value),
|
|
6865
|
+
line,
|
|
6866
|
+
column
|
|
6867
|
+
});
|
|
6868
|
+
}
|
|
6869
|
+
return false;
|
|
6870
|
+
}
|
|
6718
6871
|
var HANDLERS = {
|
|
6719
6872
|
ExpressionStatement: handleExpressionStatement,
|
|
6720
6873
|
ImportDeclaration: handleImportDeclaration,
|
|
@@ -6724,7 +6877,9 @@ var HANDLERS = {
|
|
|
6724
6877
|
MemberExpression: handleMemberExpression,
|
|
6725
6878
|
JSXAttribute: handleJSXAttribute,
|
|
6726
6879
|
JSXOpeningElement: handleJSXOpeningElement,
|
|
6727
|
-
VariableDeclarator: handleVariableDeclarator
|
|
6880
|
+
VariableDeclarator: handleVariableDeclarator,
|
|
6881
|
+
IfStatement: handleIfStatement,
|
|
6882
|
+
WhileStatement: handleWhileStatement
|
|
6728
6883
|
};
|
|
6729
6884
|
function dispatchNode(node, parent, path, vctx) {
|
|
6730
6885
|
if (!isObject(node)) return false;
|
|
@@ -6889,6 +7044,17 @@ function buildV2Facts(facts, source, ext, framework, config, templateClassNames
|
|
|
6889
7044
|
},
|
|
6890
7045
|
logic: buildLogicBlock(facts),
|
|
6891
7046
|
designTokens: scanDesignTokens(facts.staticClassNames),
|
|
7047
|
+
// dead-code detector. Copy the internal accumulator
|
|
7048
|
+
// into the v2 shape, marking each binding as referenced
|
|
7049
|
+
// iff the file-level referenced-name set contains its name.
|
|
7050
|
+
deadCode: {
|
|
7051
|
+
bindings: facts.deadCode.bindings.map((b) => ({
|
|
7052
|
+
...b,
|
|
7053
|
+
isReferenced: facts.referencedNames.has(b.name)
|
|
7054
|
+
})),
|
|
7055
|
+
constantConditions: facts.deadCode.constantConditions,
|
|
7056
|
+
unreachableStatements: facts.deadCode.unreachableStatements
|
|
7057
|
+
},
|
|
6892
7058
|
componentSizes: facts.componentSizes.map((cs) => ({
|
|
6893
7059
|
name: cs.name,
|
|
6894
7060
|
lineCount: cs.lineCount,
|
|
@@ -6956,7 +7122,17 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
6956
7122
|
componentSizes: [],
|
|
6957
7123
|
astroComponents: [],
|
|
6958
7124
|
fetchCalls: [],
|
|
6959
|
-
optimisticUpdates: []
|
|
7125
|
+
optimisticUpdates: [],
|
|
7126
|
+
// dead-code detector. The visitor's identifier walk + import/
|
|
7127
|
+
// branch/return handlers populate these. The v2 builder at the
|
|
7128
|
+
// bottom of extractFacts() reads them and produces
|
|
7129
|
+
// `facts.v2.deadCode`.
|
|
7130
|
+
deadCode: {
|
|
7131
|
+
bindings: [],
|
|
7132
|
+
constantConditions: [],
|
|
7133
|
+
unreachableStatements: []
|
|
7134
|
+
},
|
|
7135
|
+
referencedNames: /* @__PURE__ */ new Set()
|
|
6960
7136
|
};
|
|
6961
7137
|
const ctx = {
|
|
6962
7138
|
stack: [],
|
|
@@ -7043,6 +7219,16 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
7043
7219
|
propBindingSet.add(bindingName);
|
|
7044
7220
|
}
|
|
7045
7221
|
}
|
|
7222
|
+
const { line: pLine, column: pCol } = positionFrom(param, lineOffsets);
|
|
7223
|
+
for (const bindingName of collectBindingNames2(param)) {
|
|
7224
|
+
facts.deadCode.bindings.push({
|
|
7225
|
+
name: bindingName,
|
|
7226
|
+
kind: "parameter",
|
|
7227
|
+
line: pLine,
|
|
7228
|
+
column: pCol,
|
|
7229
|
+
isReferenced: false
|
|
7230
|
+
});
|
|
7231
|
+
}
|
|
7046
7232
|
}
|
|
7047
7233
|
}
|
|
7048
7234
|
ctx.stack.push({
|
|
@@ -7058,6 +7244,12 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
7058
7244
|
propUsages: [],
|
|
7059
7245
|
isComponent,
|
|
7060
7246
|
bindings,
|
|
7247
|
+
// dead-code detector: per-frame referenced-name set.
|
|
7248
|
+
// Identifiers encountered inside the frame are added to this
|
|
7249
|
+
// set; the deadCode builder unions it with parent frames at
|
|
7250
|
+
// pop time so a binding is considered used if any reachable
|
|
7251
|
+
// scope references it.
|
|
7252
|
+
references: /* @__PURE__ */ new Set(),
|
|
7061
7253
|
propBindingSet,
|
|
7062
7254
|
propUsageSet: /* @__PURE__ */ new Set(),
|
|
7063
7255
|
node
|
|
@@ -7153,6 +7345,11 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
|
|
|
7153
7345
|
mergeTemplateClassNames(filePath, source, facts, templateClassNames);
|
|
7154
7346
|
const { ext } = splitFilePath(filePath);
|
|
7155
7347
|
facts._source = source;
|
|
7348
|
+
facts.deadCode.unreachableStatements = findUnreachableStatements(
|
|
7349
|
+
ast,
|
|
7350
|
+
source,
|
|
7351
|
+
lineOffsets
|
|
7352
|
+
);
|
|
7156
7353
|
const v2 = buildV2Facts(facts, source, ext, framework, config, templateClassNames);
|
|
7157
7354
|
return envelopeScanFacts(filePath, v2);
|
|
7158
7355
|
}
|
|
@@ -34287,6 +34484,170 @@ var sqlConcatRule = createRule({
|
|
|
34287
34484
|
}
|
|
34288
34485
|
});
|
|
34289
34486
|
|
|
34487
|
+
// src/rules/dead/dead-branch.ts
|
|
34488
|
+
var deadBranchRule = createRule({
|
|
34489
|
+
id: "dead/dead-branch",
|
|
34490
|
+
category: "logic",
|
|
34491
|
+
severity: "medium",
|
|
34492
|
+
aiSpecific: true,
|
|
34493
|
+
description: "Literal boolean condition makes one branch statically dead",
|
|
34494
|
+
create(_context) {
|
|
34495
|
+
return {};
|
|
34496
|
+
},
|
|
34497
|
+
analyze(_context, facts) {
|
|
34498
|
+
const issues = [];
|
|
34499
|
+
if (!facts.v2) return issues;
|
|
34500
|
+
for (const cond of facts.v2.deadCode.constantConditions) {
|
|
34501
|
+
const isWhileTrue = cond.kind === "while-true";
|
|
34502
|
+
issues.push({
|
|
34503
|
+
ruleId: "dead/dead-branch",
|
|
34504
|
+
category: "logic",
|
|
34505
|
+
severity: isWhileTrue ? "low" : "medium",
|
|
34506
|
+
aiSpecific: true,
|
|
34507
|
+
message: isWhileTrue ? `Infinite loop with literal condition (${cond.kind})` : `Dead branch: condition is always ${cond.condition}`,
|
|
34508
|
+
line: cond.line,
|
|
34509
|
+
column: cond.column,
|
|
34510
|
+
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.`
|
|
34511
|
+
});
|
|
34512
|
+
}
|
|
34513
|
+
return issues;
|
|
34514
|
+
}
|
|
34515
|
+
});
|
|
34516
|
+
|
|
34517
|
+
// src/rules/dead/unreachable.ts
|
|
34518
|
+
var unreachableRule = createRule({
|
|
34519
|
+
id: "dead/unreachable",
|
|
34520
|
+
category: "logic",
|
|
34521
|
+
severity: "high",
|
|
34522
|
+
aiSpecific: true,
|
|
34523
|
+
description: "Statement is unreachable after an unconditional return/throw/break/continue",
|
|
34524
|
+
create(_context) {
|
|
34525
|
+
return {};
|
|
34526
|
+
},
|
|
34527
|
+
analyze(_context, facts) {
|
|
34528
|
+
const issues = [];
|
|
34529
|
+
if (!facts.v2) return issues;
|
|
34530
|
+
for (const u of facts.v2.deadCode.unreachableStatements) {
|
|
34531
|
+
if (u.snippet === "<unreachable>") continue;
|
|
34532
|
+
issues.push({
|
|
34533
|
+
ruleId: "dead/unreachable",
|
|
34534
|
+
category: "logic",
|
|
34535
|
+
severity: "high",
|
|
34536
|
+
aiSpecific: true,
|
|
34537
|
+
message: `Unreachable after ${u.terminator}: ${u.snippet}`,
|
|
34538
|
+
line: u.line,
|
|
34539
|
+
column: u.column,
|
|
34540
|
+
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.`
|
|
34541
|
+
});
|
|
34542
|
+
}
|
|
34543
|
+
return issues;
|
|
34544
|
+
}
|
|
34545
|
+
});
|
|
34546
|
+
|
|
34547
|
+
// src/rules/dead/unused-import.ts
|
|
34548
|
+
var unusedImportRule = createRule({
|
|
34549
|
+
id: "dead/unused-import",
|
|
34550
|
+
category: "logic",
|
|
34551
|
+
severity: "low",
|
|
34552
|
+
aiSpecific: true,
|
|
34553
|
+
description: "ES module import is never referenced in the file",
|
|
34554
|
+
create(_context) {
|
|
34555
|
+
return {};
|
|
34556
|
+
},
|
|
34557
|
+
analyze(_context, facts) {
|
|
34558
|
+
const issues = [];
|
|
34559
|
+
if (!facts.v2) return issues;
|
|
34560
|
+
for (const binding of facts.v2.deadCode.bindings) {
|
|
34561
|
+
if (binding.kind !== "import-specifier" && binding.kind !== "import-default" && binding.kind !== "import-namespace") {
|
|
34562
|
+
continue;
|
|
34563
|
+
}
|
|
34564
|
+
if (binding.isReferenced) continue;
|
|
34565
|
+
if (!binding.name) continue;
|
|
34566
|
+
const source = binding.source ? ` from '${binding.source}'` : "";
|
|
34567
|
+
issues.push({
|
|
34568
|
+
ruleId: "dead/unused-import",
|
|
34569
|
+
category: "logic",
|
|
34570
|
+
severity: "low",
|
|
34571
|
+
aiSpecific: true,
|
|
34572
|
+
message: `Unused import: '${binding.name}'${source}`,
|
|
34573
|
+
line: binding.line,
|
|
34574
|
+
column: binding.column,
|
|
34575
|
+
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.`
|
|
34576
|
+
});
|
|
34577
|
+
}
|
|
34578
|
+
return issues;
|
|
34579
|
+
}
|
|
34580
|
+
});
|
|
34581
|
+
|
|
34582
|
+
// src/rules/dead/unused-local.ts
|
|
34583
|
+
var SKIP_NAMES = /* @__PURE__ */ new Set(["React", "_"]);
|
|
34584
|
+
var unusedLocalRule = createRule({
|
|
34585
|
+
id: "dead/unused-local",
|
|
34586
|
+
category: "logic",
|
|
34587
|
+
severity: "low",
|
|
34588
|
+
aiSpecific: true,
|
|
34589
|
+
description: "Variable is declared but never read after declaration",
|
|
34590
|
+
create(_context) {
|
|
34591
|
+
return {};
|
|
34592
|
+
},
|
|
34593
|
+
analyze(_context, facts) {
|
|
34594
|
+
const issues = [];
|
|
34595
|
+
if (!facts.v2) return issues;
|
|
34596
|
+
for (const binding of facts.v2.deadCode.bindings) {
|
|
34597
|
+
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") {
|
|
34598
|
+
continue;
|
|
34599
|
+
}
|
|
34600
|
+
if (binding.isReferenced) continue;
|
|
34601
|
+
if (SKIP_NAMES.has(binding.name)) continue;
|
|
34602
|
+
if (binding.name.startsWith("_")) continue;
|
|
34603
|
+
issues.push({
|
|
34604
|
+
ruleId: "dead/unused-local",
|
|
34605
|
+
category: "logic",
|
|
34606
|
+
severity: "low",
|
|
34607
|
+
aiSpecific: true,
|
|
34608
|
+
message: `Unused ${binding.kind}: '${binding.name}'`,
|
|
34609
|
+
line: binding.line,
|
|
34610
|
+
column: binding.column,
|
|
34611
|
+
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.`
|
|
34612
|
+
});
|
|
34613
|
+
}
|
|
34614
|
+
return issues;
|
|
34615
|
+
}
|
|
34616
|
+
});
|
|
34617
|
+
|
|
34618
|
+
// src/rules/dead/unused-parameter.ts
|
|
34619
|
+
var unusedParameterRule = createRule({
|
|
34620
|
+
id: "dead/unused-parameter",
|
|
34621
|
+
category: "logic",
|
|
34622
|
+
severity: "low",
|
|
34623
|
+
aiSpecific: true,
|
|
34624
|
+
description: "Function parameter is declared but never read",
|
|
34625
|
+
create(_context) {
|
|
34626
|
+
return {};
|
|
34627
|
+
},
|
|
34628
|
+
analyze(_context, facts) {
|
|
34629
|
+
const issues = [];
|
|
34630
|
+
if (!facts.v2) return issues;
|
|
34631
|
+
for (const binding of facts.v2.deadCode.bindings) {
|
|
34632
|
+
if (binding.kind !== "parameter") continue;
|
|
34633
|
+
if (binding.isReferenced) continue;
|
|
34634
|
+
if (binding.name.startsWith("_")) continue;
|
|
34635
|
+
if (binding.name === "props") continue;
|
|
34636
|
+
issues.push({
|
|
34637
|
+
ruleId: "dead/unused-parameter",
|
|
34638
|
+
category: "logic",
|
|
34639
|
+
severity: "low",
|
|
34640
|
+
aiSpecific: true,
|
|
34641
|
+
message: `Unused parameter: '${binding.name}'`,
|
|
34642
|
+
line: binding.line,
|
|
34643
|
+
column: binding.column,
|
|
34644
|
+
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.`
|
|
34645
|
+
});
|
|
34646
|
+
}
|
|
34647
|
+
return issues;
|
|
34648
|
+
}
|
|
34649
|
+
});
|
|
34650
|
+
|
|
34290
34651
|
// src/rules/docs/broken-link.ts
|
|
34291
34652
|
import { existsSync as existsSync4 } from "fs";
|
|
34292
34653
|
import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
|
|
@@ -39579,6 +39940,11 @@ var builtinRules = [
|
|
|
39579
39940
|
missingNotNullRule,
|
|
39580
39941
|
namingInconsistencyRule,
|
|
39581
39942
|
sqlConcatRule,
|
|
39943
|
+
deadBranchRule,
|
|
39944
|
+
unreachableRule,
|
|
39945
|
+
unusedImportRule,
|
|
39946
|
+
unusedLocalRule,
|
|
39947
|
+
unusedParameterRule,
|
|
39582
39948
|
brokenLinkRule,
|
|
39583
39949
|
expiredCodeExampleRule,
|
|
39584
39950
|
staleFunctionReferenceRule,
|
|
@@ -40586,6 +40952,61 @@ var signal_strength_default = {
|
|
|
40586
40952
|
_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.)",
|
|
40587
40953
|
aiSpecific: true
|
|
40588
40954
|
},
|
|
40955
|
+
"dead/unused-import": {
|
|
40956
|
+
recall: 0,
|
|
40957
|
+
fpRate: 0,
|
|
40958
|
+
ratio: 0,
|
|
40959
|
+
precision: 0,
|
|
40960
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
40961
|
+
verdict: "DORMANT",
|
|
40962
|
+
defaultOff: true,
|
|
40963
|
+
_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.",
|
|
40964
|
+
aiSpecific: true
|
|
40965
|
+
},
|
|
40966
|
+
"dead/unused-local": {
|
|
40967
|
+
recall: 0,
|
|
40968
|
+
fpRate: 0,
|
|
40969
|
+
ratio: 0,
|
|
40970
|
+
precision: 0,
|
|
40971
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
40972
|
+
verdict: "DORMANT",
|
|
40973
|
+
defaultOff: true,
|
|
40974
|
+
_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).",
|
|
40975
|
+
aiSpecific: true
|
|
40976
|
+
},
|
|
40977
|
+
"dead/unused-parameter": {
|
|
40978
|
+
recall: 0,
|
|
40979
|
+
fpRate: 0,
|
|
40980
|
+
ratio: 0,
|
|
40981
|
+
precision: 0,
|
|
40982
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
40983
|
+
verdict: "DORMANT",
|
|
40984
|
+
defaultOff: true,
|
|
40985
|
+
_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.",
|
|
40986
|
+
aiSpecific: true
|
|
40987
|
+
},
|
|
40988
|
+
"dead/dead-branch": {
|
|
40989
|
+
recall: 0,
|
|
40990
|
+
fpRate: 0,
|
|
40991
|
+
ratio: 0,
|
|
40992
|
+
precision: 0,
|
|
40993
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
40994
|
+
verdict: "DORMANT",
|
|
40995
|
+
defaultOff: true,
|
|
40996
|
+
_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.",
|
|
40997
|
+
aiSpecific: true
|
|
40998
|
+
},
|
|
40999
|
+
"dead/unreachable": {
|
|
41000
|
+
recall: 0,
|
|
41001
|
+
fpRate: 0,
|
|
41002
|
+
ratio: 0,
|
|
41003
|
+
precision: 0,
|
|
41004
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41005
|
+
verdict: "DORMANT",
|
|
41006
|
+
defaultOff: true,
|
|
41007
|
+
_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.",
|
|
41008
|
+
aiSpecific: true
|
|
41009
|
+
},
|
|
40589
41010
|
"docs/stale-package-reference": {
|
|
40590
41011
|
recall: 0,
|
|
40591
41012
|
fpRate: 0,
|