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.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");
|
|
@@ -34396,6 +34757,9 @@ var expiredCodeExampleRule = createRule({
|
|
|
34396
34757
|
const issues = [];
|
|
34397
34758
|
const source = facts.v2?._source;
|
|
34398
34759
|
if (!source) return issues;
|
|
34760
|
+
const packages = declaredPackages(context.cwd);
|
|
34761
|
+
const packageName = context.packageName;
|
|
34762
|
+
if (packageName) packages.add(packageName);
|
|
34399
34763
|
const blocks = extractFencedCodeBlocks(source);
|
|
34400
34764
|
for (const block of blocks) {
|
|
34401
34765
|
if (!CODE_LANGS.has(block.lang)) continue;
|
|
@@ -34403,7 +34767,7 @@ var expiredCodeExampleRule = createRule({
|
|
|
34403
34767
|
const imports = extractImports(block.body);
|
|
34404
34768
|
for (const imp of imports) {
|
|
34405
34769
|
const pkgName = stripSubpath(imp);
|
|
34406
|
-
if (
|
|
34770
|
+
if (packages.has(pkgName)) continue;
|
|
34407
34771
|
issues.push({
|
|
34408
34772
|
ruleId: "docs/expired-code-example",
|
|
34409
34773
|
category: "docs",
|
|
@@ -34424,6 +34788,7 @@ var expiredCodeExampleRule = createRule({
|
|
|
34424
34788
|
var import_node_fs4 = require("fs");
|
|
34425
34789
|
var import_node_path4 = require("path");
|
|
34426
34790
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
34791
|
+
// JS reserved words
|
|
34427
34792
|
"true",
|
|
34428
34793
|
"false",
|
|
34429
34794
|
"null",
|
|
@@ -34523,7 +34888,472 @@ var RESERVED = /* @__PURE__ */ new Set([
|
|
|
34523
34888
|
"next",
|
|
34524
34889
|
"vue",
|
|
34525
34890
|
"angular",
|
|
34526
|
-
"svelte"
|
|
34891
|
+
"svelte",
|
|
34892
|
+
// Framework / runtime names
|
|
34893
|
+
"html",
|
|
34894
|
+
"astro",
|
|
34895
|
+
"python",
|
|
34896
|
+
"jvm",
|
|
34897
|
+
"kotlin",
|
|
34898
|
+
"swift",
|
|
34899
|
+
"dart",
|
|
34900
|
+
"ruby",
|
|
34901
|
+
"rust",
|
|
34902
|
+
"cpp",
|
|
34903
|
+
"go",
|
|
34904
|
+
"java",
|
|
34905
|
+
"php",
|
|
34906
|
+
"php-html",
|
|
34907
|
+
"csharp",
|
|
34908
|
+
"typescript",
|
|
34909
|
+
"javascript",
|
|
34910
|
+
"jsx",
|
|
34911
|
+
"tsx",
|
|
34912
|
+
"mjs",
|
|
34913
|
+
"cjs",
|
|
34914
|
+
"esnext",
|
|
34915
|
+
"es6",
|
|
34916
|
+
"es2022",
|
|
34917
|
+
"es2023",
|
|
34918
|
+
"esm",
|
|
34919
|
+
"cjs",
|
|
34920
|
+
"umd",
|
|
34921
|
+
"amd",
|
|
34922
|
+
"commonjs",
|
|
34923
|
+
"require",
|
|
34924
|
+
"module",
|
|
34925
|
+
"exports",
|
|
34926
|
+
"define",
|
|
34927
|
+
"global",
|
|
34928
|
+
"window",
|
|
34929
|
+
"document",
|
|
34930
|
+
"process",
|
|
34931
|
+
"console",
|
|
34932
|
+
"buffer",
|
|
34933
|
+
"stream",
|
|
34934
|
+
"fetch",
|
|
34935
|
+
"axios",
|
|
34936
|
+
"express",
|
|
34937
|
+
"fastify",
|
|
34938
|
+
"koa",
|
|
34939
|
+
"hapi",
|
|
34940
|
+
"nextjs",
|
|
34941
|
+
"nuxt",
|
|
34942
|
+
"remix",
|
|
34943
|
+
"gatsby",
|
|
34944
|
+
"sveltekit",
|
|
34945
|
+
"solid",
|
|
34946
|
+
"preact",
|
|
34947
|
+
"qwik",
|
|
34948
|
+
"lit",
|
|
34949
|
+
"stencil",
|
|
34950
|
+
"marko",
|
|
34951
|
+
"alpine",
|
|
34952
|
+
"stimulus",
|
|
34953
|
+
"turbo",
|
|
34954
|
+
"hotwire",
|
|
34955
|
+
// Models / providers
|
|
34956
|
+
"gpt",
|
|
34957
|
+
"claude",
|
|
34958
|
+
"gpt-3",
|
|
34959
|
+
"gpt-3.5",
|
|
34960
|
+
"gpt-4",
|
|
34961
|
+
"gpt-oss",
|
|
34962
|
+
"haiku",
|
|
34963
|
+
"sonnet",
|
|
34964
|
+
"opus",
|
|
34965
|
+
"aider",
|
|
34966
|
+
"tabby",
|
|
34967
|
+
"copilot",
|
|
34968
|
+
"cursor",
|
|
34969
|
+
"windsurf",
|
|
34970
|
+
"devin",
|
|
34971
|
+
"claude-code",
|
|
34972
|
+
// LLM-detection lingo
|
|
34973
|
+
"heuristic",
|
|
34974
|
+
"heuristics",
|
|
34975
|
+
"calibrate",
|
|
34976
|
+
"calibration",
|
|
34977
|
+
"calibrator",
|
|
34978
|
+
"corpus",
|
|
34979
|
+
"baseline",
|
|
34980
|
+
"baselines",
|
|
34981
|
+
"corpus-baselines",
|
|
34982
|
+
"lift",
|
|
34983
|
+
"recall",
|
|
34984
|
+
"precision",
|
|
34985
|
+
"fpRate",
|
|
34986
|
+
"ratio",
|
|
34987
|
+
"verdict",
|
|
34988
|
+
"USEFUL",
|
|
34989
|
+
"NOISY",
|
|
34990
|
+
"INVERTED",
|
|
34991
|
+
"HYGIENE",
|
|
34992
|
+
"DORMANT",
|
|
34993
|
+
"OK",
|
|
34994
|
+
"aiSpecific",
|
|
34995
|
+
"defaultOff",
|
|
34996
|
+
// Common slop-audit verbs/nouns
|
|
34997
|
+
"commit",
|
|
34998
|
+
"push",
|
|
34999
|
+
"reset",
|
|
35000
|
+
"rebase",
|
|
35001
|
+
"merge",
|
|
35002
|
+
"cherry-pick",
|
|
35003
|
+
"revert",
|
|
35004
|
+
"scan",
|
|
35005
|
+
"parse",
|
|
35006
|
+
"build",
|
|
35007
|
+
"test",
|
|
35008
|
+
"lint",
|
|
35009
|
+
"format",
|
|
35010
|
+
"check",
|
|
35011
|
+
"audit",
|
|
35012
|
+
"fix",
|
|
35013
|
+
"patch",
|
|
35014
|
+
"diff",
|
|
35015
|
+
"pr",
|
|
35016
|
+
"ci",
|
|
35017
|
+
"cd",
|
|
35018
|
+
"gh",
|
|
35019
|
+
"npm",
|
|
35020
|
+
"npx",
|
|
35021
|
+
"pnpm",
|
|
35022
|
+
"yaml",
|
|
35023
|
+
"json",
|
|
35024
|
+
"toml",
|
|
35025
|
+
"csv",
|
|
35026
|
+
"md",
|
|
35027
|
+
"mdx",
|
|
35028
|
+
"sh",
|
|
35029
|
+
"bash",
|
|
35030
|
+
"zsh",
|
|
35031
|
+
"fish",
|
|
35032
|
+
"ascii",
|
|
35033
|
+
"utf8",
|
|
35034
|
+
"utf-8",
|
|
35035
|
+
"base64",
|
|
35036
|
+
"hex",
|
|
35037
|
+
"binary",
|
|
35038
|
+
"text",
|
|
35039
|
+
// Common design / ui terms
|
|
35040
|
+
"flex",
|
|
35041
|
+
"grid",
|
|
35042
|
+
"auto",
|
|
35043
|
+
"min",
|
|
35044
|
+
"max",
|
|
35045
|
+
"fill",
|
|
35046
|
+
"stretch",
|
|
35047
|
+
"wrap",
|
|
35048
|
+
"nowrap",
|
|
35049
|
+
"inline",
|
|
35050
|
+
"block",
|
|
35051
|
+
"hidden",
|
|
35052
|
+
"visible",
|
|
35053
|
+
"static",
|
|
35054
|
+
"fixed",
|
|
35055
|
+
"absolute",
|
|
35056
|
+
"relative",
|
|
35057
|
+
"sticky",
|
|
35058
|
+
"pointer",
|
|
35059
|
+
"cursor",
|
|
35060
|
+
"focus",
|
|
35061
|
+
"hover",
|
|
35062
|
+
"active",
|
|
35063
|
+
"disabled",
|
|
35064
|
+
"readonly",
|
|
35065
|
+
"primary",
|
|
35066
|
+
"secondary",
|
|
35067
|
+
"tertiary",
|
|
35068
|
+
"success",
|
|
35069
|
+
"warning",
|
|
35070
|
+
"danger",
|
|
35071
|
+
"info",
|
|
35072
|
+
"muted",
|
|
35073
|
+
"sm",
|
|
35074
|
+
"md",
|
|
35075
|
+
"lg",
|
|
35076
|
+
"xl",
|
|
35077
|
+
"xxl",
|
|
35078
|
+
"xs",
|
|
35079
|
+
"2xl",
|
|
35080
|
+
"3xl",
|
|
35081
|
+
"4xl",
|
|
35082
|
+
// Math / types
|
|
35083
|
+
"array",
|
|
35084
|
+
"map",
|
|
35085
|
+
"set",
|
|
35086
|
+
"weakmap",
|
|
35087
|
+
"weakset",
|
|
35088
|
+
"object",
|
|
35089
|
+
"string",
|
|
35090
|
+
"number",
|
|
35091
|
+
"boolean",
|
|
35092
|
+
"bigint",
|
|
35093
|
+
"symbol",
|
|
35094
|
+
"null",
|
|
35095
|
+
"undefined",
|
|
35096
|
+
"any",
|
|
35097
|
+
"unknown",
|
|
35098
|
+
"never",
|
|
35099
|
+
"void",
|
|
35100
|
+
"readonly",
|
|
35101
|
+
"private",
|
|
35102
|
+
"public",
|
|
35103
|
+
"protected",
|
|
35104
|
+
"static",
|
|
35105
|
+
"abstract",
|
|
35106
|
+
"async",
|
|
35107
|
+
"generator",
|
|
35108
|
+
"iterator",
|
|
35109
|
+
"iterable",
|
|
35110
|
+
"promise",
|
|
35111
|
+
"observable",
|
|
35112
|
+
// Auth / domain
|
|
35113
|
+
"admin",
|
|
35114
|
+
"user",
|
|
35115
|
+
"guest",
|
|
35116
|
+
"anonymous",
|
|
35117
|
+
"authenticated",
|
|
35118
|
+
"unauthenticated",
|
|
35119
|
+
"jwt",
|
|
35120
|
+
"oauth",
|
|
35121
|
+
"oidc",
|
|
35122
|
+
"saml",
|
|
35123
|
+
"csrf",
|
|
35124
|
+
"xss",
|
|
35125
|
+
"sql",
|
|
35126
|
+
"nosql",
|
|
35127
|
+
"orm",
|
|
35128
|
+
"prisma",
|
|
35129
|
+
"drizzle",
|
|
35130
|
+
"sequelize",
|
|
35131
|
+
"mongoose",
|
|
35132
|
+
"redis",
|
|
35133
|
+
"postgres",
|
|
35134
|
+
"mysql",
|
|
35135
|
+
"sqlite",
|
|
35136
|
+
"kafka",
|
|
35137
|
+
"rabbitmq",
|
|
35138
|
+
"graphql",
|
|
35139
|
+
"rest",
|
|
35140
|
+
"grpc",
|
|
35141
|
+
"websocket",
|
|
35142
|
+
// slop-audit specific
|
|
35143
|
+
"slopbrick",
|
|
35144
|
+
"usebrick",
|
|
35145
|
+
"deadcode",
|
|
35146
|
+
"unused",
|
|
35147
|
+
"orphan",
|
|
35148
|
+
"zombie",
|
|
35149
|
+
"blocker",
|
|
35150
|
+
"warning",
|
|
35151
|
+
"info",
|
|
35152
|
+
"error",
|
|
35153
|
+
"verbose",
|
|
35154
|
+
"debug",
|
|
35155
|
+
"silly",
|
|
35156
|
+
"p50",
|
|
35157
|
+
"p90",
|
|
35158
|
+
"p95",
|
|
35159
|
+
"p99",
|
|
35160
|
+
"min",
|
|
35161
|
+
"max",
|
|
35162
|
+
"avg",
|
|
35163
|
+
"mean",
|
|
35164
|
+
"median",
|
|
35165
|
+
"ratchet",
|
|
35166
|
+
"tier",
|
|
35167
|
+
"composite",
|
|
35168
|
+
"fitness",
|
|
35169
|
+
"fpr",
|
|
35170
|
+
"tpr",
|
|
35171
|
+
"roc",
|
|
35172
|
+
"should",
|
|
35173
|
+
"could",
|
|
35174
|
+
"would",
|
|
35175
|
+
"might",
|
|
35176
|
+
"must",
|
|
35177
|
+
"shall",
|
|
35178
|
+
"may",
|
|
35179
|
+
"can",
|
|
35180
|
+
"todo",
|
|
35181
|
+
"fixme",
|
|
35182
|
+
"xxx",
|
|
35183
|
+
"hack",
|
|
35184
|
+
"note",
|
|
35185
|
+
"warning",
|
|
35186
|
+
"attention",
|
|
35187
|
+
"h1",
|
|
35188
|
+
"h2",
|
|
35189
|
+
"h3",
|
|
35190
|
+
"h4",
|
|
35191
|
+
"h5",
|
|
35192
|
+
"h6",
|
|
35193
|
+
"strong",
|
|
35194
|
+
"em",
|
|
35195
|
+
"b",
|
|
35196
|
+
"i",
|
|
35197
|
+
"u",
|
|
35198
|
+
"true",
|
|
35199
|
+
"false",
|
|
35200
|
+
"yes",
|
|
35201
|
+
"no",
|
|
35202
|
+
"on",
|
|
35203
|
+
"off",
|
|
35204
|
+
"enable",
|
|
35205
|
+
"disable",
|
|
35206
|
+
"ltr",
|
|
35207
|
+
"rtl",
|
|
35208
|
+
"auto",
|
|
35209
|
+
"start",
|
|
35210
|
+
"end",
|
|
35211
|
+
"center",
|
|
35212
|
+
"baseline",
|
|
35213
|
+
"stretch",
|
|
35214
|
+
"rounded",
|
|
35215
|
+
"sharp",
|
|
35216
|
+
"outline",
|
|
35217
|
+
"ghost",
|
|
35218
|
+
"link",
|
|
35219
|
+
"filled",
|
|
35220
|
+
"row",
|
|
35221
|
+
"col",
|
|
35222
|
+
"gap",
|
|
35223
|
+
"pad",
|
|
35224
|
+
"margin",
|
|
35225
|
+
"padding",
|
|
35226
|
+
"border",
|
|
35227
|
+
"shadow",
|
|
35228
|
+
"transparent",
|
|
35229
|
+
"currentcolor",
|
|
35230
|
+
"inherit",
|
|
35231
|
+
"initial",
|
|
35232
|
+
"unset",
|
|
35233
|
+
"revert",
|
|
35234
|
+
"hover",
|
|
35235
|
+
"focus",
|
|
35236
|
+
"active",
|
|
35237
|
+
"disabled",
|
|
35238
|
+
"checked",
|
|
35239
|
+
"indeterminate",
|
|
35240
|
+
"open",
|
|
35241
|
+
"close",
|
|
35242
|
+
"expanded",
|
|
35243
|
+
"collapsed",
|
|
35244
|
+
"selected",
|
|
35245
|
+
"pressed",
|
|
35246
|
+
// Web/CSS
|
|
35247
|
+
"div",
|
|
35248
|
+
"span",
|
|
35249
|
+
"p",
|
|
35250
|
+
"a",
|
|
35251
|
+
"img",
|
|
35252
|
+
"ul",
|
|
35253
|
+
"ol",
|
|
35254
|
+
"li",
|
|
35255
|
+
"table",
|
|
35256
|
+
"tr",
|
|
35257
|
+
"td",
|
|
35258
|
+
"th",
|
|
35259
|
+
"thead",
|
|
35260
|
+
"tbody",
|
|
35261
|
+
"tfoot",
|
|
35262
|
+
"caption",
|
|
35263
|
+
"figure",
|
|
35264
|
+
"figcaption",
|
|
35265
|
+
"main",
|
|
35266
|
+
"section",
|
|
35267
|
+
"article",
|
|
35268
|
+
"aside",
|
|
35269
|
+
"header",
|
|
35270
|
+
"footer",
|
|
35271
|
+
"nav",
|
|
35272
|
+
"form",
|
|
35273
|
+
"input",
|
|
35274
|
+
"button",
|
|
35275
|
+
"select",
|
|
35276
|
+
"option",
|
|
35277
|
+
"textarea",
|
|
35278
|
+
"label",
|
|
35279
|
+
"fieldset",
|
|
35280
|
+
"legend",
|
|
35281
|
+
"details",
|
|
35282
|
+
"summary",
|
|
35283
|
+
"dialog",
|
|
35284
|
+
"menu",
|
|
35285
|
+
"menuitem",
|
|
35286
|
+
"template",
|
|
35287
|
+
"slot",
|
|
35288
|
+
"picture",
|
|
35289
|
+
"source",
|
|
35290
|
+
"track",
|
|
35291
|
+
"video",
|
|
35292
|
+
"audio",
|
|
35293
|
+
"canvas",
|
|
35294
|
+
"svg",
|
|
35295
|
+
"iframe",
|
|
35296
|
+
"embed",
|
|
35297
|
+
"object",
|
|
35298
|
+
"portal",
|
|
35299
|
+
// Common business terms
|
|
35300
|
+
"api",
|
|
35301
|
+
"cli",
|
|
35302
|
+
"ui",
|
|
35303
|
+
"ux",
|
|
35304
|
+
"sdk",
|
|
35305
|
+
"ide",
|
|
35306
|
+
"cli",
|
|
35307
|
+
"docs",
|
|
35308
|
+
"doc",
|
|
35309
|
+
"blog",
|
|
35310
|
+
"post",
|
|
35311
|
+
"page",
|
|
35312
|
+
"view",
|
|
35313
|
+
"tab",
|
|
35314
|
+
"panel",
|
|
35315
|
+
"card",
|
|
35316
|
+
"list",
|
|
35317
|
+
"grid",
|
|
35318
|
+
"form",
|
|
35319
|
+
"modal",
|
|
35320
|
+
"menu",
|
|
35321
|
+
"button",
|
|
35322
|
+
"icon",
|
|
35323
|
+
"avatar",
|
|
35324
|
+
"badge",
|
|
35325
|
+
"chip",
|
|
35326
|
+
"tooltip",
|
|
35327
|
+
"popover",
|
|
35328
|
+
"dropdown",
|
|
35329
|
+
"banner",
|
|
35330
|
+
"alert",
|
|
35331
|
+
"toast",
|
|
35332
|
+
"notification",
|
|
35333
|
+
"drawer",
|
|
35334
|
+
"sidebar",
|
|
35335
|
+
"navbar",
|
|
35336
|
+
"header",
|
|
35337
|
+
"footer",
|
|
35338
|
+
"hero",
|
|
35339
|
+
"cta",
|
|
35340
|
+
"cta-primary",
|
|
35341
|
+
"cta-secondary",
|
|
35342
|
+
"pricing",
|
|
35343
|
+
"price",
|
|
35344
|
+
"cost",
|
|
35345
|
+
"rate",
|
|
35346
|
+
"percent",
|
|
35347
|
+
"pct",
|
|
35348
|
+
"count",
|
|
35349
|
+
"total",
|
|
35350
|
+
"small",
|
|
35351
|
+
"medium",
|
|
35352
|
+
"large",
|
|
35353
|
+
"xl",
|
|
35354
|
+
"xxl",
|
|
35355
|
+
"tiny",
|
|
35356
|
+
"huge"
|
|
34527
35357
|
]);
|
|
34528
35358
|
var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
34529
35359
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
@@ -34561,7 +35391,12 @@ function collectExports(cwd) {
|
|
|
34561
35391
|
/\bexport\s+class\s+([A-Za-z_$][\w$]*)/g,
|
|
34562
35392
|
/\bexport\s+interface\s+([A-Za-z_$][\w$]*)/g,
|
|
34563
35393
|
/\bexport\s+type\s+([A-Za-z_$][\w$]*)/g,
|
|
34564
|
-
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g
|
|
35394
|
+
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
|
|
35395
|
+
// v0.18.6: also collect field names from `export interface` and
|
|
35396
|
+
// `export type` declarations. Without this, fields like
|
|
35397
|
+
// `crossFileDrift`, `aiQuality`, `engineeringHygiene` are
|
|
35398
|
+
// flagged as stale even though they're valid type fields.
|
|
35399
|
+
/^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
|
|
34565
35400
|
]) {
|
|
34566
35401
|
let m;
|
|
34567
35402
|
while ((m = re.exec(source)) !== null) {
|
|
@@ -34572,6 +35407,21 @@ function collectExports(cwd) {
|
|
|
34572
35407
|
}
|
|
34573
35408
|
return out;
|
|
34574
35409
|
}
|
|
35410
|
+
function looksLikeProseLabel(inside) {
|
|
35411
|
+
const trimmed = inside.trim();
|
|
35412
|
+
if (trimmed.length === 0) return false;
|
|
35413
|
+
if (trimmed.startsWith("`") || trimmed.startsWith("/")) return true;
|
|
35414
|
+
if (trimmed.includes("`")) return true;
|
|
35415
|
+
if (/\d+\s+[a-z]/i.test(trimmed)) return true;
|
|
35416
|
+
const parts = trimmed.split(",").map((s) => s.trim());
|
|
35417
|
+
if (parts.length >= 3) {
|
|
35418
|
+
const allNumeric = parts.every((p) => /^\d+\.?\d*$/.test(p));
|
|
35419
|
+
if (!allNumeric) return true;
|
|
35420
|
+
}
|
|
35421
|
+
if (trimmed.length > 40 && !trimmed.includes(",")) return true;
|
|
35422
|
+
if (trimmed.includes("\u2014") || trimmed.includes("\u2013")) return true;
|
|
35423
|
+
return false;
|
|
35424
|
+
}
|
|
34575
35425
|
var staleFunctionReferenceRule = createRule({
|
|
34576
35426
|
id: "docs/stale-function-reference",
|
|
34577
35427
|
category: "docs",
|
|
@@ -34591,8 +35441,39 @@ var staleFunctionReferenceRule = createRule({
|
|
|
34591
35441
|
if (text.length < 3) continue;
|
|
34592
35442
|
if (RESERVED.has(text.toLowerCase())) continue;
|
|
34593
35443
|
if (context.exports.has(text)) continue;
|
|
34594
|
-
const
|
|
34595
|
-
|
|
35444
|
+
const lineEnd = source.indexOf("\n", span.index);
|
|
35445
|
+
const restOfLine = source.slice(
|
|
35446
|
+
span.index,
|
|
35447
|
+
lineEnd === -1 ? source.length : lineEnd
|
|
35448
|
+
);
|
|
35449
|
+
const closeTick = restOfLine.indexOf("`", 1);
|
|
35450
|
+
if (closeTick === -1) continue;
|
|
35451
|
+
const afterTick = restOfLine.slice(closeTick + 1);
|
|
35452
|
+
const directCall = /^\s*\(/.test(afterTick);
|
|
35453
|
+
let identifierRepeats = false;
|
|
35454
|
+
if (!directCall) {
|
|
35455
|
+
const afterSpan = restOfLine.slice(closeTick + 1);
|
|
35456
|
+
const needle = text + "(";
|
|
35457
|
+
identifierRepeats = afterSpan.indexOf(needle) !== -1;
|
|
35458
|
+
}
|
|
35459
|
+
if (!directCall && !identifierRepeats) continue;
|
|
35460
|
+
const beforeTickIdx = span.index - 1;
|
|
35461
|
+
const beforeChar = beforeTickIdx >= 0 ? source[beforeTickIdx] : "";
|
|
35462
|
+
if (beforeChar === "." || beforeChar === "|") continue;
|
|
35463
|
+
const parenStart = restOfLine.indexOf("(", closeTick);
|
|
35464
|
+
const parenEnd = restOfLine.indexOf(")", parenStart);
|
|
35465
|
+
if (parenStart !== -1 && parenEnd !== -1) {
|
|
35466
|
+
const inside = restOfLine.slice(parenStart + 1, parenEnd);
|
|
35467
|
+
const trimmed = inside.trim();
|
|
35468
|
+
if (looksLikeProseLabel(inside)) continue;
|
|
35469
|
+
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(
|
|
35470
|
+
inside
|
|
35471
|
+
) || // Short single-word label (≤ 24 chars, no `,`,
|
|
35472
|
+
// doesn't look like a function arg). Real function
|
|
35473
|
+
// calls are usually longer or contain commas.
|
|
35474
|
+
trimmed.length > 0 && trimmed.length <= 24 && !trimmed.includes(",") && /[a-zA-Z]/.test(trimmed));
|
|
35475
|
+
if (looksLikeTypeAnnotation) continue;
|
|
35476
|
+
}
|
|
34596
35477
|
issues.push({
|
|
34597
35478
|
ruleId: "docs/stale-function-reference",
|
|
34598
35479
|
category: "docs",
|
|
@@ -34664,7 +35545,33 @@ var ENGLISH_WORD_DENYLIST = /* @__PURE__ */ new Set([
|
|
|
34664
35545
|
"jsx",
|
|
34665
35546
|
"ok",
|
|
34666
35547
|
"no",
|
|
34667
|
-
"yes"
|
|
35548
|
+
"yes",
|
|
35549
|
+
// v0.18.6: common English adjectives / adverbs that frequently
|
|
35550
|
+
// appear in backticked prose but are not package names.
|
|
35551
|
+
"aspirational",
|
|
35552
|
+
"concrete",
|
|
35553
|
+
"abstract",
|
|
35554
|
+
"inline",
|
|
35555
|
+
"exposed",
|
|
35556
|
+
"deprecated",
|
|
35557
|
+
"experimental",
|
|
35558
|
+
"stable",
|
|
35559
|
+
"beta",
|
|
35560
|
+
"alpha",
|
|
35561
|
+
"wip",
|
|
35562
|
+
"draft",
|
|
35563
|
+
"final",
|
|
35564
|
+
"shim",
|
|
35565
|
+
"polyfill",
|
|
35566
|
+
"stub",
|
|
35567
|
+
"mock",
|
|
35568
|
+
"fake",
|
|
35569
|
+
"real",
|
|
35570
|
+
"false",
|
|
35571
|
+
"true",
|
|
35572
|
+
"optional",
|
|
35573
|
+
"required",
|
|
35574
|
+
"default"
|
|
34668
35575
|
]);
|
|
34669
35576
|
var stalePackageReferenceRule = createRule({
|
|
34670
35577
|
id: "docs/stale-package-reference",
|
|
@@ -34826,7 +35733,9 @@ var brokenLinkRule = createRule({
|
|
|
34826
35733
|
if (target.startsWith("#")) continue;
|
|
34827
35734
|
if (target.startsWith("//")) continue;
|
|
34828
35735
|
if (target.startsWith("/")) continue;
|
|
34829
|
-
const
|
|
35736
|
+
const filePart = target.split("#")[0] ?? target;
|
|
35737
|
+
if (filePart === "") continue;
|
|
35738
|
+
const resolved = (0, import_node_path6.join)(docDir, filePart);
|
|
34830
35739
|
if ((0, import_node_fs6.existsSync)(resolved)) continue;
|
|
34831
35740
|
issues.push({
|
|
34832
35741
|
ruleId: "docs/broken-link",
|
|
@@ -39598,6 +40507,11 @@ var builtinRules = [
|
|
|
39598
40507
|
missingNotNullRule,
|
|
39599
40508
|
namingInconsistencyRule,
|
|
39600
40509
|
sqlConcatRule,
|
|
40510
|
+
deadBranchRule,
|
|
40511
|
+
unreachableRule,
|
|
40512
|
+
unusedImportRule,
|
|
40513
|
+
unusedLocalRule,
|
|
40514
|
+
unusedParameterRule,
|
|
39601
40515
|
brokenLinkRule,
|
|
39602
40516
|
expiredCodeExampleRule,
|
|
39603
40517
|
staleFunctionReferenceRule,
|
|
@@ -40605,6 +41519,61 @@ var signal_strength_default = {
|
|
|
40605
41519
|
_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
41520
|
aiSpecific: true
|
|
40607
41521
|
},
|
|
41522
|
+
"dead/unused-import": {
|
|
41523
|
+
recall: 0,
|
|
41524
|
+
fpRate: 0,
|
|
41525
|
+
ratio: 0,
|
|
41526
|
+
precision: 0,
|
|
41527
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41528
|
+
verdict: "DORMANT",
|
|
41529
|
+
defaultOff: true,
|
|
41530
|
+
_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.",
|
|
41531
|
+
aiSpecific: true
|
|
41532
|
+
},
|
|
41533
|
+
"dead/unused-local": {
|
|
41534
|
+
recall: 0,
|
|
41535
|
+
fpRate: 0,
|
|
41536
|
+
ratio: 0,
|
|
41537
|
+
precision: 0,
|
|
41538
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41539
|
+
verdict: "DORMANT",
|
|
41540
|
+
defaultOff: true,
|
|
41541
|
+
_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).",
|
|
41542
|
+
aiSpecific: true
|
|
41543
|
+
},
|
|
41544
|
+
"dead/unused-parameter": {
|
|
41545
|
+
recall: 0,
|
|
41546
|
+
fpRate: 0,
|
|
41547
|
+
ratio: 0,
|
|
41548
|
+
precision: 0,
|
|
41549
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41550
|
+
verdict: "DORMANT",
|
|
41551
|
+
defaultOff: true,
|
|
41552
|
+
_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.",
|
|
41553
|
+
aiSpecific: true
|
|
41554
|
+
},
|
|
41555
|
+
"dead/dead-branch": {
|
|
41556
|
+
recall: 0,
|
|
41557
|
+
fpRate: 0,
|
|
41558
|
+
ratio: 0,
|
|
41559
|
+
precision: 0,
|
|
41560
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41561
|
+
verdict: "DORMANT",
|
|
41562
|
+
defaultOff: true,
|
|
41563
|
+
_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.",
|
|
41564
|
+
aiSpecific: true
|
|
41565
|
+
},
|
|
41566
|
+
"dead/unreachable": {
|
|
41567
|
+
recall: 0,
|
|
41568
|
+
fpRate: 0,
|
|
41569
|
+
ratio: 0,
|
|
41570
|
+
precision: 0,
|
|
41571
|
+
lastCalibratedAt: "2026-06-30T00:00:00Z",
|
|
41572
|
+
verdict: "DORMANT",
|
|
41573
|
+
defaultOff: true,
|
|
41574
|
+
_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.",
|
|
41575
|
+
aiSpecific: true
|
|
41576
|
+
},
|
|
40608
41577
|
"docs/stale-package-reference": {
|
|
40609
41578
|
recall: 0,
|
|
40610
41579
|
fpRate: 0,
|