slopbrick 0.18.4 → 0.18.7
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 +981 -12
- package/dist/engine/worker.js +981 -12
- package/dist/index.cjs +1160 -84
- package/dist/index.d.cts +611 -511
- package/dist/index.d.ts +611 -511
- package/dist/index.js +1159 -83
- 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";
|
|
@@ -34377,6 +34738,9 @@ var expiredCodeExampleRule = createRule({
|
|
|
34377
34738
|
const issues = [];
|
|
34378
34739
|
const source = facts.v2?._source;
|
|
34379
34740
|
if (!source) return issues;
|
|
34741
|
+
const packages = declaredPackages(context.cwd);
|
|
34742
|
+
const packageName = context.packageName;
|
|
34743
|
+
if (packageName) packages.add(packageName);
|
|
34380
34744
|
const blocks = extractFencedCodeBlocks(source);
|
|
34381
34745
|
for (const block of blocks) {
|
|
34382
34746
|
if (!CODE_LANGS.has(block.lang)) continue;
|
|
@@ -34384,7 +34748,7 @@ var expiredCodeExampleRule = createRule({
|
|
|
34384
34748
|
const imports = extractImports(block.body);
|
|
34385
34749
|
for (const imp of imports) {
|
|
34386
34750
|
const pkgName = stripSubpath(imp);
|
|
34387
|
-
if (
|
|
34751
|
+
if (packages.has(pkgName)) continue;
|
|
34388
34752
|
issues.push({
|
|
34389
34753
|
ruleId: "docs/expired-code-example",
|
|
34390
34754
|
category: "docs",
|
|
@@ -34405,6 +34769,7 @@ var expiredCodeExampleRule = createRule({
|
|
|
34405
34769
|
import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync2 } from "fs";
|
|
34406
34770
|
import { join as join5, extname as extname3 } from "path";
|
|
34407
34771
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
34772
|
+
// JS reserved words
|
|
34408
34773
|
"true",
|
|
34409
34774
|
"false",
|
|
34410
34775
|
"null",
|
|
@@ -34504,7 +34869,472 @@ var RESERVED = /* @__PURE__ */ new Set([
|
|
|
34504
34869
|
"next",
|
|
34505
34870
|
"vue",
|
|
34506
34871
|
"angular",
|
|
34507
|
-
"svelte"
|
|
34872
|
+
"svelte",
|
|
34873
|
+
// Framework / runtime names
|
|
34874
|
+
"html",
|
|
34875
|
+
"astro",
|
|
34876
|
+
"python",
|
|
34877
|
+
"jvm",
|
|
34878
|
+
"kotlin",
|
|
34879
|
+
"swift",
|
|
34880
|
+
"dart",
|
|
34881
|
+
"ruby",
|
|
34882
|
+
"rust",
|
|
34883
|
+
"cpp",
|
|
34884
|
+
"go",
|
|
34885
|
+
"java",
|
|
34886
|
+
"php",
|
|
34887
|
+
"php-html",
|
|
34888
|
+
"csharp",
|
|
34889
|
+
"typescript",
|
|
34890
|
+
"javascript",
|
|
34891
|
+
"jsx",
|
|
34892
|
+
"tsx",
|
|
34893
|
+
"mjs",
|
|
34894
|
+
"cjs",
|
|
34895
|
+
"esnext",
|
|
34896
|
+
"es6",
|
|
34897
|
+
"es2022",
|
|
34898
|
+
"es2023",
|
|
34899
|
+
"esm",
|
|
34900
|
+
"cjs",
|
|
34901
|
+
"umd",
|
|
34902
|
+
"amd",
|
|
34903
|
+
"commonjs",
|
|
34904
|
+
"require",
|
|
34905
|
+
"module",
|
|
34906
|
+
"exports",
|
|
34907
|
+
"define",
|
|
34908
|
+
"global",
|
|
34909
|
+
"window",
|
|
34910
|
+
"document",
|
|
34911
|
+
"process",
|
|
34912
|
+
"console",
|
|
34913
|
+
"buffer",
|
|
34914
|
+
"stream",
|
|
34915
|
+
"fetch",
|
|
34916
|
+
"axios",
|
|
34917
|
+
"express",
|
|
34918
|
+
"fastify",
|
|
34919
|
+
"koa",
|
|
34920
|
+
"hapi",
|
|
34921
|
+
"nextjs",
|
|
34922
|
+
"nuxt",
|
|
34923
|
+
"remix",
|
|
34924
|
+
"gatsby",
|
|
34925
|
+
"sveltekit",
|
|
34926
|
+
"solid",
|
|
34927
|
+
"preact",
|
|
34928
|
+
"qwik",
|
|
34929
|
+
"lit",
|
|
34930
|
+
"stencil",
|
|
34931
|
+
"marko",
|
|
34932
|
+
"alpine",
|
|
34933
|
+
"stimulus",
|
|
34934
|
+
"turbo",
|
|
34935
|
+
"hotwire",
|
|
34936
|
+
// Models / providers
|
|
34937
|
+
"gpt",
|
|
34938
|
+
"claude",
|
|
34939
|
+
"gpt-3",
|
|
34940
|
+
"gpt-3.5",
|
|
34941
|
+
"gpt-4",
|
|
34942
|
+
"gpt-oss",
|
|
34943
|
+
"haiku",
|
|
34944
|
+
"sonnet",
|
|
34945
|
+
"opus",
|
|
34946
|
+
"aider",
|
|
34947
|
+
"tabby",
|
|
34948
|
+
"copilot",
|
|
34949
|
+
"cursor",
|
|
34950
|
+
"windsurf",
|
|
34951
|
+
"devin",
|
|
34952
|
+
"claude-code",
|
|
34953
|
+
// LLM-detection lingo
|
|
34954
|
+
"heuristic",
|
|
34955
|
+
"heuristics",
|
|
34956
|
+
"calibrate",
|
|
34957
|
+
"calibration",
|
|
34958
|
+
"calibrator",
|
|
34959
|
+
"corpus",
|
|
34960
|
+
"baseline",
|
|
34961
|
+
"baselines",
|
|
34962
|
+
"corpus-baselines",
|
|
34963
|
+
"lift",
|
|
34964
|
+
"recall",
|
|
34965
|
+
"precision",
|
|
34966
|
+
"fpRate",
|
|
34967
|
+
"ratio",
|
|
34968
|
+
"verdict",
|
|
34969
|
+
"USEFUL",
|
|
34970
|
+
"NOISY",
|
|
34971
|
+
"INVERTED",
|
|
34972
|
+
"HYGIENE",
|
|
34973
|
+
"DORMANT",
|
|
34974
|
+
"OK",
|
|
34975
|
+
"aiSpecific",
|
|
34976
|
+
"defaultOff",
|
|
34977
|
+
// Common slop-audit verbs/nouns
|
|
34978
|
+
"commit",
|
|
34979
|
+
"push",
|
|
34980
|
+
"reset",
|
|
34981
|
+
"rebase",
|
|
34982
|
+
"merge",
|
|
34983
|
+
"cherry-pick",
|
|
34984
|
+
"revert",
|
|
34985
|
+
"scan",
|
|
34986
|
+
"parse",
|
|
34987
|
+
"build",
|
|
34988
|
+
"test",
|
|
34989
|
+
"lint",
|
|
34990
|
+
"format",
|
|
34991
|
+
"check",
|
|
34992
|
+
"audit",
|
|
34993
|
+
"fix",
|
|
34994
|
+
"patch",
|
|
34995
|
+
"diff",
|
|
34996
|
+
"pr",
|
|
34997
|
+
"ci",
|
|
34998
|
+
"cd",
|
|
34999
|
+
"gh",
|
|
35000
|
+
"npm",
|
|
35001
|
+
"npx",
|
|
35002
|
+
"pnpm",
|
|
35003
|
+
"yaml",
|
|
35004
|
+
"json",
|
|
35005
|
+
"toml",
|
|
35006
|
+
"csv",
|
|
35007
|
+
"md",
|
|
35008
|
+
"mdx",
|
|
35009
|
+
"sh",
|
|
35010
|
+
"bash",
|
|
35011
|
+
"zsh",
|
|
35012
|
+
"fish",
|
|
35013
|
+
"ascii",
|
|
35014
|
+
"utf8",
|
|
35015
|
+
"utf-8",
|
|
35016
|
+
"base64",
|
|
35017
|
+
"hex",
|
|
35018
|
+
"binary",
|
|
35019
|
+
"text",
|
|
35020
|
+
// Common design / ui terms
|
|
35021
|
+
"flex",
|
|
35022
|
+
"grid",
|
|
35023
|
+
"auto",
|
|
35024
|
+
"min",
|
|
35025
|
+
"max",
|
|
35026
|
+
"fill",
|
|
35027
|
+
"stretch",
|
|
35028
|
+
"wrap",
|
|
35029
|
+
"nowrap",
|
|
35030
|
+
"inline",
|
|
35031
|
+
"block",
|
|
35032
|
+
"hidden",
|
|
35033
|
+
"visible",
|
|
35034
|
+
"static",
|
|
35035
|
+
"fixed",
|
|
35036
|
+
"absolute",
|
|
35037
|
+
"relative",
|
|
35038
|
+
"sticky",
|
|
35039
|
+
"pointer",
|
|
35040
|
+
"cursor",
|
|
35041
|
+
"focus",
|
|
35042
|
+
"hover",
|
|
35043
|
+
"active",
|
|
35044
|
+
"disabled",
|
|
35045
|
+
"readonly",
|
|
35046
|
+
"primary",
|
|
35047
|
+
"secondary",
|
|
35048
|
+
"tertiary",
|
|
35049
|
+
"success",
|
|
35050
|
+
"warning",
|
|
35051
|
+
"danger",
|
|
35052
|
+
"info",
|
|
35053
|
+
"muted",
|
|
35054
|
+
"sm",
|
|
35055
|
+
"md",
|
|
35056
|
+
"lg",
|
|
35057
|
+
"xl",
|
|
35058
|
+
"xxl",
|
|
35059
|
+
"xs",
|
|
35060
|
+
"2xl",
|
|
35061
|
+
"3xl",
|
|
35062
|
+
"4xl",
|
|
35063
|
+
// Math / types
|
|
35064
|
+
"array",
|
|
35065
|
+
"map",
|
|
35066
|
+
"set",
|
|
35067
|
+
"weakmap",
|
|
35068
|
+
"weakset",
|
|
35069
|
+
"object",
|
|
35070
|
+
"string",
|
|
35071
|
+
"number",
|
|
35072
|
+
"boolean",
|
|
35073
|
+
"bigint",
|
|
35074
|
+
"symbol",
|
|
35075
|
+
"null",
|
|
35076
|
+
"undefined",
|
|
35077
|
+
"any",
|
|
35078
|
+
"unknown",
|
|
35079
|
+
"never",
|
|
35080
|
+
"void",
|
|
35081
|
+
"readonly",
|
|
35082
|
+
"private",
|
|
35083
|
+
"public",
|
|
35084
|
+
"protected",
|
|
35085
|
+
"static",
|
|
35086
|
+
"abstract",
|
|
35087
|
+
"async",
|
|
35088
|
+
"generator",
|
|
35089
|
+
"iterator",
|
|
35090
|
+
"iterable",
|
|
35091
|
+
"promise",
|
|
35092
|
+
"observable",
|
|
35093
|
+
// Auth / domain
|
|
35094
|
+
"admin",
|
|
35095
|
+
"user",
|
|
35096
|
+
"guest",
|
|
35097
|
+
"anonymous",
|
|
35098
|
+
"authenticated",
|
|
35099
|
+
"unauthenticated",
|
|
35100
|
+
"jwt",
|
|
35101
|
+
"oauth",
|
|
35102
|
+
"oidc",
|
|
35103
|
+
"saml",
|
|
35104
|
+
"csrf",
|
|
35105
|
+
"xss",
|
|
35106
|
+
"sql",
|
|
35107
|
+
"nosql",
|
|
35108
|
+
"orm",
|
|
35109
|
+
"prisma",
|
|
35110
|
+
"drizzle",
|
|
35111
|
+
"sequelize",
|
|
35112
|
+
"mongoose",
|
|
35113
|
+
"redis",
|
|
35114
|
+
"postgres",
|
|
35115
|
+
"mysql",
|
|
35116
|
+
"sqlite",
|
|
35117
|
+
"kafka",
|
|
35118
|
+
"rabbitmq",
|
|
35119
|
+
"graphql",
|
|
35120
|
+
"rest",
|
|
35121
|
+
"grpc",
|
|
35122
|
+
"websocket",
|
|
35123
|
+
// slop-audit specific
|
|
35124
|
+
"slopbrick",
|
|
35125
|
+
"usebrick",
|
|
35126
|
+
"deadcode",
|
|
35127
|
+
"unused",
|
|
35128
|
+
"orphan",
|
|
35129
|
+
"zombie",
|
|
35130
|
+
"blocker",
|
|
35131
|
+
"warning",
|
|
35132
|
+
"info",
|
|
35133
|
+
"error",
|
|
35134
|
+
"verbose",
|
|
35135
|
+
"debug",
|
|
35136
|
+
"silly",
|
|
35137
|
+
"p50",
|
|
35138
|
+
"p90",
|
|
35139
|
+
"p95",
|
|
35140
|
+
"p99",
|
|
35141
|
+
"min",
|
|
35142
|
+
"max",
|
|
35143
|
+
"avg",
|
|
35144
|
+
"mean",
|
|
35145
|
+
"median",
|
|
35146
|
+
"ratchet",
|
|
35147
|
+
"tier",
|
|
35148
|
+
"composite",
|
|
35149
|
+
"fitness",
|
|
35150
|
+
"fpr",
|
|
35151
|
+
"tpr",
|
|
35152
|
+
"roc",
|
|
35153
|
+
"should",
|
|
35154
|
+
"could",
|
|
35155
|
+
"would",
|
|
35156
|
+
"might",
|
|
35157
|
+
"must",
|
|
35158
|
+
"shall",
|
|
35159
|
+
"may",
|
|
35160
|
+
"can",
|
|
35161
|
+
"todo",
|
|
35162
|
+
"fixme",
|
|
35163
|
+
"xxx",
|
|
35164
|
+
"hack",
|
|
35165
|
+
"note",
|
|
35166
|
+
"warning",
|
|
35167
|
+
"attention",
|
|
35168
|
+
"h1",
|
|
35169
|
+
"h2",
|
|
35170
|
+
"h3",
|
|
35171
|
+
"h4",
|
|
35172
|
+
"h5",
|
|
35173
|
+
"h6",
|
|
35174
|
+
"strong",
|
|
35175
|
+
"em",
|
|
35176
|
+
"b",
|
|
35177
|
+
"i",
|
|
35178
|
+
"u",
|
|
35179
|
+
"true",
|
|
35180
|
+
"false",
|
|
35181
|
+
"yes",
|
|
35182
|
+
"no",
|
|
35183
|
+
"on",
|
|
35184
|
+
"off",
|
|
35185
|
+
"enable",
|
|
35186
|
+
"disable",
|
|
35187
|
+
"ltr",
|
|
35188
|
+
"rtl",
|
|
35189
|
+
"auto",
|
|
35190
|
+
"start",
|
|
35191
|
+
"end",
|
|
35192
|
+
"center",
|
|
35193
|
+
"baseline",
|
|
35194
|
+
"stretch",
|
|
35195
|
+
"rounded",
|
|
35196
|
+
"sharp",
|
|
35197
|
+
"outline",
|
|
35198
|
+
"ghost",
|
|
35199
|
+
"link",
|
|
35200
|
+
"filled",
|
|
35201
|
+
"row",
|
|
35202
|
+
"col",
|
|
35203
|
+
"gap",
|
|
35204
|
+
"pad",
|
|
35205
|
+
"margin",
|
|
35206
|
+
"padding",
|
|
35207
|
+
"border",
|
|
35208
|
+
"shadow",
|
|
35209
|
+
"transparent",
|
|
35210
|
+
"currentcolor",
|
|
35211
|
+
"inherit",
|
|
35212
|
+
"initial",
|
|
35213
|
+
"unset",
|
|
35214
|
+
"revert",
|
|
35215
|
+
"hover",
|
|
35216
|
+
"focus",
|
|
35217
|
+
"active",
|
|
35218
|
+
"disabled",
|
|
35219
|
+
"checked",
|
|
35220
|
+
"indeterminate",
|
|
35221
|
+
"open",
|
|
35222
|
+
"close",
|
|
35223
|
+
"expanded",
|
|
35224
|
+
"collapsed",
|
|
35225
|
+
"selected",
|
|
35226
|
+
"pressed",
|
|
35227
|
+
// Web/CSS
|
|
35228
|
+
"div",
|
|
35229
|
+
"span",
|
|
35230
|
+
"p",
|
|
35231
|
+
"a",
|
|
35232
|
+
"img",
|
|
35233
|
+
"ul",
|
|
35234
|
+
"ol",
|
|
35235
|
+
"li",
|
|
35236
|
+
"table",
|
|
35237
|
+
"tr",
|
|
35238
|
+
"td",
|
|
35239
|
+
"th",
|
|
35240
|
+
"thead",
|
|
35241
|
+
"tbody",
|
|
35242
|
+
"tfoot",
|
|
35243
|
+
"caption",
|
|
35244
|
+
"figure",
|
|
35245
|
+
"figcaption",
|
|
35246
|
+
"main",
|
|
35247
|
+
"section",
|
|
35248
|
+
"article",
|
|
35249
|
+
"aside",
|
|
35250
|
+
"header",
|
|
35251
|
+
"footer",
|
|
35252
|
+
"nav",
|
|
35253
|
+
"form",
|
|
35254
|
+
"input",
|
|
35255
|
+
"button",
|
|
35256
|
+
"select",
|
|
35257
|
+
"option",
|
|
35258
|
+
"textarea",
|
|
35259
|
+
"label",
|
|
35260
|
+
"fieldset",
|
|
35261
|
+
"legend",
|
|
35262
|
+
"details",
|
|
35263
|
+
"summary",
|
|
35264
|
+
"dialog",
|
|
35265
|
+
"menu",
|
|
35266
|
+
"menuitem",
|
|
35267
|
+
"template",
|
|
35268
|
+
"slot",
|
|
35269
|
+
"picture",
|
|
35270
|
+
"source",
|
|
35271
|
+
"track",
|
|
35272
|
+
"video",
|
|
35273
|
+
"audio",
|
|
35274
|
+
"canvas",
|
|
35275
|
+
"svg",
|
|
35276
|
+
"iframe",
|
|
35277
|
+
"embed",
|
|
35278
|
+
"object",
|
|
35279
|
+
"portal",
|
|
35280
|
+
// Common business terms
|
|
35281
|
+
"api",
|
|
35282
|
+
"cli",
|
|
35283
|
+
"ui",
|
|
35284
|
+
"ux",
|
|
35285
|
+
"sdk",
|
|
35286
|
+
"ide",
|
|
35287
|
+
"cli",
|
|
35288
|
+
"docs",
|
|
35289
|
+
"doc",
|
|
35290
|
+
"blog",
|
|
35291
|
+
"post",
|
|
35292
|
+
"page",
|
|
35293
|
+
"view",
|
|
35294
|
+
"tab",
|
|
35295
|
+
"panel",
|
|
35296
|
+
"card",
|
|
35297
|
+
"list",
|
|
35298
|
+
"grid",
|
|
35299
|
+
"form",
|
|
35300
|
+
"modal",
|
|
35301
|
+
"menu",
|
|
35302
|
+
"button",
|
|
35303
|
+
"icon",
|
|
35304
|
+
"avatar",
|
|
35305
|
+
"badge",
|
|
35306
|
+
"chip",
|
|
35307
|
+
"tooltip",
|
|
35308
|
+
"popover",
|
|
35309
|
+
"dropdown",
|
|
35310
|
+
"banner",
|
|
35311
|
+
"alert",
|
|
35312
|
+
"toast",
|
|
35313
|
+
"notification",
|
|
35314
|
+
"drawer",
|
|
35315
|
+
"sidebar",
|
|
35316
|
+
"navbar",
|
|
35317
|
+
"header",
|
|
35318
|
+
"footer",
|
|
35319
|
+
"hero",
|
|
35320
|
+
"cta",
|
|
35321
|
+
"cta-primary",
|
|
35322
|
+
"cta-secondary",
|
|
35323
|
+
"pricing",
|
|
35324
|
+
"price",
|
|
35325
|
+
"cost",
|
|
35326
|
+
"rate",
|
|
35327
|
+
"percent",
|
|
35328
|
+
"pct",
|
|
35329
|
+
"count",
|
|
35330
|
+
"total",
|
|
35331
|
+
"small",
|
|
35332
|
+
"medium",
|
|
35333
|
+
"large",
|
|
35334
|
+
"xl",
|
|
35335
|
+
"xxl",
|
|
35336
|
+
"tiny",
|
|
35337
|
+
"huge"
|
|
34508
35338
|
]);
|
|
34509
35339
|
var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
34510
35340
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
@@ -34542,7 +35372,12 @@ function collectExports(cwd) {
|
|
|
34542
35372
|
/\bexport\s+class\s+([A-Za-z_$][\w$]*)/g,
|
|
34543
35373
|
/\bexport\s+interface\s+([A-Za-z_$][\w$]*)/g,
|
|
34544
35374
|
/\bexport\s+type\s+([A-Za-z_$][\w$]*)/g,
|
|
34545
|
-
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g
|
|
35375
|
+
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
|
|
35376
|
+
// v0.18.6: also collect field names from `export interface` and
|
|
35377
|
+
// `export type` declarations. Without this, fields like
|
|
35378
|
+
// `crossFileDrift`, `aiQuality`, `engineeringHygiene` are
|
|
35379
|
+
// flagged as stale even though they're valid type fields.
|
|
35380
|
+
/^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
|
|
34546
35381
|
]) {
|
|
34547
35382
|
let m;
|
|
34548
35383
|
while ((m = re.exec(source)) !== null) {
|
|
@@ -34553,6 +35388,21 @@ function collectExports(cwd) {
|
|
|
34553
35388
|
}
|
|
34554
35389
|
return out;
|
|
34555
35390
|
}
|
|
35391
|
+
function looksLikeProseLabel(inside) {
|
|
35392
|
+
const trimmed = inside.trim();
|
|
35393
|
+
if (trimmed.length === 0) return false;
|
|
35394
|
+
if (trimmed.startsWith("`") || trimmed.startsWith("/")) return true;
|
|
35395
|
+
if (trimmed.includes("`")) return true;
|
|
35396
|
+
if (/\d+\s+[a-z]/i.test(trimmed)) return true;
|
|
35397
|
+
const parts = trimmed.split(",").map((s) => s.trim());
|
|
35398
|
+
if (parts.length >= 3) {
|
|
35399
|
+
const allNumeric = parts.every((p) => /^\d+\.?\d*$/.test(p));
|
|
35400
|
+
if (!allNumeric) return true;
|
|
35401
|
+
}
|
|
35402
|
+
if (trimmed.length > 40 && !trimmed.includes(",")) return true;
|
|
35403
|
+
if (trimmed.includes("\u2014") || trimmed.includes("\u2013")) return true;
|
|
35404
|
+
return false;
|
|
35405
|
+
}
|
|
34556
35406
|
var staleFunctionReferenceRule = createRule({
|
|
34557
35407
|
id: "docs/stale-function-reference",
|
|
34558
35408
|
category: "docs",
|
|
@@ -34572,8 +35422,39 @@ var staleFunctionReferenceRule = createRule({
|
|
|
34572
35422
|
if (text.length < 3) continue;
|
|
34573
35423
|
if (RESERVED.has(text.toLowerCase())) continue;
|
|
34574
35424
|
if (context.exports.has(text)) continue;
|
|
34575
|
-
const
|
|
34576
|
-
|
|
35425
|
+
const lineEnd = source.indexOf("\n", span.index);
|
|
35426
|
+
const restOfLine = source.slice(
|
|
35427
|
+
span.index,
|
|
35428
|
+
lineEnd === -1 ? source.length : lineEnd
|
|
35429
|
+
);
|
|
35430
|
+
const closeTick = restOfLine.indexOf("`", 1);
|
|
35431
|
+
if (closeTick === -1) continue;
|
|
35432
|
+
const afterTick = restOfLine.slice(closeTick + 1);
|
|
35433
|
+
const directCall = /^\s*\(/.test(afterTick);
|
|
35434
|
+
let identifierRepeats = false;
|
|
35435
|
+
if (!directCall) {
|
|
35436
|
+
const afterSpan = restOfLine.slice(closeTick + 1);
|
|
35437
|
+
const needle = text + "(";
|
|
35438
|
+
identifierRepeats = afterSpan.indexOf(needle) !== -1;
|
|
35439
|
+
}
|
|
35440
|
+
if (!directCall && !identifierRepeats) continue;
|
|
35441
|
+
const beforeTickIdx = span.index - 1;
|
|
35442
|
+
const beforeChar = beforeTickIdx >= 0 ? source[beforeTickIdx] : "";
|
|
35443
|
+
if (beforeChar === "." || beforeChar === "|") continue;
|
|
35444
|
+
const parenStart = restOfLine.indexOf("(", closeTick);
|
|
35445
|
+
const parenEnd = restOfLine.indexOf(")", parenStart);
|
|
35446
|
+
if (parenStart !== -1 && parenEnd !== -1) {
|
|
35447
|
+
const inside = restOfLine.slice(parenStart + 1, parenEnd);
|
|
35448
|
+
const trimmed = inside.trim();
|
|
35449
|
+
if (looksLikeProseLabel(inside)) continue;
|
|
35450
|
+
const looksLikeTypeAnnotation = !inside.includes(":") && (/\b(string|number|boolean|null|undefined|object|array|required|optional|categorical|direct|n\/a|\bmapped\b|0[\-–][0-9]+|v[0-9]|higher is better|lower is better|added in|deprecated|pr-[0-9])\b/i.test(
|
|
35451
|
+
inside
|
|
35452
|
+
) || // Short single-word label (≤ 24 chars, no `,`,
|
|
35453
|
+
// doesn't look like a function arg). Real function
|
|
35454
|
+
// calls are usually longer or contain commas.
|
|
35455
|
+
trimmed.length > 0 && trimmed.length <= 24 && !trimmed.includes(",") && /[a-zA-Z]/.test(trimmed));
|
|
35456
|
+
if (looksLikeTypeAnnotation) continue;
|
|
35457
|
+
}
|
|
34577
35458
|
issues.push({
|
|
34578
35459
|
ruleId: "docs/stale-function-reference",
|
|
34579
35460
|
category: "docs",
|
|
@@ -34645,7 +35526,33 @@ var ENGLISH_WORD_DENYLIST = /* @__PURE__ */ new Set([
|
|
|
34645
35526
|
"jsx",
|
|
34646
35527
|
"ok",
|
|
34647
35528
|
"no",
|
|
34648
|
-
"yes"
|
|
35529
|
+
"yes",
|
|
35530
|
+
// v0.18.6: common English adjectives / adverbs that frequently
|
|
35531
|
+
// appear in backticked prose but are not package names.
|
|
35532
|
+
"aspirational",
|
|
35533
|
+
"concrete",
|
|
35534
|
+
"abstract",
|
|
35535
|
+
"inline",
|
|
35536
|
+
"exposed",
|
|
35537
|
+
"deprecated",
|
|
35538
|
+
"experimental",
|
|
35539
|
+
"stable",
|
|
35540
|
+
"beta",
|
|
35541
|
+
"alpha",
|
|
35542
|
+
"wip",
|
|
35543
|
+
"draft",
|
|
35544
|
+
"final",
|
|
35545
|
+
"shim",
|
|
35546
|
+
"polyfill",
|
|
35547
|
+
"stub",
|
|
35548
|
+
"mock",
|
|
35549
|
+
"fake",
|
|
35550
|
+
"real",
|
|
35551
|
+
"false",
|
|
35552
|
+
"true",
|
|
35553
|
+
"optional",
|
|
35554
|
+
"required",
|
|
35555
|
+
"default"
|
|
34649
35556
|
]);
|
|
34650
35557
|
var stalePackageReferenceRule = createRule({
|
|
34651
35558
|
id: "docs/stale-package-reference",
|
|
@@ -34807,7 +35714,9 @@ var brokenLinkRule = createRule({
|
|
|
34807
35714
|
if (target.startsWith("#")) continue;
|
|
34808
35715
|
if (target.startsWith("//")) continue;
|
|
34809
35716
|
if (target.startsWith("/")) continue;
|
|
34810
|
-
const
|
|
35717
|
+
const filePart = target.split("#")[0] ?? target;
|
|
35718
|
+
if (filePart === "") continue;
|
|
35719
|
+
const resolved = join7(docDir, filePart);
|
|
34811
35720
|
if (existsSync4(resolved)) continue;
|
|
34812
35721
|
issues.push({
|
|
34813
35722
|
ruleId: "docs/broken-link",
|
|
@@ -39579,6 +40488,11 @@ var builtinRules = [
|
|
|
39579
40488
|
missingNotNullRule,
|
|
39580
40489
|
namingInconsistencyRule,
|
|
39581
40490
|
sqlConcatRule,
|
|
40491
|
+
deadBranchRule,
|
|
40492
|
+
unreachableRule,
|
|
40493
|
+
unusedImportRule,
|
|
40494
|
+
unusedLocalRule,
|
|
40495
|
+
unusedParameterRule,
|
|
39582
40496
|
brokenLinkRule,
|
|
39583
40497
|
expiredCodeExampleRule,
|
|
39584
40498
|
staleFunctionReferenceRule,
|
|
@@ -40586,6 +41500,61 @@ var signal_strength_default = {
|
|
|
40586
41500
|
_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
41501
|
aiSpecific: true
|
|
40588
41502
|
},
|
|
41503
|
+
"dead/unused-import": {
|
|
41504
|
+
recall: 0,
|
|
41505
|
+
fpRate: 0,
|
|
41506
|
+
ratio: 0,
|
|
41507
|
+
precision: 0,
|
|
41508
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41509
|
+
verdict: "DORMANT",
|
|
41510
|
+
defaultOff: true,
|
|
41511
|
+
_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.",
|
|
41512
|
+
aiSpecific: true
|
|
41513
|
+
},
|
|
41514
|
+
"dead/unused-local": {
|
|
41515
|
+
recall: 0,
|
|
41516
|
+
fpRate: 0,
|
|
41517
|
+
ratio: 0,
|
|
41518
|
+
precision: 0,
|
|
41519
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41520
|
+
verdict: "DORMANT",
|
|
41521
|
+
defaultOff: true,
|
|
41522
|
+
_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).",
|
|
41523
|
+
aiSpecific: true
|
|
41524
|
+
},
|
|
41525
|
+
"dead/unused-parameter": {
|
|
41526
|
+
recall: 0,
|
|
41527
|
+
fpRate: 0,
|
|
41528
|
+
ratio: 0,
|
|
41529
|
+
precision: 0,
|
|
41530
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41531
|
+
verdict: "DORMANT",
|
|
41532
|
+
defaultOff: true,
|
|
41533
|
+
_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.",
|
|
41534
|
+
aiSpecific: true
|
|
41535
|
+
},
|
|
41536
|
+
"dead/dead-branch": {
|
|
41537
|
+
recall: 0,
|
|
41538
|
+
fpRate: 0,
|
|
41539
|
+
ratio: 0,
|
|
41540
|
+
precision: 0,
|
|
41541
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41542
|
+
verdict: "DORMANT",
|
|
41543
|
+
defaultOff: true,
|
|
41544
|
+
_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.",
|
|
41545
|
+
aiSpecific: true
|
|
41546
|
+
},
|
|
41547
|
+
"dead/unreachable": {
|
|
41548
|
+
recall: 0,
|
|
41549
|
+
fpRate: 0,
|
|
41550
|
+
ratio: 0,
|
|
41551
|
+
precision: 0,
|
|
41552
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41553
|
+
verdict: "DORMANT",
|
|
41554
|
+
defaultOff: true,
|
|
41555
|
+
_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.",
|
|
41556
|
+
aiSpecific: true
|
|
41557
|
+
},
|
|
40589
41558
|
"docs/stale-package-reference": {
|
|
40590
41559
|
recall: 0,
|
|
40591
41560
|
fpRate: 0,
|