slopbrick 0.18.9 → 0.20.0
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 +1401 -779
- package/dist/engine/worker.js +1402 -781
- package/dist/index.cjs +4037 -3123
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4039 -3125
- package/package.json +2 -2
package/dist/engine/worker.js
CHANGED
|
@@ -1,419 +1,13 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __esm = (fn, res) => function __init() {
|
|
4
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
-
};
|
|
6
2
|
var __export = (target, all) => {
|
|
7
3
|
for (var name in all)
|
|
8
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
5
|
};
|
|
10
6
|
|
|
11
|
-
// src/engine/parser-rust.ts
|
|
12
|
-
import Parser from "tree-sitter";
|
|
13
|
-
import Rust from "tree-sitter-rust";
|
|
14
|
-
function getRustParser() {
|
|
15
|
-
if (cachedParser) return { ok: true, parser: cachedParser };
|
|
16
|
-
if (loadError) return { ok: false, error: loadError };
|
|
17
|
-
try {
|
|
18
|
-
rustLanguage = Rust;
|
|
19
|
-
if (!rustLanguage || typeof rustLanguage !== "object") {
|
|
20
|
-
throw new Error("tree-sitter-rust: language export is missing or malformed");
|
|
21
|
-
}
|
|
22
|
-
const parser = new Parser();
|
|
23
|
-
parser.setLanguage(rustLanguage);
|
|
24
|
-
cachedParser = parser;
|
|
25
|
-
return { ok: true, parser };
|
|
26
|
-
} catch (err) {
|
|
27
|
-
loadError = err instanceof Error ? err : new Error(String(err));
|
|
28
|
-
return { ok: false, error: loadError };
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function effectiveParser() {
|
|
32
|
-
if (forcedFailure) return { ok: false, error: forcedFailure };
|
|
33
|
-
return getRustParser();
|
|
34
|
-
}
|
|
35
|
-
function parseRust(source) {
|
|
36
|
-
const result = effectiveParser();
|
|
37
|
-
if (!result.ok) return null;
|
|
38
|
-
if (!source || source.trim() === "") return null;
|
|
39
|
-
try {
|
|
40
|
-
const rawTree = result.parser.parse(source);
|
|
41
|
-
if (!rawTree) return null;
|
|
42
|
-
const tree = rawTree;
|
|
43
|
-
if (!tree || !tree.rootNode) return null;
|
|
44
|
-
if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
return tree;
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function isRustParserAvailable() {
|
|
53
|
-
const result = getRustParser();
|
|
54
|
-
return result.ok;
|
|
55
|
-
}
|
|
56
|
-
var cachedParser, rustLanguage, loadError, forcedFailure;
|
|
57
|
-
var init_parser_rust = __esm({
|
|
58
|
-
"src/engine/parser-rust.ts"() {
|
|
59
|
-
"use strict";
|
|
60
|
-
cachedParser = null;
|
|
61
|
-
rustLanguage = null;
|
|
62
|
-
loadError = null;
|
|
63
|
-
forcedFailure = null;
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// src/engine/visitors/rust.ts
|
|
68
|
-
function parseRustFile(filePath, source, options = {}) {
|
|
69
|
-
const empty = {
|
|
70
|
-
imports: [],
|
|
71
|
-
functions: [],
|
|
72
|
-
structs: [],
|
|
73
|
-
traits: [],
|
|
74
|
-
impls: []
|
|
75
|
-
};
|
|
76
|
-
if (!isRustParserAvailable() || options.forceFallback) {
|
|
77
|
-
return empty;
|
|
78
|
-
}
|
|
79
|
-
const tree = parseRust(source);
|
|
80
|
-
if (!tree) return empty;
|
|
81
|
-
return walkRustTree(tree);
|
|
82
|
-
}
|
|
83
|
-
function walkRustTree(tree) {
|
|
84
|
-
const ctx = { inTestConfig: false };
|
|
85
|
-
const out = {
|
|
86
|
-
imports: [],
|
|
87
|
-
functions: [],
|
|
88
|
-
structs: [],
|
|
89
|
-
traits: [],
|
|
90
|
-
impls: []
|
|
91
|
-
};
|
|
92
|
-
walkChildren(tree.rootNode, ctx, out);
|
|
93
|
-
return out;
|
|
94
|
-
}
|
|
95
|
-
function walkChildren(node, ctx, out) {
|
|
96
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
97
|
-
const child = node.namedChild(i);
|
|
98
|
-
if (!child) continue;
|
|
99
|
-
visitNode(child, ctx, out);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function visitNode(node, ctx, out) {
|
|
103
|
-
const innerCtx = { ...ctx };
|
|
104
|
-
const attrState = readPrecedingAttributes(node);
|
|
105
|
-
switch (node.type) {
|
|
106
|
-
case "use_declaration": {
|
|
107
|
-
out.imports.push(extractUse(node));
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
case "function_item": {
|
|
111
|
-
const isMethod = isInImplBlock(node);
|
|
112
|
-
out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
|
|
113
|
-
walkChildren(getField(node, "body") ?? node, innerCtx, out);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
case "struct_item": {
|
|
117
|
-
out.structs.push(extractStruct(node, attrState));
|
|
118
|
-
const body = getField(node, "body");
|
|
119
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
case "trait_item": {
|
|
123
|
-
out.traits.push(extractTrait(node, attrState));
|
|
124
|
-
const body = getField(node, "body");
|
|
125
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
case "impl_item": {
|
|
129
|
-
const implEntry = extractImpl(node, attrState, innerCtx);
|
|
130
|
-
out.impls.push(implEntry.entry);
|
|
131
|
-
const body = getField(node, "body");
|
|
132
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
case "mod_item": {
|
|
136
|
-
const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
|
|
137
|
-
const modCtx = { ...innerCtx, inTestConfig: modIsTest };
|
|
138
|
-
const body = getField(node, "body");
|
|
139
|
-
if (body) walkChildren(body, modCtx, out);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
default: {
|
|
143
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
144
|
-
const child = node.namedChild(i);
|
|
145
|
-
if (child) visitNode(child, innerCtx, out);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function readPrecedingAttributes(node) {
|
|
151
|
-
const state = {
|
|
152
|
-
isTestCfg: false,
|
|
153
|
-
isTest: false,
|
|
154
|
-
isPub: false,
|
|
155
|
-
derives: []
|
|
156
|
-
};
|
|
157
|
-
const parent = node.parent;
|
|
158
|
-
if (!parent) return state;
|
|
159
|
-
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
160
|
-
const sibling = parent.namedChild(i);
|
|
161
|
-
if (!sibling || sibling === node) continue;
|
|
162
|
-
if (sibling.endIndex > node.startIndex) continue;
|
|
163
|
-
if (sibling.type !== "attribute_item") continue;
|
|
164
|
-
decodeAttribute(sibling, state);
|
|
165
|
-
}
|
|
166
|
-
return state;
|
|
167
|
-
}
|
|
168
|
-
function decodeAttribute(attr, state) {
|
|
169
|
-
for (let i = 0; i < attr.namedChildCount; i++) {
|
|
170
|
-
const child = attr.namedChild(i);
|
|
171
|
-
if (!child || child.type !== "attribute") continue;
|
|
172
|
-
const name = firstIdentifier(child);
|
|
173
|
-
if (!name) continue;
|
|
174
|
-
if (name === "cfg") {
|
|
175
|
-
const cfgText = child.text;
|
|
176
|
-
if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
|
|
177
|
-
state.isTestCfg = true;
|
|
178
|
-
}
|
|
179
|
-
} else if (name === "test") {
|
|
180
|
-
state.isTest = true;
|
|
181
|
-
} else if (name === "derive") {
|
|
182
|
-
for (let j = 0; j < child.namedChildCount; j++) {
|
|
183
|
-
const inner = child.namedChild(j);
|
|
184
|
-
if (inner) {
|
|
185
|
-
const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
|
|
186
|
-
for (const m of matches) state.derives.push(m[1]);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function firstIdentifier(node) {
|
|
193
|
-
const text = node.text;
|
|
194
|
-
const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
195
|
-
return idMatch ? idMatch[1] : null;
|
|
196
|
-
}
|
|
197
|
-
function extractUse(node) {
|
|
198
|
-
const text = node.text;
|
|
199
|
-
const argument = node.namedChild(0);
|
|
200
|
-
const argField = node.childForFieldName("argument");
|
|
201
|
-
const path = argField?.text ?? argument?.text ?? "";
|
|
202
|
-
const names = [];
|
|
203
|
-
let isGlob = false;
|
|
204
|
-
const useListNode = findUseListNode(argument);
|
|
205
|
-
if (useListNode) {
|
|
206
|
-
for (let i = 0; i < useListNode.namedChildCount; i++) {
|
|
207
|
-
const item = useListNode.namedChild(i);
|
|
208
|
-
if (item.type === "use_wildcard") {
|
|
209
|
-
isGlob = true;
|
|
210
|
-
} else if (item.type === "identifier") {
|
|
211
|
-
names.push({ name: item.text });
|
|
212
|
-
} else if (item.type === "use_as_clause") {
|
|
213
|
-
const alias = item.childForFieldName("alias");
|
|
214
|
-
const binding = item.childForFieldName("path");
|
|
215
|
-
if (binding) {
|
|
216
|
-
names.push({ name: binding.text, alias: alias?.text });
|
|
217
|
-
} else {
|
|
218
|
-
names.push({ name: item.text });
|
|
219
|
-
}
|
|
220
|
-
} else if (item.type === "scoped_identifier") {
|
|
221
|
-
const idents = collectIdentifiers(item);
|
|
222
|
-
names.push({ name: idents[idents.length - 1] ?? item.text });
|
|
223
|
-
} else if (item.text.includes(" as ")) {
|
|
224
|
-
const [head, alias] = item.text.split(/\s+as\s+/);
|
|
225
|
-
names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
|
|
226
|
-
} else {
|
|
227
|
-
names.push({ name: item.text });
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
} else if (argument?.type === "use_wildcard") {
|
|
231
|
-
isGlob = true;
|
|
232
|
-
} else if (argument) {
|
|
233
|
-
const idents = collectIdentifiers(argument);
|
|
234
|
-
const last = idents[idents.length - 1] ?? argument.text;
|
|
235
|
-
names.push({ name: last });
|
|
236
|
-
const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
|
|
237
|
-
if (asMatch) names[0].alias = asMatch[1];
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
path: path.trim(),
|
|
241
|
-
names,
|
|
242
|
-
isGlob,
|
|
243
|
-
line: node.startPosition.row + 1,
|
|
244
|
-
column: node.startPosition.column
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
|
|
248
|
-
const nameNode = node.childForFieldName("name");
|
|
249
|
-
const name = nameNode?.text ?? "<anon>";
|
|
250
|
-
const params = node.childForFieldName("parameters");
|
|
251
|
-
const body = node.childForFieldName("body");
|
|
252
|
-
const visibilityMod = node.namedChild(0);
|
|
253
|
-
const isPublic = visibilityMod?.type === "visibility_modifier";
|
|
254
|
-
let receiver;
|
|
255
|
-
if (isMethod && params) {
|
|
256
|
-
const selfParam = findSelfParameter(params);
|
|
257
|
-
if (selfParam) receiver = selfParam.text;
|
|
258
|
-
}
|
|
259
|
-
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
260
|
-
const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
|
|
261
|
-
return {
|
|
262
|
-
name,
|
|
263
|
-
line: node.startPosition.row + 1,
|
|
264
|
-
column: node.startPosition.column,
|
|
265
|
-
isPublic,
|
|
266
|
-
isMethod,
|
|
267
|
-
receiver,
|
|
268
|
-
bodyLines,
|
|
269
|
-
inTestConfig
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
function extractStruct(node, attrs) {
|
|
273
|
-
const nameNode = node.childForFieldName("name");
|
|
274
|
-
return {
|
|
275
|
-
name: nameNode?.text ?? "<anon>",
|
|
276
|
-
line: node.startPosition.row + 1,
|
|
277
|
-
column: node.startPosition.column,
|
|
278
|
-
isPublic: hasVisibility(node),
|
|
279
|
-
isDerive: attrs.derives.length > 0,
|
|
280
|
-
derives: [...attrs.derives]
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
function extractTrait(node, _attrs) {
|
|
284
|
-
const nameNode = node.childForFieldName("name");
|
|
285
|
-
return {
|
|
286
|
-
name: nameNode?.text ?? "<anon>",
|
|
287
|
-
line: node.startPosition.row + 1,
|
|
288
|
-
column: node.startPosition.column,
|
|
289
|
-
isPublic: hasVisibility(node)
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
function extractImpl(node, _attrs, _ctx) {
|
|
293
|
-
const traitNode = node.childForFieldName("trait");
|
|
294
|
-
const typeNode = node.childForFieldName("type");
|
|
295
|
-
const typeText = typeNode?.text ?? "<anon>";
|
|
296
|
-
const traitText = traitNode?.text;
|
|
297
|
-
const body = node.childForFieldName("body");
|
|
298
|
-
const methods = [];
|
|
299
|
-
if (body) {
|
|
300
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
301
|
-
const child = body.namedChild(i);
|
|
302
|
-
if (child?.type === "function_item") {
|
|
303
|
-
const m = child.childForFieldName("name");
|
|
304
|
-
if (m) methods.push(m.text);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return {
|
|
309
|
-
entry: {
|
|
310
|
-
type: typeText,
|
|
311
|
-
trait: traitText ?? void 0,
|
|
312
|
-
methods,
|
|
313
|
-
line: node.startPosition.row + 1,
|
|
314
|
-
column: node.startPosition.column
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
function findUseListNode(argument) {
|
|
319
|
-
if (!argument) return null;
|
|
320
|
-
if (argument.type === "use_list") return argument;
|
|
321
|
-
if (argument.type === "scoped_use_list") {
|
|
322
|
-
for (let i = 0; i < argument.namedChildCount; i++) {
|
|
323
|
-
const c = argument.namedChild(i);
|
|
324
|
-
if (c?.type === "use_list") return c;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
function getField(node, fieldName) {
|
|
330
|
-
return node.childForFieldName(fieldName);
|
|
331
|
-
}
|
|
332
|
-
function collectIdentifiers(node) {
|
|
333
|
-
const out = [];
|
|
334
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
335
|
-
const child = node.namedChild(i);
|
|
336
|
-
if (!child) continue;
|
|
337
|
-
if (child.type === "identifier" || child.type === "type_identifier") {
|
|
338
|
-
out.push(child.text);
|
|
339
|
-
} else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
|
|
340
|
-
out.push(...collectIdentifiers(child));
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return out;
|
|
344
|
-
}
|
|
345
|
-
function hasVisibility(node) {
|
|
346
|
-
return node.namedChild(0)?.type === "visibility_modifier";
|
|
347
|
-
}
|
|
348
|
-
function isInImplBlock(node) {
|
|
349
|
-
for (let p = node.parent; p; p = p.parent) {
|
|
350
|
-
if (p.type === "impl_item") return true;
|
|
351
|
-
if (p.type === "function_item" || p.type === "source_file") return false;
|
|
352
|
-
}
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
function findSelfParameter(params) {
|
|
356
|
-
for (let i = 0; i < params.namedChildCount; i++) {
|
|
357
|
-
const child = params.namedChild(i);
|
|
358
|
-
if (child?.type === "self_parameter") return child;
|
|
359
|
-
}
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
function isFunctionAttrTest(node) {
|
|
363
|
-
void node;
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
var SERVICE_SUFFIXES, SERVICE_SUFFIX_GROUP, RUST_SERVICE_STRUCT_RE, RUST_SERVICE_IMPL_RE;
|
|
367
|
-
var init_rust = __esm({
|
|
368
|
-
"src/engine/visitors/rust.ts"() {
|
|
369
|
-
"use strict";
|
|
370
|
-
init_parser_rust();
|
|
371
|
-
SERVICE_SUFFIXES = [
|
|
372
|
-
"Service",
|
|
373
|
-
"Manager",
|
|
374
|
-
"Handler",
|
|
375
|
-
"Repository",
|
|
376
|
-
"Controller",
|
|
377
|
-
"Helper",
|
|
378
|
-
"Factory",
|
|
379
|
-
"Provider",
|
|
380
|
-
"Store",
|
|
381
|
-
"API",
|
|
382
|
-
"Client",
|
|
383
|
-
"Adapter",
|
|
384
|
-
"Resolver",
|
|
385
|
-
"Mapper",
|
|
386
|
-
"Transformer",
|
|
387
|
-
"Serializer",
|
|
388
|
-
"Validator",
|
|
389
|
-
"Strategy",
|
|
390
|
-
"Facade",
|
|
391
|
-
"Decorator",
|
|
392
|
-
"Observer",
|
|
393
|
-
"Builder",
|
|
394
|
-
"Command",
|
|
395
|
-
"Processor",
|
|
396
|
-
"Worker",
|
|
397
|
-
"Job",
|
|
398
|
-
"Actor",
|
|
399
|
-
"Executor"
|
|
400
|
-
];
|
|
401
|
-
SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
|
|
402
|
-
RUST_SERVICE_STRUCT_RE = new RegExp(
|
|
403
|
-
`^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
404
|
-
"gm"
|
|
405
|
-
);
|
|
406
|
-
RUST_SERVICE_IMPL_RE = new RegExp(
|
|
407
|
-
`^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
408
|
-
"gm"
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
7
|
// src/engine/worker.ts
|
|
414
8
|
import { isMainThread, parentPort, workerData } from "worker_threads";
|
|
415
|
-
import { extname as
|
|
416
|
-
import { join as
|
|
9
|
+
import { extname as extname4 } from "path";
|
|
10
|
+
import { join as join11 } from "path";
|
|
417
11
|
|
|
418
12
|
// ../engine/dist/index.js
|
|
419
13
|
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
@@ -5435,76 +5029,6 @@ function tokenizeIdentifiers(source) {
|
|
|
5435
5029
|
return tokens;
|
|
5436
5030
|
}
|
|
5437
5031
|
var SQRT_2_TIMES_LN_2 = Math.sqrt(2 * Math.LN2);
|
|
5438
|
-
function ecdfAt(sortedSamples, x) {
|
|
5439
|
-
let lo = 0;
|
|
5440
|
-
let hi = sortedSamples.length;
|
|
5441
|
-
while (lo < hi) {
|
|
5442
|
-
const mid = lo + hi >>> 1;
|
|
5443
|
-
if (sortedSamples[mid] <= x) lo = mid + 1;
|
|
5444
|
-
else hi = mid;
|
|
5445
|
-
}
|
|
5446
|
-
return lo / sortedSamples.length;
|
|
5447
|
-
}
|
|
5448
|
-
function ksStatistic(sampleA, sampleB) {
|
|
5449
|
-
if (sampleA.length === 0 || sampleB.length === 0) return 1;
|
|
5450
|
-
const sortedA = [...sampleA].sort((a, b) => a - b);
|
|
5451
|
-
const sortedB = [...sampleB].sort((a, b) => a - b);
|
|
5452
|
-
const allPoints = [...sortedA, ...sortedB].sort((a, b) => a - b);
|
|
5453
|
-
let maxDiff = 0;
|
|
5454
|
-
for (const x of allPoints) {
|
|
5455
|
-
const fa = ecdfAt(sortedA, x);
|
|
5456
|
-
const fb = ecdfAt(sortedB, x);
|
|
5457
|
-
const diff = Math.abs(fa - fb);
|
|
5458
|
-
if (diff > maxDiff) maxDiff = diff;
|
|
5459
|
-
}
|
|
5460
|
-
return maxDiff;
|
|
5461
|
-
}
|
|
5462
|
-
function ksPValue(statistic, n, m) {
|
|
5463
|
-
if (n === 0 || m === 0) return 1;
|
|
5464
|
-
if (statistic < 0) return 1;
|
|
5465
|
-
if (statistic > 1) return 0;
|
|
5466
|
-
const lambda = Math.sqrt(n * m / (n + m)) * statistic;
|
|
5467
|
-
if (lambda > 3.6) return 0;
|
|
5468
|
-
let p = 0;
|
|
5469
|
-
for (let j = 1; j < 1e3; j++) {
|
|
5470
|
-
const term = 2 * Math.pow(-1, j - 1) * Math.exp(-2 * j * j * lambda * lambda);
|
|
5471
|
-
p += term;
|
|
5472
|
-
if (Math.abs(term) < 1e-15) break;
|
|
5473
|
-
}
|
|
5474
|
-
return Math.max(0, Math.min(1, p));
|
|
5475
|
-
}
|
|
5476
|
-
function ksTest(sampleA, sampleB, alpha = 0.05) {
|
|
5477
|
-
const statistic = ksStatistic(sampleA, sampleB);
|
|
5478
|
-
const pValue = ksPValue(statistic, sampleA.length, sampleB.length);
|
|
5479
|
-
return {
|
|
5480
|
-
statistic,
|
|
5481
|
-
pValue,
|
|
5482
|
-
significant: pValue < alpha,
|
|
5483
|
-
n: sampleA.length,
|
|
5484
|
-
m: sampleB.length
|
|
5485
|
-
};
|
|
5486
|
-
}
|
|
5487
|
-
function multiFeatureKsTest(features, baselines, alpha = 0.05) {
|
|
5488
|
-
const featureNames = [...features.keys()];
|
|
5489
|
-
const k = featureNames.length;
|
|
5490
|
-
const bonferroniAlpha = k > 0 ? alpha / k : alpha;
|
|
5491
|
-
const perFeature = /* @__PURE__ */ new Map();
|
|
5492
|
-
const significantFeatures = [];
|
|
5493
|
-
for (const name of featureNames) {
|
|
5494
|
-
const sample = features.get(name);
|
|
5495
|
-
const baseline = baselines.get(name);
|
|
5496
|
-
if (!sample || !baseline) continue;
|
|
5497
|
-
const result = ksTest(sample, baseline, bonferroniAlpha);
|
|
5498
|
-
perFeature.set(name, result);
|
|
5499
|
-
if (result.significant) significantFeatures.push(name);
|
|
5500
|
-
}
|
|
5501
|
-
return {
|
|
5502
|
-
perFeature,
|
|
5503
|
-
bonferroniAlpha,
|
|
5504
|
-
anySignificant: significantFeatures.length > 0,
|
|
5505
|
-
significantFeatures
|
|
5506
|
-
};
|
|
5507
|
-
}
|
|
5508
5032
|
|
|
5509
5033
|
// src/engine/visitors/react.ts
|
|
5510
5034
|
function isObject(node) {
|
|
@@ -7304,8 +6828,396 @@ function dispatchNode(node, parent, path, vctx) {
|
|
|
7304
6828
|
return false;
|
|
7305
6829
|
}
|
|
7306
6830
|
|
|
6831
|
+
// src/engine/parser-rust.ts
|
|
6832
|
+
import Parser from "tree-sitter";
|
|
6833
|
+
import Rust from "tree-sitter-rust";
|
|
6834
|
+
var cachedParser = null;
|
|
6835
|
+
var rustLanguage = null;
|
|
6836
|
+
var loadError = null;
|
|
6837
|
+
function getRustParser() {
|
|
6838
|
+
if (cachedParser) return { ok: true, parser: cachedParser };
|
|
6839
|
+
if (loadError) return { ok: false, error: loadError };
|
|
6840
|
+
try {
|
|
6841
|
+
rustLanguage = Rust;
|
|
6842
|
+
if (!rustLanguage || typeof rustLanguage !== "object") {
|
|
6843
|
+
throw new Error("tree-sitter-rust: language export is missing or malformed");
|
|
6844
|
+
}
|
|
6845
|
+
const parser = new Parser();
|
|
6846
|
+
parser.setLanguage(rustLanguage);
|
|
6847
|
+
cachedParser = parser;
|
|
6848
|
+
return { ok: true, parser };
|
|
6849
|
+
} catch (err) {
|
|
6850
|
+
loadError = err instanceof Error ? err : new Error(String(err));
|
|
6851
|
+
return { ok: false, error: loadError };
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
var forcedFailure = null;
|
|
6855
|
+
function effectiveParser() {
|
|
6856
|
+
if (forcedFailure) return { ok: false, error: forcedFailure };
|
|
6857
|
+
return getRustParser();
|
|
6858
|
+
}
|
|
6859
|
+
function parseRust(source) {
|
|
6860
|
+
const result = effectiveParser();
|
|
6861
|
+
if (!result.ok) return null;
|
|
6862
|
+
if (!source || source.trim() === "") return null;
|
|
6863
|
+
try {
|
|
6864
|
+
const rawTree = result.parser.parse(source);
|
|
6865
|
+
if (!rawTree) return null;
|
|
6866
|
+
const tree = rawTree;
|
|
6867
|
+
if (!tree || !tree.rootNode) return null;
|
|
6868
|
+
if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
|
|
6869
|
+
return null;
|
|
6870
|
+
}
|
|
6871
|
+
return tree;
|
|
6872
|
+
} catch {
|
|
6873
|
+
return null;
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
function isRustParserAvailable() {
|
|
6877
|
+
const result = getRustParser();
|
|
6878
|
+
return result.ok;
|
|
6879
|
+
}
|
|
6880
|
+
|
|
6881
|
+
// src/engine/visitors/rust.ts
|
|
6882
|
+
var SERVICE_SUFFIXES = [
|
|
6883
|
+
"Service",
|
|
6884
|
+
"Manager",
|
|
6885
|
+
"Handler",
|
|
6886
|
+
"Repository",
|
|
6887
|
+
"Controller",
|
|
6888
|
+
"Helper",
|
|
6889
|
+
"Factory",
|
|
6890
|
+
"Provider",
|
|
6891
|
+
"Store",
|
|
6892
|
+
"API",
|
|
6893
|
+
"Client",
|
|
6894
|
+
"Adapter",
|
|
6895
|
+
"Resolver",
|
|
6896
|
+
"Mapper",
|
|
6897
|
+
"Transformer",
|
|
6898
|
+
"Serializer",
|
|
6899
|
+
"Validator",
|
|
6900
|
+
"Strategy",
|
|
6901
|
+
"Facade",
|
|
6902
|
+
"Decorator",
|
|
6903
|
+
"Observer",
|
|
6904
|
+
"Builder",
|
|
6905
|
+
"Command",
|
|
6906
|
+
"Processor",
|
|
6907
|
+
"Worker",
|
|
6908
|
+
"Job",
|
|
6909
|
+
"Actor",
|
|
6910
|
+
"Executor"
|
|
6911
|
+
];
|
|
6912
|
+
var SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
|
|
6913
|
+
var RUST_SERVICE_STRUCT_RE = new RegExp(
|
|
6914
|
+
`^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
6915
|
+
"gm"
|
|
6916
|
+
);
|
|
6917
|
+
var RUST_SERVICE_IMPL_RE = new RegExp(
|
|
6918
|
+
`^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
6919
|
+
"gm"
|
|
6920
|
+
);
|
|
6921
|
+
function parseRustFile(filePath, source, options = {}) {
|
|
6922
|
+
const empty = {
|
|
6923
|
+
imports: [],
|
|
6924
|
+
functions: [],
|
|
6925
|
+
structs: [],
|
|
6926
|
+
traits: [],
|
|
6927
|
+
impls: []
|
|
6928
|
+
};
|
|
6929
|
+
if (!isRustParserAvailable() || options.forceFallback) {
|
|
6930
|
+
return empty;
|
|
6931
|
+
}
|
|
6932
|
+
const tree = parseRust(source);
|
|
6933
|
+
if (!tree) return empty;
|
|
6934
|
+
return walkRustTree(tree);
|
|
6935
|
+
}
|
|
6936
|
+
function walkRustTree(tree) {
|
|
6937
|
+
const ctx = { inTestConfig: false };
|
|
6938
|
+
const out = {
|
|
6939
|
+
imports: [],
|
|
6940
|
+
functions: [],
|
|
6941
|
+
structs: [],
|
|
6942
|
+
traits: [],
|
|
6943
|
+
impls: []
|
|
6944
|
+
};
|
|
6945
|
+
walkChildren(tree.rootNode, ctx, out);
|
|
6946
|
+
return out;
|
|
6947
|
+
}
|
|
6948
|
+
function walkChildren(node, ctx, out) {
|
|
6949
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6950
|
+
const child = node.namedChild(i);
|
|
6951
|
+
if (!child) continue;
|
|
6952
|
+
visitNode(child, ctx, out);
|
|
6953
|
+
}
|
|
6954
|
+
}
|
|
6955
|
+
function visitNode(node, ctx, out) {
|
|
6956
|
+
const innerCtx = { ...ctx };
|
|
6957
|
+
const attrState = readPrecedingAttributes(node);
|
|
6958
|
+
switch (node.type) {
|
|
6959
|
+
case "use_declaration": {
|
|
6960
|
+
out.imports.push(extractUse(node));
|
|
6961
|
+
return;
|
|
6962
|
+
}
|
|
6963
|
+
case "function_item": {
|
|
6964
|
+
const isMethod = isInImplBlock(node);
|
|
6965
|
+
out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
|
|
6966
|
+
walkChildren(getField(node, "body") ?? node, innerCtx, out);
|
|
6967
|
+
return;
|
|
6968
|
+
}
|
|
6969
|
+
case "struct_item": {
|
|
6970
|
+
out.structs.push(extractStruct(node, attrState));
|
|
6971
|
+
const body = getField(node, "body");
|
|
6972
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6975
|
+
case "trait_item": {
|
|
6976
|
+
out.traits.push(extractTrait(node, attrState));
|
|
6977
|
+
const body = getField(node, "body");
|
|
6978
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
case "impl_item": {
|
|
6982
|
+
const implEntry = extractImpl(node, attrState, innerCtx);
|
|
6983
|
+
out.impls.push(implEntry.entry);
|
|
6984
|
+
const body = getField(node, "body");
|
|
6985
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
6986
|
+
return;
|
|
6987
|
+
}
|
|
6988
|
+
case "mod_item": {
|
|
6989
|
+
const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
|
|
6990
|
+
const modCtx = { ...innerCtx, inTestConfig: modIsTest };
|
|
6991
|
+
const body = getField(node, "body");
|
|
6992
|
+
if (body) walkChildren(body, modCtx, out);
|
|
6993
|
+
return;
|
|
6994
|
+
}
|
|
6995
|
+
default: {
|
|
6996
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6997
|
+
const child = node.namedChild(i);
|
|
6998
|
+
if (child) visitNode(child, innerCtx, out);
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
function readPrecedingAttributes(node) {
|
|
7004
|
+
const state = {
|
|
7005
|
+
isTestCfg: false,
|
|
7006
|
+
isTest: false,
|
|
7007
|
+
isPub: false,
|
|
7008
|
+
derives: []
|
|
7009
|
+
};
|
|
7010
|
+
const parent = node.parent;
|
|
7011
|
+
if (!parent) return state;
|
|
7012
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
7013
|
+
const sibling = parent.namedChild(i);
|
|
7014
|
+
if (!sibling || sibling === node) continue;
|
|
7015
|
+
if (sibling.endIndex > node.startIndex) continue;
|
|
7016
|
+
if (sibling.type !== "attribute_item") continue;
|
|
7017
|
+
decodeAttribute(sibling, state);
|
|
7018
|
+
}
|
|
7019
|
+
return state;
|
|
7020
|
+
}
|
|
7021
|
+
function decodeAttribute(attr, state) {
|
|
7022
|
+
for (let i = 0; i < attr.namedChildCount; i++) {
|
|
7023
|
+
const child = attr.namedChild(i);
|
|
7024
|
+
if (!child || child.type !== "attribute") continue;
|
|
7025
|
+
const name = firstIdentifier(child);
|
|
7026
|
+
if (!name) continue;
|
|
7027
|
+
if (name === "cfg") {
|
|
7028
|
+
const cfgText = child.text;
|
|
7029
|
+
if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
|
|
7030
|
+
state.isTestCfg = true;
|
|
7031
|
+
}
|
|
7032
|
+
} else if (name === "test") {
|
|
7033
|
+
state.isTest = true;
|
|
7034
|
+
} else if (name === "derive") {
|
|
7035
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
7036
|
+
const inner = child.namedChild(j);
|
|
7037
|
+
if (inner) {
|
|
7038
|
+
const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
|
|
7039
|
+
for (const m of matches) state.derives.push(m[1]);
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
function firstIdentifier(node) {
|
|
7046
|
+
const text = node.text;
|
|
7047
|
+
const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
7048
|
+
return idMatch ? idMatch[1] : null;
|
|
7049
|
+
}
|
|
7050
|
+
function extractUse(node) {
|
|
7051
|
+
const text = node.text;
|
|
7052
|
+
const argument = node.namedChild(0);
|
|
7053
|
+
const argField = node.childForFieldName("argument");
|
|
7054
|
+
const path = argField?.text ?? argument?.text ?? "";
|
|
7055
|
+
const names = [];
|
|
7056
|
+
let isGlob = false;
|
|
7057
|
+
const useListNode = findUseListNode(argument);
|
|
7058
|
+
if (useListNode) {
|
|
7059
|
+
for (let i = 0; i < useListNode.namedChildCount; i++) {
|
|
7060
|
+
const item = useListNode.namedChild(i);
|
|
7061
|
+
if (item.type === "use_wildcard") {
|
|
7062
|
+
isGlob = true;
|
|
7063
|
+
} else if (item.type === "identifier") {
|
|
7064
|
+
names.push({ name: item.text });
|
|
7065
|
+
} else if (item.type === "use_as_clause") {
|
|
7066
|
+
const alias = item.childForFieldName("alias");
|
|
7067
|
+
const binding = item.childForFieldName("path");
|
|
7068
|
+
if (binding) {
|
|
7069
|
+
names.push({ name: binding.text, alias: alias?.text });
|
|
7070
|
+
} else {
|
|
7071
|
+
names.push({ name: item.text });
|
|
7072
|
+
}
|
|
7073
|
+
} else if (item.type === "scoped_identifier") {
|
|
7074
|
+
const idents = collectIdentifiers(item);
|
|
7075
|
+
names.push({ name: idents[idents.length - 1] ?? item.text });
|
|
7076
|
+
} else if (item.text.includes(" as ")) {
|
|
7077
|
+
const [head, alias] = item.text.split(/\s+as\s+/);
|
|
7078
|
+
names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
|
|
7079
|
+
} else {
|
|
7080
|
+
names.push({ name: item.text });
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
} else if (argument?.type === "use_wildcard") {
|
|
7084
|
+
isGlob = true;
|
|
7085
|
+
} else if (argument) {
|
|
7086
|
+
const idents = collectIdentifiers(argument);
|
|
7087
|
+
const last = idents[idents.length - 1] ?? argument.text;
|
|
7088
|
+
names.push({ name: last });
|
|
7089
|
+
const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
|
|
7090
|
+
if (asMatch) names[0].alias = asMatch[1];
|
|
7091
|
+
}
|
|
7092
|
+
return {
|
|
7093
|
+
path: path.trim(),
|
|
7094
|
+
names,
|
|
7095
|
+
isGlob,
|
|
7096
|
+
line: node.startPosition.row + 1,
|
|
7097
|
+
column: node.startPosition.column
|
|
7098
|
+
};
|
|
7099
|
+
}
|
|
7100
|
+
function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
|
|
7101
|
+
const nameNode = node.childForFieldName("name");
|
|
7102
|
+
const name = nameNode?.text ?? "<anon>";
|
|
7103
|
+
const params = node.childForFieldName("parameters");
|
|
7104
|
+
const body = node.childForFieldName("body");
|
|
7105
|
+
const visibilityMod = node.namedChild(0);
|
|
7106
|
+
const isPublic = visibilityMod?.type === "visibility_modifier";
|
|
7107
|
+
let receiver;
|
|
7108
|
+
if (isMethod && params) {
|
|
7109
|
+
const selfParam = findSelfParameter(params);
|
|
7110
|
+
if (selfParam) receiver = selfParam.text;
|
|
7111
|
+
}
|
|
7112
|
+
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
7113
|
+
const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
|
|
7114
|
+
return {
|
|
7115
|
+
name,
|
|
7116
|
+
line: node.startPosition.row + 1,
|
|
7117
|
+
column: node.startPosition.column,
|
|
7118
|
+
isPublic,
|
|
7119
|
+
isMethod,
|
|
7120
|
+
receiver,
|
|
7121
|
+
bodyLines,
|
|
7122
|
+
inTestConfig
|
|
7123
|
+
};
|
|
7124
|
+
}
|
|
7125
|
+
function extractStruct(node, attrs) {
|
|
7126
|
+
const nameNode = node.childForFieldName("name");
|
|
7127
|
+
return {
|
|
7128
|
+
name: nameNode?.text ?? "<anon>",
|
|
7129
|
+
line: node.startPosition.row + 1,
|
|
7130
|
+
column: node.startPosition.column,
|
|
7131
|
+
isPublic: hasVisibility(node),
|
|
7132
|
+
isDerive: attrs.derives.length > 0,
|
|
7133
|
+
derives: [...attrs.derives]
|
|
7134
|
+
};
|
|
7135
|
+
}
|
|
7136
|
+
function extractTrait(node, _attrs) {
|
|
7137
|
+
const nameNode = node.childForFieldName("name");
|
|
7138
|
+
return {
|
|
7139
|
+
name: nameNode?.text ?? "<anon>",
|
|
7140
|
+
line: node.startPosition.row + 1,
|
|
7141
|
+
column: node.startPosition.column,
|
|
7142
|
+
isPublic: hasVisibility(node)
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
function extractImpl(node, _attrs, _ctx) {
|
|
7146
|
+
const traitNode = node.childForFieldName("trait");
|
|
7147
|
+
const typeNode = node.childForFieldName("type");
|
|
7148
|
+
const typeText = typeNode?.text ?? "<anon>";
|
|
7149
|
+
const traitText = traitNode?.text;
|
|
7150
|
+
const body = node.childForFieldName("body");
|
|
7151
|
+
const methods = [];
|
|
7152
|
+
if (body) {
|
|
7153
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
7154
|
+
const child = body.namedChild(i);
|
|
7155
|
+
if (child?.type === "function_item") {
|
|
7156
|
+
const m = child.childForFieldName("name");
|
|
7157
|
+
if (m) methods.push(m.text);
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
return {
|
|
7162
|
+
entry: {
|
|
7163
|
+
type: typeText,
|
|
7164
|
+
trait: traitText ?? void 0,
|
|
7165
|
+
methods,
|
|
7166
|
+
line: node.startPosition.row + 1,
|
|
7167
|
+
column: node.startPosition.column
|
|
7168
|
+
}
|
|
7169
|
+
};
|
|
7170
|
+
}
|
|
7171
|
+
function findUseListNode(argument) {
|
|
7172
|
+
if (!argument) return null;
|
|
7173
|
+
if (argument.type === "use_list") return argument;
|
|
7174
|
+
if (argument.type === "scoped_use_list") {
|
|
7175
|
+
for (let i = 0; i < argument.namedChildCount; i++) {
|
|
7176
|
+
const c = argument.namedChild(i);
|
|
7177
|
+
if (c?.type === "use_list") return c;
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
return null;
|
|
7181
|
+
}
|
|
7182
|
+
function getField(node, fieldName) {
|
|
7183
|
+
return node.childForFieldName(fieldName);
|
|
7184
|
+
}
|
|
7185
|
+
function collectIdentifiers(node) {
|
|
7186
|
+
const out = [];
|
|
7187
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7188
|
+
const child = node.namedChild(i);
|
|
7189
|
+
if (!child) continue;
|
|
7190
|
+
if (child.type === "identifier" || child.type === "type_identifier") {
|
|
7191
|
+
out.push(child.text);
|
|
7192
|
+
} else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
|
|
7193
|
+
out.push(...collectIdentifiers(child));
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
return out;
|
|
7197
|
+
}
|
|
7198
|
+
function hasVisibility(node) {
|
|
7199
|
+
return node.namedChild(0)?.type === "visibility_modifier";
|
|
7200
|
+
}
|
|
7201
|
+
function isInImplBlock(node) {
|
|
7202
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
7203
|
+
if (p.type === "impl_item") return true;
|
|
7204
|
+
if (p.type === "function_item" || p.type === "source_file") return false;
|
|
7205
|
+
}
|
|
7206
|
+
return false;
|
|
7207
|
+
}
|
|
7208
|
+
function findSelfParameter(params) {
|
|
7209
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
7210
|
+
const child = params.namedChild(i);
|
|
7211
|
+
if (child?.type === "self_parameter") return child;
|
|
7212
|
+
}
|
|
7213
|
+
return null;
|
|
7214
|
+
}
|
|
7215
|
+
function isFunctionAttrTest(node) {
|
|
7216
|
+
void node;
|
|
7217
|
+
return false;
|
|
7218
|
+
}
|
|
7219
|
+
|
|
7307
7220
|
// src/engine/visitors/v2-build.ts
|
|
7308
|
-
init_rust();
|
|
7309
7221
|
var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
|
|
7310
7222
|
var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
|
|
7311
7223
|
var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
|
|
@@ -35149,125 +35061,17 @@ var unusedParameterRule = createRule({
|
|
|
35149
35061
|
});
|
|
35150
35062
|
|
|
35151
35063
|
// src/rules/docs/broken-link.ts
|
|
35152
|
-
import { existsSync as
|
|
35153
|
-
import { dirname as
|
|
35064
|
+
import { existsSync as existsSync3 } from "fs";
|
|
35065
|
+
import { dirname as dirname3, join as join5, resolve } from "path";
|
|
35154
35066
|
|
|
35155
35067
|
// src/engine/doc-freshness.ts
|
|
35156
|
-
import { readFileSync as
|
|
35157
|
-
import { join as
|
|
35158
|
-
import { globby as globby2 } from "globby";
|
|
35159
|
-
|
|
35160
|
-
// src/mcp/patterns.ts
|
|
35161
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
35162
|
-
import { basename as basename2, extname as extname2 } from "path";
|
|
35163
|
-
|
|
35164
|
-
// src/engine/discover.ts
|
|
35068
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
35069
|
+
import { join as join4, dirname as dirname2, relative } from "path";
|
|
35165
35070
|
import { globby } from "globby";
|
|
35166
|
-
import { minimatch } from "minimatch";
|
|
35167
|
-
import { resolve, extname, relative, sep, basename, dirname as dirname2, join as join3 } from "path";
|
|
35168
|
-
import { readFileSync } from "fs";
|
|
35169
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
|
|
35170
|
-
var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35171
|
-
".py",
|
|
35172
|
-
".go",
|
|
35173
|
-
// v0.14.0
|
|
35174
|
-
".swift",
|
|
35175
|
-
".kt",
|
|
35176
|
-
".kts",
|
|
35177
|
-
".dart",
|
|
35178
|
-
".rs",
|
|
35179
|
-
".cpp",
|
|
35180
|
-
".cc",
|
|
35181
|
-
".cxx",
|
|
35182
|
-
".c",
|
|
35183
|
-
".h",
|
|
35184
|
-
".hpp",
|
|
35185
|
-
".hxx",
|
|
35186
|
-
".java",
|
|
35187
|
-
".rb",
|
|
35188
|
-
".php"
|
|
35189
|
-
]);
|
|
35190
|
-
var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35191
|
-
...SOURCE_EXTENSIONS,
|
|
35192
|
-
...BACKEND_EXTENSIONS
|
|
35193
|
-
]);
|
|
35194
|
-
|
|
35195
|
-
// src/config/conventions.ts
|
|
35196
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
35197
|
-
import { join as join4 } from "path";
|
|
35198
|
-
|
|
35199
|
-
// src/mcp/patterns.ts
|
|
35200
|
-
var ESM_IMPORT_RE = /(?:^|\n)\s*(?:import\s+(?:type\s+)?(?:[\w*${},\s]+\s+from\s+)?|import\s+|export\s+(?:type\s+)?[\w*${},\s]+\s+from\s+)(['"])([^'"]+)\1/g;
|
|
35201
|
-
var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35202
|
-
var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35203
|
-
function extractImports(source) {
|
|
35204
|
-
const seen = /* @__PURE__ */ new Set();
|
|
35205
|
-
const out = [];
|
|
35206
|
-
const push = (spec) => {
|
|
35207
|
-
if (spec.startsWith(".") || spec.startsWith("/")) return;
|
|
35208
|
-
if (seen.has(spec)) return;
|
|
35209
|
-
seen.add(spec);
|
|
35210
|
-
out.push(spec);
|
|
35211
|
-
};
|
|
35212
|
-
for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
|
|
35213
|
-
re.lastIndex = 0;
|
|
35214
|
-
let m;
|
|
35215
|
-
while ((m = re.exec(source)) !== null) {
|
|
35216
|
-
push(m[2]);
|
|
35217
|
-
}
|
|
35218
|
-
}
|
|
35219
|
-
return out;
|
|
35220
|
-
}
|
|
35221
|
-
|
|
35222
|
-
// src/rules/docs/expired-code-example.ts
|
|
35223
|
-
var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
|
|
35224
|
-
function stripSubpath(spec) {
|
|
35225
|
-
if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
|
|
35226
|
-
return spec.split("/")[0] ?? spec;
|
|
35227
|
-
}
|
|
35228
|
-
var expiredCodeExampleRule = createRule({
|
|
35229
|
-
id: "docs/expired-code-example",
|
|
35230
|
-
category: "docs",
|
|
35231
|
-
severity: "medium",
|
|
35232
|
-
aiSpecific: false,
|
|
35233
|
-
description: "A fenced code example imports a package that is not declared in package.json.",
|
|
35234
|
-
create(context) {
|
|
35235
|
-
return { ...context, packages: declaredPackages(context.cwd) };
|
|
35236
|
-
},
|
|
35237
|
-
analyze(context, facts) {
|
|
35238
|
-
const issues = [];
|
|
35239
|
-
const source = facts.v2?._source;
|
|
35240
|
-
if (!source) return issues;
|
|
35241
|
-
const packages = declaredPackages(context.cwd);
|
|
35242
|
-
const packageName = context.packageName;
|
|
35243
|
-
if (packageName) packages.add(packageName);
|
|
35244
|
-
const blocks = extractFencedCodeBlocks(source);
|
|
35245
|
-
for (const block of blocks) {
|
|
35246
|
-
if (!CODE_LANGS.has(block.lang)) continue;
|
|
35247
|
-
if (block.body.split("\n").length < 2) continue;
|
|
35248
|
-
const imports = extractImports(block.body);
|
|
35249
|
-
for (const imp of imports) {
|
|
35250
|
-
const pkgName = stripSubpath(imp);
|
|
35251
|
-
if (packages.has(pkgName)) continue;
|
|
35252
|
-
issues.push({
|
|
35253
|
-
ruleId: "docs/expired-code-example",
|
|
35254
|
-
category: "docs",
|
|
35255
|
-
severity: "medium",
|
|
35256
|
-
aiSpecific: false,
|
|
35257
|
-
message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
|
|
35258
|
-
line: block.line,
|
|
35259
|
-
column: block.column,
|
|
35260
|
-
advice: `Add \`${pkgName}\` to package.json or update the example.`
|
|
35261
|
-
});
|
|
35262
|
-
}
|
|
35263
|
-
}
|
|
35264
|
-
return issues;
|
|
35265
|
-
}
|
|
35266
|
-
});
|
|
35267
35071
|
|
|
35268
35072
|
// src/rules/docs/stale-function-reference.ts
|
|
35269
|
-
import { readFileSync
|
|
35270
|
-
import { join as
|
|
35073
|
+
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
35074
|
+
import { join as join3, extname } from "path";
|
|
35271
35075
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
35272
35076
|
// JS reserved words
|
|
35273
35077
|
"true",
|
|
@@ -35840,7 +35644,7 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
|
|
|
35840
35644
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
35841
35645
|
var CAP = 200;
|
|
35842
35646
|
function walk(dir, out, cap) {
|
|
35843
|
-
if (!
|
|
35647
|
+
if (!existsSync(dir) || out.length >= cap) return;
|
|
35844
35648
|
let entries;
|
|
35845
35649
|
try {
|
|
35846
35650
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -35850,19 +35654,19 @@ function walk(dir, out, cap) {
|
|
|
35850
35654
|
for (const entry of entries) {
|
|
35851
35655
|
if (out.length >= cap) return;
|
|
35852
35656
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
35853
|
-
const full =
|
|
35657
|
+
const full = join3(dir, entry.name);
|
|
35854
35658
|
if (entry.isDirectory()) walk(full, out, cap);
|
|
35855
|
-
else if (entry.isFile() && SOURCE_EXTS.has(
|
|
35659
|
+
else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name))) out.push(full);
|
|
35856
35660
|
}
|
|
35857
35661
|
}
|
|
35858
35662
|
function collectExports(cwd) {
|
|
35859
35663
|
const out = /* @__PURE__ */ new Set();
|
|
35860
35664
|
const files = [];
|
|
35861
|
-
for (const root of SOURCE_ROOTS) walk(
|
|
35665
|
+
for (const root of SOURCE_ROOTS) walk(join3(cwd, root), files, CAP);
|
|
35862
35666
|
for (const file of files) {
|
|
35863
35667
|
let source;
|
|
35864
35668
|
try {
|
|
35865
|
-
source =
|
|
35669
|
+
source = readFileSync(file, "utf-8");
|
|
35866
35670
|
} catch {
|
|
35867
35671
|
continue;
|
|
35868
35672
|
}
|
|
@@ -36125,38 +35929,6 @@ function extractInlineCodeSpans(source) {
|
|
|
36125
35929
|
}
|
|
36126
35930
|
return hits;
|
|
36127
35931
|
}
|
|
36128
|
-
function extractFencedCodeBlocks(source) {
|
|
36129
|
-
const blocks = [];
|
|
36130
|
-
const lines = source.split("\n");
|
|
36131
|
-
let i = 0;
|
|
36132
|
-
while (i < lines.length) {
|
|
36133
|
-
const line = lines[i] ?? "";
|
|
36134
|
-
const fenceMatch = /^```(\w*)\s*$/.exec(line);
|
|
36135
|
-
if (!fenceMatch) {
|
|
36136
|
-
i++;
|
|
36137
|
-
continue;
|
|
36138
|
-
}
|
|
36139
|
-
const lang = fenceMatch[1] ?? "";
|
|
36140
|
-
const startLine = i + 1;
|
|
36141
|
-
const bodyLines = [];
|
|
36142
|
-
i++;
|
|
36143
|
-
while (i < lines.length) {
|
|
36144
|
-
if (/^```\s*$/.test(lines[i] ?? "")) {
|
|
36145
|
-
i++;
|
|
36146
|
-
break;
|
|
36147
|
-
}
|
|
36148
|
-
bodyLines.push(lines[i] ?? "");
|
|
36149
|
-
i++;
|
|
36150
|
-
}
|
|
36151
|
-
blocks.push({
|
|
36152
|
-
lang,
|
|
36153
|
-
body: bodyLines.join("\n"),
|
|
36154
|
-
line: startLine,
|
|
36155
|
-
column: 1
|
|
36156
|
-
});
|
|
36157
|
-
}
|
|
36158
|
-
return blocks;
|
|
36159
|
-
}
|
|
36160
35932
|
function extractMarkdownLinks(source) {
|
|
36161
35933
|
const hits = [];
|
|
36162
35934
|
const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
@@ -36173,10 +35945,10 @@ function extractMarkdownLinks(source) {
|
|
|
36173
35945
|
}
|
|
36174
35946
|
function declaredPackages(cwd) {
|
|
36175
35947
|
const out = /* @__PURE__ */ new Set();
|
|
36176
|
-
const pkgPath =
|
|
36177
|
-
if (!
|
|
35948
|
+
const pkgPath = join4(cwd, "package.json");
|
|
35949
|
+
if (!existsSync2(pkgPath)) return out;
|
|
36178
35950
|
try {
|
|
36179
|
-
const raw =
|
|
35951
|
+
const raw = readFileSync2(pkgPath, "utf-8");
|
|
36180
35952
|
const pkg = JSON.parse(raw);
|
|
36181
35953
|
for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
36182
35954
|
const v = pkg[k];
|
|
@@ -36206,7 +35978,7 @@ var brokenLinkRule = createRule({
|
|
|
36206
35978
|
const source = facts.v2?._source;
|
|
36207
35979
|
if (!source) return issues;
|
|
36208
35980
|
const links = extractMarkdownLinks(source);
|
|
36209
|
-
const docDir =
|
|
35981
|
+
const docDir = dirname3(resolve(context.cwd, context.filePath));
|
|
36210
35982
|
for (const link of links) {
|
|
36211
35983
|
const target = link.target;
|
|
36212
35984
|
if (target.startsWith("http://") || target.startsWith("https://")) continue;
|
|
@@ -36216,8 +35988,8 @@ var brokenLinkRule = createRule({
|
|
|
36216
35988
|
if (target.startsWith("/")) continue;
|
|
36217
35989
|
const filePart = target.split("#")[0] ?? target;
|
|
36218
35990
|
if (filePart === "") continue;
|
|
36219
|
-
const resolved =
|
|
36220
|
-
if (
|
|
35991
|
+
const resolved = join5(docDir, filePart);
|
|
35992
|
+
if (existsSync3(resolved)) continue;
|
|
36221
35993
|
issues.push({
|
|
36222
35994
|
ruleId: "docs/broken-link",
|
|
36223
35995
|
category: "docs",
|
|
@@ -36234,6 +36006,466 @@ var brokenLinkRule = createRule({
|
|
|
36234
36006
|
}
|
|
36235
36007
|
});
|
|
36236
36008
|
|
|
36009
|
+
// src/rules/dup/identical-block.ts
|
|
36010
|
+
import * as crypto from "crypto";
|
|
36011
|
+
var WINDOW_SIZE = 10;
|
|
36012
|
+
var MIN_NORMALIZED_LENGTH = 40;
|
|
36013
|
+
var HASH_PREFIX_LENGTH = 16;
|
|
36014
|
+
var DEDUP_CACHE = /* @__PURE__ */ new Map();
|
|
36015
|
+
function normalizeAndHash(lines) {
|
|
36016
|
+
const normalized = lines.map(
|
|
36017
|
+
(line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
|
|
36018
|
+
).filter((line) => line.length > 0).join("\n");
|
|
36019
|
+
if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
|
|
36020
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
|
|
36021
|
+
}
|
|
36022
|
+
var dupIdenticalBlockRule = createRule({
|
|
36023
|
+
id: "dup/identical-block",
|
|
36024
|
+
category: "logic",
|
|
36025
|
+
severity: "medium",
|
|
36026
|
+
aiSpecific: false,
|
|
36027
|
+
description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
|
|
36028
|
+
create(_context) {
|
|
36029
|
+
return {};
|
|
36030
|
+
},
|
|
36031
|
+
analyze(_context, facts) {
|
|
36032
|
+
const issues = [];
|
|
36033
|
+
const source = facts.v2?._source;
|
|
36034
|
+
if (!source) return issues;
|
|
36035
|
+
const filePath = facts.filePath;
|
|
36036
|
+
const lines = source.split("\n");
|
|
36037
|
+
for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
|
|
36038
|
+
const window = lines.slice(i, i + WINDOW_SIZE);
|
|
36039
|
+
const hash = normalizeAndHash(window);
|
|
36040
|
+
if (!hash) continue;
|
|
36041
|
+
const existing = DEDUP_CACHE.get(hash) ?? [];
|
|
36042
|
+
const matches = existing.filter((m) => m.file !== filePath);
|
|
36043
|
+
for (const match of matches) {
|
|
36044
|
+
issues.push({
|
|
36045
|
+
ruleId: "dup/identical-block",
|
|
36046
|
+
category: "logic",
|
|
36047
|
+
severity: "medium",
|
|
36048
|
+
aiSpecific: false,
|
|
36049
|
+
message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
|
|
36050
|
+
line: i + 1,
|
|
36051
|
+
column: 0,
|
|
36052
|
+
advice: "Refactor to a shared helper. This is a Type-1 clone (byte-for-byte identical after normalization). Common in AI-generated code that copy-pastes from training data.",
|
|
36053
|
+
extras: {
|
|
36054
|
+
duplicateOf: {
|
|
36055
|
+
file: match.file,
|
|
36056
|
+
line: match.line + 1,
|
|
36057
|
+
hash
|
|
36058
|
+
}
|
|
36059
|
+
}
|
|
36060
|
+
});
|
|
36061
|
+
}
|
|
36062
|
+
existing.push({ file: filePath, line: i });
|
|
36063
|
+
DEDUP_CACHE.set(hash, existing);
|
|
36064
|
+
}
|
|
36065
|
+
return issues;
|
|
36066
|
+
}
|
|
36067
|
+
});
|
|
36068
|
+
|
|
36069
|
+
// src/rules/go/error-wrap-without-context.ts
|
|
36070
|
+
var ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
|
|
36071
|
+
var GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
|
|
36072
|
+
var goErrorWrapWithoutContextRule = createRule({
|
|
36073
|
+
id: "go/error-wrap-without-context",
|
|
36074
|
+
category: "typo",
|
|
36075
|
+
severity: "low",
|
|
36076
|
+
aiSpecific: true,
|
|
36077
|
+
description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
|
|
36078
|
+
create(_context) {
|
|
36079
|
+
return {};
|
|
36080
|
+
},
|
|
36081
|
+
analyze(_context, facts) {
|
|
36082
|
+
const issues = [];
|
|
36083
|
+
const source = facts.v2?._source;
|
|
36084
|
+
if (!source) return issues;
|
|
36085
|
+
let match;
|
|
36086
|
+
ERR_WRAP_REGEX.lastIndex = 0;
|
|
36087
|
+
while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
|
|
36088
|
+
const formatString = match[1];
|
|
36089
|
+
if (!formatString.includes("%w")) continue;
|
|
36090
|
+
if (formatString.length >= 30) continue;
|
|
36091
|
+
if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
|
|
36092
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
36093
|
+
issues.push({
|
|
36094
|
+
ruleId: "go/error-wrap-without-context",
|
|
36095
|
+
category: "typo",
|
|
36096
|
+
severity: "low",
|
|
36097
|
+
aiSpecific: true,
|
|
36098
|
+
message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
|
|
36099
|
+
line,
|
|
36100
|
+
column: match[0].indexOf("fmt") + 1,
|
|
36101
|
+
advice: 'Real Go errors include the failing operation: `fmt.Errorf("opening config: %w", err)`. Generic messages ("error: %w", "failed: %w") tell the reader nothing about what failed. Reference: go/error-wrap-without-context v0.19. See: https://github.com/golang/go/wiki/CodeReviewComments#error-strings'
|
|
36102
|
+
});
|
|
36103
|
+
}
|
|
36104
|
+
return issues;
|
|
36105
|
+
}
|
|
36106
|
+
});
|
|
36107
|
+
|
|
36108
|
+
// src/rules/go/nil-slice-vs-empty.ts
|
|
36109
|
+
var NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
|
|
36110
|
+
var EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
|
|
36111
|
+
var goNilSliceVsEmptyRule = createRule({
|
|
36112
|
+
id: "go/nil-slice-vs-empty",
|
|
36113
|
+
category: "typo",
|
|
36114
|
+
severity: "low",
|
|
36115
|
+
aiSpecific: true,
|
|
36116
|
+
description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
|
|
36117
|
+
create(_context) {
|
|
36118
|
+
return {};
|
|
36119
|
+
},
|
|
36120
|
+
analyze(_context, facts) {
|
|
36121
|
+
const issues = [];
|
|
36122
|
+
const source = facts.v2?._source;
|
|
36123
|
+
if (!source) return issues;
|
|
36124
|
+
const nilDecls = /* @__PURE__ */ new Map();
|
|
36125
|
+
let m;
|
|
36126
|
+
NIL_SLICE_DECL_REGEX.lastIndex = 0;
|
|
36127
|
+
while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
|
|
36128
|
+
nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
|
|
36129
|
+
}
|
|
36130
|
+
if (nilDecls.size === 0) return issues;
|
|
36131
|
+
EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
|
|
36132
|
+
while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
|
|
36133
|
+
const name = m[1];
|
|
36134
|
+
if (!nilDecls.has(name)) continue;
|
|
36135
|
+
const declLine = nilDecls.get(name);
|
|
36136
|
+
const assignLine = source.slice(0, m.index).split("\n").length;
|
|
36137
|
+
if (assignLine <= declLine) continue;
|
|
36138
|
+
issues.push({
|
|
36139
|
+
ruleId: "go/nil-slice-vs-empty",
|
|
36140
|
+
category: "typo",
|
|
36141
|
+
severity: "low",
|
|
36142
|
+
aiSpecific: true,
|
|
36143
|
+
message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
|
|
36144
|
+
line: declLine,
|
|
36145
|
+
column: 1,
|
|
36146
|
+
advice: "Either declare as `var " + name + " = []int{}` or assign with `make([]int, 0)`. The nil/empty inconsistency is an AI signal \u2014 real code picks one form and sticks with it. Reference: go/nil-slice-vs-empty v0.19."
|
|
36147
|
+
});
|
|
36148
|
+
}
|
|
36149
|
+
return issues;
|
|
36150
|
+
}
|
|
36151
|
+
});
|
|
36152
|
+
|
|
36153
|
+
// src/rules/go/struct-tag-inconsistency.ts
|
|
36154
|
+
var JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
|
|
36155
|
+
var goStructTagInconsistencyRule = createRule({
|
|
36156
|
+
id: "go/struct-tag-inconsistency",
|
|
36157
|
+
category: "typo",
|
|
36158
|
+
severity: "low",
|
|
36159
|
+
aiSpecific: true,
|
|
36160
|
+
description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
|
|
36161
|
+
create(_context) {
|
|
36162
|
+
return {};
|
|
36163
|
+
},
|
|
36164
|
+
analyze(_context, facts) {
|
|
36165
|
+
const issues = [];
|
|
36166
|
+
const source = facts.v2?._source;
|
|
36167
|
+
if (!source) return issues;
|
|
36168
|
+
const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
|
|
36169
|
+
let structMatch;
|
|
36170
|
+
while ((structMatch = structRegex.exec(source)) !== null) {
|
|
36171
|
+
const startIdx = structMatch.index;
|
|
36172
|
+
const openBrace = source.indexOf("{", startIdx);
|
|
36173
|
+
if (openBrace < 0) continue;
|
|
36174
|
+
let depth = 1;
|
|
36175
|
+
let i = openBrace + 1;
|
|
36176
|
+
while (i < source.length && depth > 0) {
|
|
36177
|
+
const ch = source[i];
|
|
36178
|
+
if (ch === "{") depth++;
|
|
36179
|
+
else if (ch === "}") depth--;
|
|
36180
|
+
i++;
|
|
36181
|
+
}
|
|
36182
|
+
const structBody = source.slice(openBrace, i);
|
|
36183
|
+
const structLine = source.slice(0, startIdx).split("\n").length;
|
|
36184
|
+
const styleCount = {};
|
|
36185
|
+
const tagMatches = [];
|
|
36186
|
+
let m;
|
|
36187
|
+
JSON_TAG_REGEX.lastIndex = 0;
|
|
36188
|
+
while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
|
|
36189
|
+
const tag = m[1];
|
|
36190
|
+
const options = m[2] ?? "";
|
|
36191
|
+
const style = options ? "with-options" : "no-options";
|
|
36192
|
+
styleCount[style] = (styleCount[style] ?? 0) + 1;
|
|
36193
|
+
tagMatches.push({ tag, style, idx: openBrace + m.index });
|
|
36194
|
+
}
|
|
36195
|
+
const styles = Object.keys(styleCount);
|
|
36196
|
+
if (styles.length < 2 || tagMatches.length < 2) continue;
|
|
36197
|
+
const dominant = styles.reduce(
|
|
36198
|
+
(a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
|
|
36199
|
+
);
|
|
36200
|
+
const minority = tagMatches.filter((t) => t.style !== dominant);
|
|
36201
|
+
if (minority.length === 0) continue;
|
|
36202
|
+
for (const m2 of minority) {
|
|
36203
|
+
const line = source.slice(0, m2.idx).split("\n").length;
|
|
36204
|
+
issues.push({
|
|
36205
|
+
ruleId: "go/struct-tag-inconsistency",
|
|
36206
|
+
category: "typo",
|
|
36207
|
+
severity: "low",
|
|
36208
|
+
aiSpecific: true,
|
|
36209
|
+
message: `Struct mixes json tag styles \u2014 this field uses "json:"${m2.tag}${m2.style === "with-options" ? ",..." : ""}"" but the dominant style is ${dominant === "with-options" ? "with options (e.g. omitempty)" : "no options"}`,
|
|
36210
|
+
line,
|
|
36211
|
+
column: 1,
|
|
36212
|
+
advice: 'Pick one tag style per struct. If most fields are `json:"foo"`, this field should be too. Real Go code maintains consistency within a struct (or within a package). Reference: go/struct-tag-inconsistency v0.19.'
|
|
36213
|
+
});
|
|
36214
|
+
}
|
|
36215
|
+
if (issues.length > 0) break;
|
|
36216
|
+
}
|
|
36217
|
+
return issues;
|
|
36218
|
+
}
|
|
36219
|
+
});
|
|
36220
|
+
|
|
36221
|
+
// src/rules/java/arraylist-vs-linkedlist.ts
|
|
36222
|
+
var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
|
|
36223
|
+
var javaArraylistVsLinkedlistRule = createRule({
|
|
36224
|
+
id: "java/arraylist-vs-linkedlist",
|
|
36225
|
+
category: "typo",
|
|
36226
|
+
severity: "low",
|
|
36227
|
+
aiSpecific: true,
|
|
36228
|
+
description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
|
|
36229
|
+
create(_context) {
|
|
36230
|
+
return {};
|
|
36231
|
+
},
|
|
36232
|
+
analyze(_context, facts) {
|
|
36233
|
+
const issues = [];
|
|
36234
|
+
const source = facts.v2?._source;
|
|
36235
|
+
if (!source) return issues;
|
|
36236
|
+
let m;
|
|
36237
|
+
NEW_LINKED_LIST_REGEX.lastIndex = 0;
|
|
36238
|
+
while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
|
|
36239
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36240
|
+
issues.push({
|
|
36241
|
+
ruleId: "java/arraylist-vs-linkedlist",
|
|
36242
|
+
category: "typo",
|
|
36243
|
+
severity: "low",
|
|
36244
|
+
aiSpecific: true,
|
|
36245
|
+
message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
|
|
36246
|
+
line,
|
|
36247
|
+
column: 1,
|
|
36248
|
+
advice: "Replace `new LinkedList<>()` with `new ArrayList<>()`. LinkedList is rarely the right choice (worse cache locality, 5x more memory per element, O(n) indexed access). Joshua Bloch (Effective Java, Item 28) recommends ArrayList unless you specifically need a Deque. AI agents default to LinkedList because of textbook examples. Reference: java/arraylist-vs-linkedlist v0.20."
|
|
36249
|
+
});
|
|
36250
|
+
}
|
|
36251
|
+
return issues;
|
|
36252
|
+
}
|
|
36253
|
+
});
|
|
36254
|
+
|
|
36255
|
+
// src/rules/java/empty-catch-block.ts
|
|
36256
|
+
var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
36257
|
+
var javaEmptyCatchBlockRule = createRule({
|
|
36258
|
+
id: "java/empty-catch-block",
|
|
36259
|
+
category: "logic",
|
|
36260
|
+
severity: "medium",
|
|
36261
|
+
aiSpecific: true,
|
|
36262
|
+
description: "Empty catch block \u2014 silently swallows exceptions",
|
|
36263
|
+
create(_context) {
|
|
36264
|
+
return {};
|
|
36265
|
+
},
|
|
36266
|
+
analyze(_context, facts) {
|
|
36267
|
+
const issues = [];
|
|
36268
|
+
const source = facts.v2?._source;
|
|
36269
|
+
if (!source) return issues;
|
|
36270
|
+
let m;
|
|
36271
|
+
SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
|
|
36272
|
+
while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
|
|
36273
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36274
|
+
issues.push({
|
|
36275
|
+
ruleId: "java/empty-catch-block",
|
|
36276
|
+
category: "logic",
|
|
36277
|
+
severity: "medium",
|
|
36278
|
+
aiSpecific: true,
|
|
36279
|
+
message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
|
|
36280
|
+
line,
|
|
36281
|
+
column: 1,
|
|
36282
|
+
advice: 'Log the exception (`log.error("...", e)`), re-throw it, or both. Empty catch blocks hide bugs. The pattern is common in AI-generated code that wants to look defensive. Reference: java/empty-catch-block v0.20.'
|
|
36283
|
+
});
|
|
36284
|
+
}
|
|
36285
|
+
return issues;
|
|
36286
|
+
}
|
|
36287
|
+
});
|
|
36288
|
+
|
|
36289
|
+
// src/rules/java/legacy-date-api.ts
|
|
36290
|
+
var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
|
|
36291
|
+
var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
|
|
36292
|
+
var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
|
|
36293
|
+
var javaLegacyDateApiRule = createRule({
|
|
36294
|
+
id: "java/legacy-date-api",
|
|
36295
|
+
category: "typo",
|
|
36296
|
+
severity: "low",
|
|
36297
|
+
aiSpecific: true,
|
|
36298
|
+
description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
|
|
36299
|
+
create(_context) {
|
|
36300
|
+
return {};
|
|
36301
|
+
},
|
|
36302
|
+
analyze(_context, facts) {
|
|
36303
|
+
const issues = [];
|
|
36304
|
+
const source = facts.v2?._source;
|
|
36305
|
+
if (!source) return issues;
|
|
36306
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
36307
|
+
let m;
|
|
36308
|
+
LEGACY_IMPORT_REGEX.lastIndex = 0;
|
|
36309
|
+
while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
|
|
36310
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36311
|
+
flagged.add(line);
|
|
36312
|
+
issues.push({
|
|
36313
|
+
ruleId: "java/legacy-date-api",
|
|
36314
|
+
category: "typo",
|
|
36315
|
+
severity: "low",
|
|
36316
|
+
aiSpecific: true,
|
|
36317
|
+
message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
|
|
36318
|
+
line,
|
|
36319
|
+
column: 1,
|
|
36320
|
+
advice: "Replace `java.util.Date` / `java.util.Calendar` with `java.time` (`LocalDate`, `LocalDateTime`, `Instant`, `ZonedDateTime`). java.time is immutable, thread-safe, and has a much better API. AI agents default to the legacy API because their training data predates Java 8 (2014). Reference: java/legacy-date-api v0.20."
|
|
36321
|
+
});
|
|
36322
|
+
}
|
|
36323
|
+
LEGACY_USAGE_REGEX.lastIndex = 0;
|
|
36324
|
+
while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
|
|
36325
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36326
|
+
if (flagged.has(line)) continue;
|
|
36327
|
+
issues.push({
|
|
36328
|
+
ruleId: "java/legacy-date-api",
|
|
36329
|
+
category: "typo",
|
|
36330
|
+
severity: "low",
|
|
36331
|
+
aiSpecific: true,
|
|
36332
|
+
message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
|
|
36333
|
+
line,
|
|
36334
|
+
column: 1,
|
|
36335
|
+
advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36336
|
+
});
|
|
36337
|
+
}
|
|
36338
|
+
CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
|
|
36339
|
+
while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
|
|
36340
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36341
|
+
if (flagged.has(line)) continue;
|
|
36342
|
+
issues.push({
|
|
36343
|
+
ruleId: "java/legacy-date-api",
|
|
36344
|
+
category: "typo",
|
|
36345
|
+
severity: "low",
|
|
36346
|
+
aiSpecific: true,
|
|
36347
|
+
message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
|
|
36348
|
+
line,
|
|
36349
|
+
column: 1,
|
|
36350
|
+
advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36351
|
+
});
|
|
36352
|
+
}
|
|
36353
|
+
return issues;
|
|
36354
|
+
}
|
|
36355
|
+
});
|
|
36356
|
+
|
|
36357
|
+
// src/rules/java/raw-type-overuse.ts
|
|
36358
|
+
var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
|
|
36359
|
+
var javaRawTypeOveruseRule = createRule({
|
|
36360
|
+
id: "java/raw-type-overuse",
|
|
36361
|
+
category: "typo",
|
|
36362
|
+
severity: "low",
|
|
36363
|
+
aiSpecific: true,
|
|
36364
|
+
description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
|
|
36365
|
+
create(_context) {
|
|
36366
|
+
return {};
|
|
36367
|
+
},
|
|
36368
|
+
analyze(_context, facts) {
|
|
36369
|
+
const issues = [];
|
|
36370
|
+
const source = facts.v2?._source;
|
|
36371
|
+
if (!source) return issues;
|
|
36372
|
+
let m;
|
|
36373
|
+
RAW_TYPE_REGEX.lastIndex = 0;
|
|
36374
|
+
while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
|
|
36375
|
+
const typeName = m[1];
|
|
36376
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36377
|
+
issues.push({
|
|
36378
|
+
ruleId: "java/raw-type-overuse",
|
|
36379
|
+
category: "typo",
|
|
36380
|
+
severity: "low",
|
|
36381
|
+
aiSpecific: true,
|
|
36382
|
+
message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
|
|
36383
|
+
line,
|
|
36384
|
+
column: 1,
|
|
36385
|
+
advice: `Replace raw \`${typeName}\` with \`${typeName}<...>\`. Raw types disable generic type checking. Effective Java, Item 23: 'Don't use raw types in new code'. AI agents default to raw types when unsure of the correct generic parameters. Reference: java/raw-type-overuse v0.20.`
|
|
36386
|
+
});
|
|
36387
|
+
}
|
|
36388
|
+
return issues;
|
|
36389
|
+
}
|
|
36390
|
+
});
|
|
36391
|
+
|
|
36392
|
+
// src/rules/java/string-concat-loop.ts
|
|
36393
|
+
var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
|
|
36394
|
+
var javaStringConcatLoopRule = createRule({
|
|
36395
|
+
id: "java/string-concat-loop",
|
|
36396
|
+
category: "perf",
|
|
36397
|
+
severity: "low",
|
|
36398
|
+
aiSpecific: true,
|
|
36399
|
+
description: "String concatenation in a loop \u2014 use StringBuilder",
|
|
36400
|
+
create(_context) {
|
|
36401
|
+
return {};
|
|
36402
|
+
},
|
|
36403
|
+
analyze(_context, facts) {
|
|
36404
|
+
const issues = [];
|
|
36405
|
+
const source = facts.v2?._source;
|
|
36406
|
+
if (!source) return issues;
|
|
36407
|
+
if (!/\b(for|while|do)\b/.test(source)) return issues;
|
|
36408
|
+
let m;
|
|
36409
|
+
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
36410
|
+
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
36411
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36412
|
+
issues.push({
|
|
36413
|
+
ruleId: "java/string-concat-loop",
|
|
36414
|
+
category: "perf",
|
|
36415
|
+
severity: "low",
|
|
36416
|
+
aiSpecific: true,
|
|
36417
|
+
message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
|
|
36418
|
+
line,
|
|
36419
|
+
column: 1,
|
|
36420
|
+
advice: "Declare a `StringBuilder` outside the loop: `StringBuilder sb = new StringBuilder(); sb.append(...);` then `return sb.toString();` after the loop. String concatenation in a loop is O(n\xB2) \u2014 each iteration copies the prior string. AI agents concatenate strings in loops because of training-data examples. Reference: java/string-concat-loop v0.20."
|
|
36421
|
+
});
|
|
36422
|
+
}
|
|
36423
|
+
return issues;
|
|
36424
|
+
}
|
|
36425
|
+
});
|
|
36426
|
+
|
|
36427
|
+
// src/rules/java/system-out-println.ts
|
|
36428
|
+
var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
|
|
36429
|
+
var DEFAULT_THRESHOLD = 1;
|
|
36430
|
+
var javaSystemOutPrintlnRule = createRule({
|
|
36431
|
+
id: "java/system-out-println",
|
|
36432
|
+
category: "typo",
|
|
36433
|
+
severity: "low",
|
|
36434
|
+
aiSpecific: true,
|
|
36435
|
+
description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
|
|
36436
|
+
create(_context) {
|
|
36437
|
+
return { threshold: DEFAULT_THRESHOLD };
|
|
36438
|
+
},
|
|
36439
|
+
analyze(context, facts) {
|
|
36440
|
+
const issues = [];
|
|
36441
|
+
const source = facts.v2?._source;
|
|
36442
|
+
if (!source) return issues;
|
|
36443
|
+
const matches = [];
|
|
36444
|
+
let m;
|
|
36445
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
36446
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
36447
|
+
matches.push(m.index);
|
|
36448
|
+
}
|
|
36449
|
+
if (matches.length <= context.threshold) return issues;
|
|
36450
|
+
const cap = Math.min(matches.length, 10);
|
|
36451
|
+
for (let i = 0; i < cap; i++) {
|
|
36452
|
+
const idx = matches[i];
|
|
36453
|
+
const line = source.slice(0, idx).split("\n").length;
|
|
36454
|
+
issues.push({
|
|
36455
|
+
ruleId: "java/system-out-println",
|
|
36456
|
+
category: "typo",
|
|
36457
|
+
severity: "low",
|
|
36458
|
+
aiSpecific: true,
|
|
36459
|
+
message: `System.out.println at line ${line} \u2014 use a logger for production output`,
|
|
36460
|
+
line,
|
|
36461
|
+
column: 1,
|
|
36462
|
+
advice: "Replace with `private static final Logger log = LoggerFactory.getLogger(...);` then `log.info(...)`. AI agents default to println because their training data has countless textbook examples. Real Java code uses a logger. Reference: java/system-out-println v0.20."
|
|
36463
|
+
});
|
|
36464
|
+
}
|
|
36465
|
+
return issues;
|
|
36466
|
+
}
|
|
36467
|
+
});
|
|
36468
|
+
|
|
36237
36469
|
// src/rules/layout/gap-monopoly.ts
|
|
36238
36470
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36239
36471
|
var gapMonopolyRule = createRule({
|
|
@@ -36586,16 +36818,16 @@ var DEFAULT_CONFIG = {
|
|
|
36586
36818
|
};
|
|
36587
36819
|
|
|
36588
36820
|
// src/config/detect/monorepo.ts
|
|
36589
|
-
import { existsSync as
|
|
36590
|
-
import { dirname as
|
|
36821
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
36822
|
+
import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
|
|
36591
36823
|
|
|
36592
36824
|
// src/config/detect/styling.ts
|
|
36593
|
-
import { existsSync as
|
|
36594
|
-
import { join as
|
|
36825
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
36826
|
+
import { join as join7, resolve as resolve3 } from "path";
|
|
36595
36827
|
|
|
36596
36828
|
// src/config/detect/stack.ts
|
|
36597
|
-
import { existsSync as
|
|
36598
|
-
import { extname as
|
|
36829
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
36830
|
+
import { extname as extname2, join as join8, resolve as resolve4 } from "path";
|
|
36599
36831
|
|
|
36600
36832
|
// src/config/presets.ts
|
|
36601
36833
|
var REACT_ONLY_RULES = {
|
|
@@ -36644,8 +36876,8 @@ var FRAMEWORK_PRESETS = {
|
|
|
36644
36876
|
};
|
|
36645
36877
|
|
|
36646
36878
|
// src/config/load.ts
|
|
36647
|
-
import { existsSync as existsSync8, readFileSync as
|
|
36648
|
-
import { dirname as
|
|
36879
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
36880
|
+
import { dirname as dirname5, extname as extname3, join as join10, resolve as resolve5 } from "path";
|
|
36649
36881
|
import { createRequire } from "module";
|
|
36650
36882
|
|
|
36651
36883
|
// src/engine/logger.ts
|
|
@@ -36667,6 +36899,10 @@ function setLoggerQuiet(quiet) {
|
|
|
36667
36899
|
logger = createLogger(quiet);
|
|
36668
36900
|
}
|
|
36669
36901
|
|
|
36902
|
+
// src/config/conventions.ts
|
|
36903
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
36904
|
+
import { join as join9 } from "path";
|
|
36905
|
+
|
|
36670
36906
|
// src/config/init.ts
|
|
36671
36907
|
var STRICTNESS_PRESETS = {
|
|
36672
36908
|
strict: {
|
|
@@ -37021,82 +37257,6 @@ var keyPropMissingRule = createRule({
|
|
|
37021
37257
|
}
|
|
37022
37258
|
});
|
|
37023
37259
|
|
|
37024
|
-
// src/rules/logic/ks-distribution-shift.ts
|
|
37025
|
-
var MIN_SAMPLES_PER_FEATURE = 20;
|
|
37026
|
-
function extractFileFeatures(source) {
|
|
37027
|
-
const lines = source.split("\n");
|
|
37028
|
-
const lineLengths = lines.map((l) => l.length);
|
|
37029
|
-
const identifierLengths = [];
|
|
37030
|
-
const idRe = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
37031
|
-
let m;
|
|
37032
|
-
while ((m = idRe.exec(source)) !== null) {
|
|
37033
|
-
identifierLengths.push(m[0].length);
|
|
37034
|
-
}
|
|
37035
|
-
const commentDensity = lines.map((l) => {
|
|
37036
|
-
const trimmed = l.trim();
|
|
37037
|
-
if (trimmed.length === 0) return 0;
|
|
37038
|
-
const commentChars = (trimmed.match(/^\/\/.*$/)?.[0]?.length ?? 0) + (trimmed.match(/^\s*\/\*.*?\*\/\s*$/)?.at(0)?.length ?? 0);
|
|
37039
|
-
return commentChars / trimmed.length;
|
|
37040
|
-
});
|
|
37041
|
-
return { lineLengths, identifierLengths, commentDensity };
|
|
37042
|
-
}
|
|
37043
|
-
var ksDistributionShiftRule = createRule({
|
|
37044
|
-
id: "logic/ks-distribution-shift",
|
|
37045
|
-
category: "logic",
|
|
37046
|
-
severity: "medium",
|
|
37047
|
-
aiSpecific: false,
|
|
37048
|
-
description: "Multi-feature Kolmogorov\u2013Smirnov distribution-shift vs corpus baseline (Bonferroni-corrected). Peer-reviewed ML distribution-shift detector (arXiv:2510.15996, Oct 2025).",
|
|
37049
|
-
create(context) {
|
|
37050
|
-
return context;
|
|
37051
|
-
},
|
|
37052
|
-
analyze(_context, facts) {
|
|
37053
|
-
const issues = [];
|
|
37054
|
-
if (!facts.v2) return issues;
|
|
37055
|
-
const source = facts.v2._source ?? "";
|
|
37056
|
-
if (source.length < 200) return issues;
|
|
37057
|
-
const features = extractFileFeatures(source);
|
|
37058
|
-
const samples = /* @__PURE__ */ new Map([
|
|
37059
|
-
["lineLengths", features.lineLengths],
|
|
37060
|
-
["identifierLengths", features.identifierLengths],
|
|
37061
|
-
["commentDensity", features.commentDensity]
|
|
37062
|
-
]);
|
|
37063
|
-
const baselines = getCorpusBaselines();
|
|
37064
|
-
const baselinesMap = /* @__PURE__ */ new Map();
|
|
37065
|
-
if (baselines) {
|
|
37066
|
-
baselinesMap.set("lineLengths", baselines.features.lineLengths.sample);
|
|
37067
|
-
baselinesMap.set("identifierLengths", baselines.features.identifierLengths.sample);
|
|
37068
|
-
baselinesMap.set("commentDensity", baselines.features.commentDensity.sample);
|
|
37069
|
-
} else {
|
|
37070
|
-
baselinesMap.set("lineLengths", [20, 25, 30, 32, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]);
|
|
37071
|
-
baselinesMap.set("identifierLengths", [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28]);
|
|
37072
|
-
baselinesMap.set("commentDensity", [0, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]);
|
|
37073
|
-
}
|
|
37074
|
-
for (const [name, vals] of samples) {
|
|
37075
|
-
if (vals.length < MIN_SAMPLES_PER_FEATURE) samples.delete(name);
|
|
37076
|
-
}
|
|
37077
|
-
if (samples.size === 0) return issues;
|
|
37078
|
-
const result = multiFeatureKsTest(samples, baselinesMap, 0.05);
|
|
37079
|
-
if (!result.anySignificant) return issues;
|
|
37080
|
-
const shifted = result.significantFeatures.join(", ");
|
|
37081
|
-
const details = result.significantFeatures.map((name) => {
|
|
37082
|
-
const r = result.perFeature.get(name);
|
|
37083
|
-
if (!r) return name;
|
|
37084
|
-
return `${name} (D=${r.statistic.toFixed(3)}, p=${r.pValue.toExponential(2)})`;
|
|
37085
|
-
}).join("; ");
|
|
37086
|
-
issues.push({
|
|
37087
|
-
ruleId: "logic/ks-distribution-shift",
|
|
37088
|
-
category: "logic",
|
|
37089
|
-
severity: "medium",
|
|
37090
|
-
aiSpecific: true,
|
|
37091
|
-
message: `Distribution shift detected on ${result.significantFeatures.length} of ${result.perFeature.size} features (Bonferroni \u03B1=${result.bonferroniAlpha.toExponential(2)}). Features: ${shifted}. Detail: ${details}.`,
|
|
37092
|
-
line: 1,
|
|
37093
|
-
column: 1,
|
|
37094
|
-
advice: "Inspect the shifted features. KS detects both AI anomalies and production-rot anomalies (it is symmetric); combine with Heaps/Zipf for AI-specific signal."
|
|
37095
|
-
});
|
|
37096
|
-
return issues;
|
|
37097
|
-
}
|
|
37098
|
-
});
|
|
37099
|
-
|
|
37100
37260
|
// src/rules/logic/math-any-density.ts
|
|
37101
37261
|
var ANY_PER_100_LINES = 5;
|
|
37102
37262
|
var MIN_ABSOLUTE = 6;
|
|
@@ -37158,7 +37318,7 @@ var mathAnyDensityRule = createRule({
|
|
|
37158
37318
|
});
|
|
37159
37319
|
|
|
37160
37320
|
// src/rules/logic/math-console-log-storm.ts
|
|
37161
|
-
var
|
|
37321
|
+
var WINDOW_SIZE2 = 30;
|
|
37162
37322
|
var STORM_THRESHOLD = 5;
|
|
37163
37323
|
var CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
|
|
37164
37324
|
var mathConsoleLogStormRule = createRule({
|
|
@@ -37189,7 +37349,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37189
37349
|
let maxEndLine = 0;
|
|
37190
37350
|
let i = 0;
|
|
37191
37351
|
for (let j = 0; j < lines.length; j++) {
|
|
37192
|
-
while (lines[j] - lines[i] >
|
|
37352
|
+
while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
|
|
37193
37353
|
const count = j - i + 1;
|
|
37194
37354
|
if (count > maxCount) {
|
|
37195
37355
|
maxCount = count;
|
|
@@ -37203,7 +37363,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37203
37363
|
category: "logic",
|
|
37204
37364
|
severity: "high",
|
|
37205
37365
|
aiSpecific: true,
|
|
37206
|
-
message: `${maxCount} console.log calls clustered in a ${
|
|
37366
|
+
message: `${maxCount} console.log calls clustered in a ${WINDOW_SIZE2}-line window ending at line ${maxEndLine}. AI debug-sprays logs in a single function; humans use one strategic log.`,
|
|
37207
37367
|
line: firstIdx >= 0 ? lines[firstIdx] : 1,
|
|
37208
37368
|
column: firstIdx >= 0 ? columns[firstIdx] : 1,
|
|
37209
37369
|
advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
|
|
@@ -38108,7 +38268,6 @@ function scanForStringlyParams(paramText) {
|
|
|
38108
38268
|
}
|
|
38109
38269
|
|
|
38110
38270
|
// src/rules/rust/todo-macro.ts
|
|
38111
|
-
init_parser_rust();
|
|
38112
38271
|
var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
|
|
38113
38272
|
var rustTodoMacroRule = createRule({
|
|
38114
38273
|
id: "rust/todo-macro",
|
|
@@ -38244,7 +38403,6 @@ function collectReferencedNames(source) {
|
|
|
38244
38403
|
}
|
|
38245
38404
|
|
|
38246
38405
|
// src/rules/rust/unwrap-in-production.ts
|
|
38247
|
-
init_parser_rust();
|
|
38248
38406
|
var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
|
|
38249
38407
|
var rustUnwrapInProductionRule = createRule({
|
|
38250
38408
|
id: "rust/unwrap-in-production",
|
|
@@ -39007,7 +39165,7 @@ var unsafeHtmlRenderRule = createRule({
|
|
|
39007
39165
|
});
|
|
39008
39166
|
|
|
39009
39167
|
// src/rules/test/utils.ts
|
|
39010
|
-
import { createHash as
|
|
39168
|
+
import { createHash as createHash3 } from "crypto";
|
|
39011
39169
|
function isTestFile(filePath) {
|
|
39012
39170
|
const normalized = filePath.replace(/\\/g, "/");
|
|
39013
39171
|
if (/(^|\/)__tests__\//.test(normalized)) return true;
|
|
@@ -39167,7 +39325,7 @@ function extractSetupBlocks(source, minLines = 3) {
|
|
|
39167
39325
|
}).trim();
|
|
39168
39326
|
const lineCount = rawBody.split("\n").length;
|
|
39169
39327
|
if (lineCount < minLines) continue;
|
|
39170
|
-
const bodyHash =
|
|
39328
|
+
const bodyHash = createHash3("sha1").update(normalizedBody).digest("hex");
|
|
39171
39329
|
const { line, column } = lineColumnOf(source, m.index);
|
|
39172
39330
|
hits.push({
|
|
39173
39331
|
kind,
|
|
@@ -39381,7 +39539,7 @@ function looksRealistic(value) {
|
|
|
39381
39539
|
|
|
39382
39540
|
// src/rules/test/missing-edge-case.ts
|
|
39383
39541
|
import { parseSync as parseSync2 } from "@swc/core";
|
|
39384
|
-
import { existsSync as existsSync9, readFileSync as
|
|
39542
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
39385
39543
|
var MAX_PER_FILE = 20;
|
|
39386
39544
|
var missingEdgeCaseRule = createRule({
|
|
39387
39545
|
id: "test/missing-edge-case",
|
|
@@ -39403,7 +39561,7 @@ var missingEdgeCaseRule = createRule({
|
|
|
39403
39561
|
const testFileSources = /* @__PURE__ */ new Map();
|
|
39404
39562
|
for (const testFile of discoverTestFiles(cwd)) {
|
|
39405
39563
|
try {
|
|
39406
|
-
testFileSources.set(testFile,
|
|
39564
|
+
testFileSources.set(testFile, readFileSync8(testFile, "utf-8"));
|
|
39407
39565
|
} catch {
|
|
39408
39566
|
}
|
|
39409
39567
|
}
|
|
@@ -39777,6 +39935,216 @@ function isTautologicalAssertion(hit) {
|
|
|
39777
39935
|
return false;
|
|
39778
39936
|
}
|
|
39779
39937
|
|
|
39938
|
+
// src/rules/ts/enum-vs-as-const.ts
|
|
39939
|
+
var ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
|
|
39940
|
+
var tsEnumVsAsConstRule = createRule({
|
|
39941
|
+
id: "ts/enum-vs-as-const",
|
|
39942
|
+
category: "typo",
|
|
39943
|
+
severity: "low",
|
|
39944
|
+
aiSpecific: true,
|
|
39945
|
+
description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
|
|
39946
|
+
create(_context) {
|
|
39947
|
+
return {};
|
|
39948
|
+
},
|
|
39949
|
+
analyze(_context, facts) {
|
|
39950
|
+
const issues = [];
|
|
39951
|
+
const source = facts.v2?._source;
|
|
39952
|
+
if (!source) return issues;
|
|
39953
|
+
let match;
|
|
39954
|
+
ENUM_DECL_REGEX.lastIndex = 0;
|
|
39955
|
+
while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
|
|
39956
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
39957
|
+
issues.push({
|
|
39958
|
+
ruleId: "ts/enum-vs-as-const",
|
|
39959
|
+
category: "typo",
|
|
39960
|
+
severity: "low",
|
|
39961
|
+
aiSpecific: true,
|
|
39962
|
+
message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
|
|
39963
|
+
line,
|
|
39964
|
+
column: match[0].indexOf("enum") + 1,
|
|
39965
|
+
advice: 'Replace `enum Foo { A, B }` with `const Foo = { A: "A", B: "B" } as const` (or `const Foo = ["A", "B"] as const`). Modern TS style guides (Google, TS-eslint) prefer `as const` because enums have surprising runtime semantics. Reference: ts/enum-vs-as-const v0.19.'
|
|
39966
|
+
});
|
|
39967
|
+
}
|
|
39968
|
+
return issues;
|
|
39969
|
+
}
|
|
39970
|
+
});
|
|
39971
|
+
|
|
39972
|
+
// src/rules/ts/excessive-type-assertion.ts
|
|
39973
|
+
var DEFAULT_MAX = 3;
|
|
39974
|
+
var FN_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\([^)]*\)\s*[^{]*\{|^\s*(?:export\s+)?(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>\s*\{|^\s*(?:public|private|protected|static|async|abstract|readonly|\s)*\s*[A-Za-z_$][\w$]*\s*\([^)]*\)\s*:\s*[^{]*\{/gm;
|
|
39975
|
+
var AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
|
|
39976
|
+
var tsExcessiveTypeAssertionRule = createRule({
|
|
39977
|
+
id: "ts/excessive-type-assertion",
|
|
39978
|
+
category: "typo",
|
|
39979
|
+
severity: "low",
|
|
39980
|
+
aiSpecific: true,
|
|
39981
|
+
description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
|
|
39982
|
+
create(_context) {
|
|
39983
|
+
return { maxAssertionsPerFunction: DEFAULT_MAX };
|
|
39984
|
+
},
|
|
39985
|
+
analyze(context, facts) {
|
|
39986
|
+
const issues = [];
|
|
39987
|
+
const source = facts.v2?._source;
|
|
39988
|
+
if (!source) return issues;
|
|
39989
|
+
let match;
|
|
39990
|
+
FN_DECL_REGEX.lastIndex = 0;
|
|
39991
|
+
while ((match = FN_DECL_REGEX.exec(source)) !== null) {
|
|
39992
|
+
const startIdx = match.index;
|
|
39993
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
39994
|
+
if (openBraceIdx < 0) continue;
|
|
39995
|
+
let depth = 1;
|
|
39996
|
+
let i = openBraceIdx + 1;
|
|
39997
|
+
while (i < source.length && depth > 0) {
|
|
39998
|
+
const ch = source[i];
|
|
39999
|
+
if (ch === "{") depth++;
|
|
40000
|
+
else if (ch === "}") depth--;
|
|
40001
|
+
i++;
|
|
40002
|
+
}
|
|
40003
|
+
const body = source.slice(openBraceIdx, i);
|
|
40004
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
40005
|
+
let asCount = 0;
|
|
40006
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40007
|
+
let asMatch;
|
|
40008
|
+
AS_REGEX.lastIndex = 0;
|
|
40009
|
+
while ((asMatch = AS_REGEX.exec(body)) !== null) {
|
|
40010
|
+
const captured = asMatch[1];
|
|
40011
|
+
if (seen.has(captured)) continue;
|
|
40012
|
+
seen.add(captured);
|
|
40013
|
+
asCount++;
|
|
40014
|
+
}
|
|
40015
|
+
if (asCount > context.maxAssertionsPerFunction) {
|
|
40016
|
+
issues.push({
|
|
40017
|
+
ruleId: "ts/excessive-type-assertion",
|
|
40018
|
+
category: "typo",
|
|
40019
|
+
severity: "low",
|
|
40020
|
+
aiSpecific: true,
|
|
40021
|
+
message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
|
|
40022
|
+
line,
|
|
40023
|
+
column: 1,
|
|
40024
|
+
advice: "More than 3 `as` assertions in a function is a strong signal that the type is wrong, not the code. Fix the type definition (or use a type guard) instead of bypassing the type system. Reference: ts/excessive-type-assertion v0.19."
|
|
40025
|
+
});
|
|
40026
|
+
}
|
|
40027
|
+
}
|
|
40028
|
+
return issues;
|
|
40029
|
+
}
|
|
40030
|
+
});
|
|
40031
|
+
|
|
40032
|
+
// src/rules/ts/import-type-misuse.ts
|
|
40033
|
+
var INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
|
|
40034
|
+
var tsImportTypeMisuseRule = createRule({
|
|
40035
|
+
id: "ts/import-type-misuse",
|
|
40036
|
+
category: "typo",
|
|
40037
|
+
severity: "low",
|
|
40038
|
+
aiSpecific: true,
|
|
40039
|
+
description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
|
|
40040
|
+
create(_context) {
|
|
40041
|
+
return {};
|
|
40042
|
+
},
|
|
40043
|
+
analyze(_context, facts) {
|
|
40044
|
+
const issues = [];
|
|
40045
|
+
const source = facts.v2?._source;
|
|
40046
|
+
if (!source) return issues;
|
|
40047
|
+
let match;
|
|
40048
|
+
INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
|
|
40049
|
+
while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
|
|
40050
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
40051
|
+
issues.push({
|
|
40052
|
+
ruleId: "ts/import-type-misuse",
|
|
40053
|
+
category: "typo",
|
|
40054
|
+
severity: "low",
|
|
40055
|
+
aiSpecific: true,
|
|
40056
|
+
message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
|
|
40057
|
+
line,
|
|
40058
|
+
column: match[0].indexOf("type") + 1,
|
|
40059
|
+
advice: 'Use `import type { X } from "..."` instead of `import { type X } from "..."`. The inline form is valid but the split form is more common in real codebases and makes the type-only intent unambiguous. Reference: ts/import-type-misuse v0.19.'
|
|
40060
|
+
});
|
|
40061
|
+
}
|
|
40062
|
+
return issues;
|
|
40063
|
+
}
|
|
40064
|
+
});
|
|
40065
|
+
|
|
40066
|
+
// src/rules/ts/never-vs-unknown.ts
|
|
40067
|
+
var NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
|
|
40068
|
+
var THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
|
|
40069
|
+
var tsNeverVsUnknownRule = createRule({
|
|
40070
|
+
id: "ts/never-vs-unknown",
|
|
40071
|
+
category: "typo",
|
|
40072
|
+
severity: "low",
|
|
40073
|
+
aiSpecific: true,
|
|
40074
|
+
description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
|
|
40075
|
+
create(_context) {
|
|
40076
|
+
return {};
|
|
40077
|
+
},
|
|
40078
|
+
analyze(_context, facts) {
|
|
40079
|
+
const issues = [];
|
|
40080
|
+
const source = facts.v2?._source;
|
|
40081
|
+
if (!source) return issues;
|
|
40082
|
+
let match;
|
|
40083
|
+
NEVER_RETURN_REGEX.lastIndex = 0;
|
|
40084
|
+
while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
|
|
40085
|
+
const fnName = match[1];
|
|
40086
|
+
const startIdx = match.index;
|
|
40087
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
40088
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
40089
|
+
if (openBraceIdx < 0) continue;
|
|
40090
|
+
let depth = 1;
|
|
40091
|
+
let i = openBraceIdx + 1;
|
|
40092
|
+
while (i < source.length && depth > 0) {
|
|
40093
|
+
const ch = source[i];
|
|
40094
|
+
if (ch === "{") depth++;
|
|
40095
|
+
else if (ch === "}") depth--;
|
|
40096
|
+
i++;
|
|
40097
|
+
}
|
|
40098
|
+
const body = source.slice(openBraceIdx, i);
|
|
40099
|
+
if (THROW_OR_LOOP_REGEX.test(body)) continue;
|
|
40100
|
+
issues.push({
|
|
40101
|
+
ruleId: "ts/never-vs-unknown",
|
|
40102
|
+
category: "typo",
|
|
40103
|
+
severity: "low",
|
|
40104
|
+
aiSpecific: true,
|
|
40105
|
+
message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
|
|
40106
|
+
line,
|
|
40107
|
+
column: match[0].indexOf("never") + 1,
|
|
40108
|
+
advice: 'The `never` return type means "this function never returns". Reserve it for functions that always throw, always loop, or always exit. For "impossible" branches, use a concrete type (`void`, `Error`, `unknown`) and an exhaustive check. Reference: ts/never-vs-unknown v0.19.'
|
|
40109
|
+
});
|
|
40110
|
+
}
|
|
40111
|
+
return issues;
|
|
40112
|
+
}
|
|
40113
|
+
});
|
|
40114
|
+
|
|
40115
|
+
// src/rules/ts/optional-chain-overuse.ts
|
|
40116
|
+
var DEFAULT_MIN_CHAIN_LENGTH = 5;
|
|
40117
|
+
var tsOptionalChainOveruseRule = createRule({
|
|
40118
|
+
id: "ts/optional-chain-overuse",
|
|
40119
|
+
category: "logic",
|
|
40120
|
+
severity: "low",
|
|
40121
|
+
aiSpecific: true,
|
|
40122
|
+
description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
|
|
40123
|
+
create(_context) {
|
|
40124
|
+
return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
|
|
40125
|
+
},
|
|
40126
|
+
analyze(context, facts) {
|
|
40127
|
+
const issues = [];
|
|
40128
|
+
const expressions = facts.v2.logic?.logicalExpressions;
|
|
40129
|
+
if (!expressions) return issues;
|
|
40130
|
+
for (const expression of expressions) {
|
|
40131
|
+
if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
|
|
40132
|
+
issues.push({
|
|
40133
|
+
ruleId: "ts/optional-chain-overuse",
|
|
40134
|
+
category: "logic",
|
|
40135
|
+
severity: "low",
|
|
40136
|
+
aiSpecific: true,
|
|
40137
|
+
message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
|
|
40138
|
+
line: expression.line,
|
|
40139
|
+
column: expression.column,
|
|
40140
|
+
advice: "Long optional chains are an AI pattern. Use a guard clause (`if (!value) return`) or intermediate variables to make the narrowing explicit. Reference: ts/optional-chain-overuse v0.19."
|
|
40141
|
+
});
|
|
40142
|
+
}
|
|
40143
|
+
}
|
|
40144
|
+
return issues;
|
|
40145
|
+
}
|
|
40146
|
+
});
|
|
40147
|
+
|
|
39780
40148
|
// src/rules/typo/calc-fontsize.ts
|
|
39781
40149
|
var FONT_SIZE_RE = /\bfont-size\s*:\s*[^;]*\bcalc\s*\(/i;
|
|
39782
40150
|
var calcFontsizeRule = createRule({
|
|
@@ -41314,9 +41682,18 @@ var builtinRules = [
|
|
|
41314
41682
|
unusedLocalRule,
|
|
41315
41683
|
unusedParameterRule,
|
|
41316
41684
|
brokenLinkRule,
|
|
41317
|
-
expiredCodeExampleRule,
|
|
41318
41685
|
staleFunctionReferenceRule,
|
|
41319
41686
|
stalePackageReferenceRule,
|
|
41687
|
+
dupIdenticalBlockRule,
|
|
41688
|
+
goErrorWrapWithoutContextRule,
|
|
41689
|
+
goNilSliceVsEmptyRule,
|
|
41690
|
+
goStructTagInconsistencyRule,
|
|
41691
|
+
javaArraylistVsLinkedlistRule,
|
|
41692
|
+
javaEmptyCatchBlockRule,
|
|
41693
|
+
javaLegacyDateApiRule,
|
|
41694
|
+
javaRawTypeOveruseRule,
|
|
41695
|
+
javaStringConcatLoopRule,
|
|
41696
|
+
javaSystemOutPrintlnRule,
|
|
41320
41697
|
gapMonopolyRule,
|
|
41321
41698
|
mathElementUniformityRule,
|
|
41322
41699
|
mathGridUniformityRule,
|
|
@@ -41326,7 +41703,6 @@ var builtinRules = [
|
|
|
41326
41703
|
ghostDefensiveRule,
|
|
41327
41704
|
heapsDeviationRule,
|
|
41328
41705
|
keyPropMissingRule,
|
|
41329
|
-
ksDistributionShiftRule,
|
|
41330
41706
|
mathAnyDensityRule,
|
|
41331
41707
|
mathConsoleLogStormRule,
|
|
41332
41708
|
mathGiniClassUsageRule,
|
|
@@ -41360,6 +41736,11 @@ var builtinRules = [
|
|
|
41360
41736
|
fakePlaceholderRule,
|
|
41361
41737
|
missingEdgeCaseRule,
|
|
41362
41738
|
weakAssertionRule,
|
|
41739
|
+
tsEnumVsAsConstRule,
|
|
41740
|
+
tsExcessiveTypeAssertionRule,
|
|
41741
|
+
tsImportTypeMisuseRule,
|
|
41742
|
+
tsNeverVsUnknownRule,
|
|
41743
|
+
tsOptionalChainOveruseRule,
|
|
41363
41744
|
calcFontsizeRule,
|
|
41364
41745
|
calcRawPxRule,
|
|
41365
41746
|
clampOffscaleRule,
|
|
@@ -41530,7 +41911,7 @@ var signal_strength_default = {
|
|
|
41530
41911
|
precision: 0.9966,
|
|
41531
41912
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41532
41913
|
verdict: "USEFUL",
|
|
41533
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=294, FP=1, P=99.7%, FPR=0.00%, lift=251225.49. v7 was USEFUL (TP=231, FP=1, lift=182622.43). v8 was USEFUL (TP=63, FP=0).",
|
|
41914
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=294, FP=1, P=99.7%, FPR=0.00%, lift=251225.49. v7 was USEFUL (TP=231, FP=1, lift=182622.43). v8 was USEFUL (TP=63, FP=0). v0.19 default-on (explicit defaultOff: false): P 99.7% / 251k lift \u2014 core AI fingerprint.",
|
|
41534
41915
|
aiSpecific: true,
|
|
41535
41916
|
_v7Verdict: "USEFUL",
|
|
41536
41917
|
_v7Lift: 182622.43,
|
|
@@ -41538,7 +41919,8 @@ var signal_strength_default = {
|
|
|
41538
41919
|
_v7FpRate: 0,
|
|
41539
41920
|
_v7Precision: 0.9957,
|
|
41540
41921
|
_v8Verdict: "USEFUL",
|
|
41541
|
-
_v8Lift: 99999
|
|
41922
|
+
_v8Lift: 99999,
|
|
41923
|
+
defaultOff: false
|
|
41542
41924
|
},
|
|
41543
41925
|
"ai/errors-near-eof": {
|
|
41544
41926
|
recall: 0.0948,
|
|
@@ -41787,7 +42169,7 @@ var signal_strength_default = {
|
|
|
41787
42169
|
precision: 0.9512,
|
|
41788
42170
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41789
42171
|
verdict: "USEFUL",
|
|
41790
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=468, FP=24, P=95.1%, FPR=0.01%, lift=9990.98. v7 was USEFUL (TP=314, FP=24, lift=7099.57). v8 was USEFUL (TP=154, FP=0).",
|
|
42172
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=468, FP=24, P=95.1%, FPR=0.01%, lift=9990.98. v7 was USEFUL (TP=314, FP=24, lift=7099.57). v8 was USEFUL (TP=154, FP=0). v0.19 default-on (explicit defaultOff: false): P 95.1% / 9990x lift \u2014 UI bug.",
|
|
41791
42173
|
aiSpecific: true,
|
|
41792
42174
|
_v7Verdict: "USEFUL",
|
|
41793
42175
|
_v7Lift: 7099.57,
|
|
@@ -41795,7 +42177,8 @@ var signal_strength_default = {
|
|
|
41795
42177
|
_v7FpRate: 1e-4,
|
|
41796
42178
|
_v7Precision: 0.929,
|
|
41797
42179
|
_v8Verdict: "USEFUL",
|
|
41798
|
-
_v8Lift: 99999
|
|
42180
|
+
_v8Lift: 99999,
|
|
42181
|
+
defaultOff: false
|
|
41799
42182
|
},
|
|
41800
42183
|
"context/import-path-mismatch": {
|
|
41801
42184
|
recall: 0.0681,
|
|
@@ -41979,7 +42362,7 @@ var signal_strength_default = {
|
|
|
41979
42362
|
precision: 0.8834,
|
|
41980
42363
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41981
42364
|
verdict: "USEFUL",
|
|
41982
|
-
_calibrationNote: "v8.5 (v0.18.9): v85 verdict=USEFUL, v7 was DORMANT, v8 was USEFUL. v0.18.8 v8a first measurement (1000 files): see docs/research/v0.18.8-dead-rules-measurement.md.",
|
|
42365
|
+
_calibrationNote: "v8.5 (v0.18.9): v85 verdict=USEFUL, v7 was DORMANT, v8 was USEFUL. v0.18.8 v8a first measurement (1000 files): see docs/research/v0.18.8-dead-rules-measurement.md. v0.19 default-on (explicit defaultOff: false): P 88.3% / FPR 0.74% / 120x lift \u2014 code hygiene.",
|
|
41983
42366
|
aiSpecific: true,
|
|
41984
42367
|
_v7Verdict: "DORMANT",
|
|
41985
42368
|
_v7Lift: 1,
|
|
@@ -41987,7 +42370,8 @@ var signal_strength_default = {
|
|
|
41987
42370
|
_v7FpRate: 0,
|
|
41988
42371
|
_v7Precision: 0,
|
|
41989
42372
|
_v8Verdict: "USEFUL",
|
|
41990
|
-
_v8Lift: 32.74
|
|
42373
|
+
_v8Lift: 32.74,
|
|
42374
|
+
defaultOff: false
|
|
41991
42375
|
},
|
|
41992
42376
|
"dead/unused-parameter": {
|
|
41993
42377
|
recall: 8e-4,
|
|
@@ -42023,24 +42407,6 @@ var signal_strength_default = {
|
|
|
42023
42407
|
_v8Verdict: "OK",
|
|
42024
42408
|
_v8Lift: 48.52
|
|
42025
42409
|
},
|
|
42026
|
-
"docs/expired-code-example": {
|
|
42027
|
-
recall: 0,
|
|
42028
|
-
fpRate: 0,
|
|
42029
|
-
ratio: 0,
|
|
42030
|
-
precision: 0,
|
|
42031
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42032
|
-
verdict: "INVERTED",
|
|
42033
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=0, FP=3, P=0.0%, FPR=0.00%, lift=0.00. v7 was DORMANT (TP=0, FP=0, lift=1.00). v8 was INVERTED (TP=0, FP=3).",
|
|
42034
|
-
aiSpecific: false,
|
|
42035
|
-
_v7Verdict: "DORMANT",
|
|
42036
|
-
_v7Lift: 1,
|
|
42037
|
-
_v7Recall: 0,
|
|
42038
|
-
_v7FpRate: 0,
|
|
42039
|
-
_v7Precision: 0,
|
|
42040
|
-
_v8Verdict: "INVERTED",
|
|
42041
|
-
_v8Lift: 0,
|
|
42042
|
-
defaultOff: true
|
|
42043
|
-
},
|
|
42044
42410
|
"docs/stale-function-reference": {
|
|
42045
42411
|
recall: 26e-4,
|
|
42046
42412
|
fpRate: 5e-4,
|
|
@@ -42075,6 +42441,186 @@ var signal_strength_default = {
|
|
|
42075
42441
|
_v8Verdict: "OK",
|
|
42076
42442
|
_v8Lift: 255.9
|
|
42077
42443
|
},
|
|
42444
|
+
"dup/identical-block": {
|
|
42445
|
+
recall: 0,
|
|
42446
|
+
fpRate: 0,
|
|
42447
|
+
ratio: 1,
|
|
42448
|
+
precision: 0,
|
|
42449
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42450
|
+
verdict: "DORMANT",
|
|
42451
|
+
_calibrationNote: "v0.19: new rule, not yet calibrated. v8.5 calibration does not run dup/* rules. Scheduled for v0.20 calibration on near-dup corpus.",
|
|
42452
|
+
aiSpecific: false,
|
|
42453
|
+
_v7Verdict: "DORMANT",
|
|
42454
|
+
_v7Lift: 1,
|
|
42455
|
+
_v7Recall: 0,
|
|
42456
|
+
_v7FpRate: 0,
|
|
42457
|
+
_v7Precision: 0,
|
|
42458
|
+
_v8Verdict: "DORMANT",
|
|
42459
|
+
_v8Lift: 1,
|
|
42460
|
+
defaultOff: true
|
|
42461
|
+
},
|
|
42462
|
+
"go/error-wrap-without-context": {
|
|
42463
|
+
recall: 0,
|
|
42464
|
+
fpRate: 0,
|
|
42465
|
+
ratio: 1,
|
|
42466
|
+
precision: 0,
|
|
42467
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42468
|
+
verdict: "DORMANT",
|
|
42469
|
+
_calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42470
|
+
aiSpecific: true,
|
|
42471
|
+
_v7Verdict: "DORMANT",
|
|
42472
|
+
_v7Lift: 1,
|
|
42473
|
+
_v7Recall: 0,
|
|
42474
|
+
_v7FpRate: 0,
|
|
42475
|
+
_v7Precision: 0,
|
|
42476
|
+
_v8Verdict: "DORMANT",
|
|
42477
|
+
_v8Lift: 1,
|
|
42478
|
+
defaultOff: true
|
|
42479
|
+
},
|
|
42480
|
+
"go/nil-slice-vs-empty": {
|
|
42481
|
+
recall: 0,
|
|
42482
|
+
fpRate: 0,
|
|
42483
|
+
ratio: 1,
|
|
42484
|
+
precision: 0,
|
|
42485
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42486
|
+
verdict: "DORMANT",
|
|
42487
|
+
_calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42488
|
+
aiSpecific: true,
|
|
42489
|
+
_v7Verdict: "DORMANT",
|
|
42490
|
+
_v7Lift: 1,
|
|
42491
|
+
_v7Recall: 0,
|
|
42492
|
+
_v7FpRate: 0,
|
|
42493
|
+
_v7Precision: 0,
|
|
42494
|
+
_v8Verdict: "DORMANT",
|
|
42495
|
+
_v8Lift: 1,
|
|
42496
|
+
defaultOff: true
|
|
42497
|
+
},
|
|
42498
|
+
"go/struct-tag-inconsistency": {
|
|
42499
|
+
recall: 0,
|
|
42500
|
+
fpRate: 0,
|
|
42501
|
+
ratio: 1,
|
|
42502
|
+
precision: 0,
|
|
42503
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42504
|
+
verdict: "DORMANT",
|
|
42505
|
+
_calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42506
|
+
aiSpecific: true,
|
|
42507
|
+
_v7Verdict: "DORMANT",
|
|
42508
|
+
_v7Lift: 1,
|
|
42509
|
+
_v7Recall: 0,
|
|
42510
|
+
_v7FpRate: 0,
|
|
42511
|
+
_v7Precision: 0,
|
|
42512
|
+
_v8Verdict: "DORMANT",
|
|
42513
|
+
_v8Lift: 1,
|
|
42514
|
+
defaultOff: true
|
|
42515
|
+
},
|
|
42516
|
+
"java/arraylist-vs-linkedlist": {
|
|
42517
|
+
recall: 0,
|
|
42518
|
+
fpRate: 0,
|
|
42519
|
+
ratio: 1,
|
|
42520
|
+
precision: 0,
|
|
42521
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42522
|
+
verdict: "DORMANT",
|
|
42523
|
+
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42524
|
+
aiSpecific: true,
|
|
42525
|
+
_v7Verdict: "DORMANT",
|
|
42526
|
+
_v7Lift: 1,
|
|
42527
|
+
_v7Recall: 0,
|
|
42528
|
+
_v7FpRate: 0,
|
|
42529
|
+
_v7Precision: 0,
|
|
42530
|
+
_v8Verdict: "DORMANT",
|
|
42531
|
+
_v8Lift: 1,
|
|
42532
|
+
defaultOff: true
|
|
42533
|
+
},
|
|
42534
|
+
"java/empty-catch-block": {
|
|
42535
|
+
recall: 0,
|
|
42536
|
+
fpRate: 0,
|
|
42537
|
+
ratio: 1,
|
|
42538
|
+
precision: 0,
|
|
42539
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42540
|
+
verdict: "DORMANT",
|
|
42541
|
+
_calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42542
|
+
aiSpecific: true,
|
|
42543
|
+
_v7Verdict: "DORMANT",
|
|
42544
|
+
_v7Lift: 1,
|
|
42545
|
+
_v7Recall: 0,
|
|
42546
|
+
_v7FpRate: 0,
|
|
42547
|
+
_v7Precision: 0,
|
|
42548
|
+
_v8Verdict: "DORMANT",
|
|
42549
|
+
_v8Lift: 1,
|
|
42550
|
+
defaultOff: true
|
|
42551
|
+
},
|
|
42552
|
+
"java/legacy-date-api": {
|
|
42553
|
+
recall: 0,
|
|
42554
|
+
fpRate: 0,
|
|
42555
|
+
ratio: 1,
|
|
42556
|
+
precision: 0,
|
|
42557
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42558
|
+
verdict: "DORMANT",
|
|
42559
|
+
_calibrationNote: "v0.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42560
|
+
aiSpecific: true,
|
|
42561
|
+
_v7Verdict: "DORMANT",
|
|
42562
|
+
_v7Lift: 1,
|
|
42563
|
+
_v7Recall: 0,
|
|
42564
|
+
_v7FpRate: 0,
|
|
42565
|
+
_v7Precision: 0,
|
|
42566
|
+
_v8Verdict: "DORMANT",
|
|
42567
|
+
_v8Lift: 1,
|
|
42568
|
+
defaultOff: true
|
|
42569
|
+
},
|
|
42570
|
+
"java/raw-type-overuse": {
|
|
42571
|
+
recall: 0,
|
|
42572
|
+
fpRate: 0,
|
|
42573
|
+
ratio: 1,
|
|
42574
|
+
precision: 0,
|
|
42575
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42576
|
+
verdict: "DORMANT",
|
|
42577
|
+
_calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
|
|
42578
|
+
aiSpecific: true,
|
|
42579
|
+
_v7Verdict: "DORMANT",
|
|
42580
|
+
_v7Lift: 1,
|
|
42581
|
+
_v7Recall: 0,
|
|
42582
|
+
_v7FpRate: 0,
|
|
42583
|
+
_v7Precision: 0,
|
|
42584
|
+
_v8Verdict: "DORMANT",
|
|
42585
|
+
_v8Lift: 1,
|
|
42586
|
+
defaultOff: true
|
|
42587
|
+
},
|
|
42588
|
+
"java/string-concat-loop": {
|
|
42589
|
+
recall: 0,
|
|
42590
|
+
fpRate: 0,
|
|
42591
|
+
ratio: 1,
|
|
42592
|
+
precision: 0,
|
|
42593
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42594
|
+
verdict: "DORMANT",
|
|
42595
|
+
_calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
|
|
42596
|
+
aiSpecific: true,
|
|
42597
|
+
_v7Verdict: "DORMANT",
|
|
42598
|
+
_v7Lift: 1,
|
|
42599
|
+
_v7Recall: 0,
|
|
42600
|
+
_v7FpRate: 0,
|
|
42601
|
+
_v7Precision: 0,
|
|
42602
|
+
_v8Verdict: "DORMANT",
|
|
42603
|
+
_v8Lift: 1,
|
|
42604
|
+
defaultOff: true
|
|
42605
|
+
},
|
|
42606
|
+
"java/system-out-println": {
|
|
42607
|
+
recall: 0,
|
|
42608
|
+
fpRate: 0,
|
|
42609
|
+
ratio: 1,
|
|
42610
|
+
precision: 0,
|
|
42611
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42612
|
+
verdict: "DORMANT",
|
|
42613
|
+
_calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42614
|
+
aiSpecific: true,
|
|
42615
|
+
_v7Verdict: "DORMANT",
|
|
42616
|
+
_v7Lift: 1,
|
|
42617
|
+
_v7Recall: 0,
|
|
42618
|
+
_v7FpRate: 0,
|
|
42619
|
+
_v7Precision: 0,
|
|
42620
|
+
_v8Verdict: "DORMANT",
|
|
42621
|
+
_v8Lift: 1,
|
|
42622
|
+
defaultOff: true
|
|
42623
|
+
},
|
|
42078
42624
|
"layout/forced-layout": {
|
|
42079
42625
|
recall: 0,
|
|
42080
42626
|
fpRate: 0,
|
|
@@ -42203,7 +42749,7 @@ var signal_strength_default = {
|
|
|
42203
42749
|
precision: 0.8889,
|
|
42204
42750
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42205
42751
|
verdict: "USEFUL",
|
|
42206
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=16, FP=2, P=88.9%, FPR=0.00%, lift=112035.56. v7 was USEFUL (TP=15, FP=2, lift=80917.50). v8 was USEFUL (TP=1, FP=0).",
|
|
42752
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=16, FP=2, P=88.9%, FPR=0.00%, lift=112035.56. v7 was USEFUL (TP=15, FP=2, lift=80917.50). v8 was USEFUL (TP=1, FP=0). v0.19 default-on (explicit defaultOff: false): P 88.9% / 112k lift \u2014 code smell.",
|
|
42207
42753
|
aiSpecific: true,
|
|
42208
42754
|
_v7Verdict: "USEFUL",
|
|
42209
42755
|
_v7Lift: 80917.5,
|
|
@@ -42211,7 +42757,8 @@ var signal_strength_default = {
|
|
|
42211
42757
|
_v7FpRate: 0,
|
|
42212
42758
|
_v7Precision: 0.8824,
|
|
42213
42759
|
_v8Verdict: "USEFUL",
|
|
42214
|
-
_v8Lift: 99999
|
|
42760
|
+
_v8Lift: 99999,
|
|
42761
|
+
defaultOff: false
|
|
42215
42762
|
},
|
|
42216
42763
|
"logic/heaps-deviation": {
|
|
42217
42764
|
recall: 0.0126,
|
|
@@ -42247,24 +42794,6 @@ var signal_strength_default = {
|
|
|
42247
42794
|
_v8Verdict: "USEFUL",
|
|
42248
42795
|
_v8Lift: 1760.69
|
|
42249
42796
|
},
|
|
42250
|
-
"logic/ks-distribution-shift": {
|
|
42251
|
-
recall: 0.6889,
|
|
42252
|
-
fpRate: 0.4411,
|
|
42253
|
-
ratio: 1.46,
|
|
42254
|
-
precision: 0.6457,
|
|
42255
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42256
|
-
verdict: "NOISY",
|
|
42257
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=202658, FP=111193, P=64.6%, FPR=44.11%, lift=1.46. v7 was USEFUL (TP=152391, FP=62358, lift=2.09). v8 was INVERTED (TP=50267, FP=48835).",
|
|
42258
|
-
aiSpecific: false,
|
|
42259
|
-
_v7Verdict: "USEFUL",
|
|
42260
|
-
_v7Lift: 2.09,
|
|
42261
|
-
_v7Recall: 0.6431,
|
|
42262
|
-
_v7FpRate: 0.34,
|
|
42263
|
-
_v7Precision: 0.7096,
|
|
42264
|
-
_v8Verdict: "INVERTED",
|
|
42265
|
-
_v8Lift: 0.71,
|
|
42266
|
-
defaultOff: true
|
|
42267
|
-
},
|
|
42268
42797
|
"logic/math-any-density": {
|
|
42269
42798
|
recall: 17e-4,
|
|
42270
42799
|
fpRate: 13e-4,
|
|
@@ -42630,7 +43159,7 @@ var signal_strength_default = {
|
|
|
42630
43159
|
precision: 1,
|
|
42631
43160
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42632
43161
|
verdict: "USEFUL",
|
|
42633
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=1, FP=0, P=100.0%, FPR=0.00%, lift=inf. v7 was USEFUL (TP=1, FP=0, lift=inf). v8 was DORMANT (TP=0, FP=0).",
|
|
43162
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=1, FP=0, P=100.0%, FPR=0.00%, lift=inf. v7 was USEFUL (TP=1, FP=0, lift=inf). v8 was DORMANT (TP=0, FP=0). v0.19 default-on (explicit defaultOff: false): P 100% / inf lift \u2014 security must always be on.",
|
|
42634
43163
|
aiSpecific: true,
|
|
42635
43164
|
_v7Verdict: "USEFUL",
|
|
42636
43165
|
_v7Lift: 99999,
|
|
@@ -42638,7 +43167,8 @@ var signal_strength_default = {
|
|
|
42638
43167
|
_v7FpRate: 0,
|
|
42639
43168
|
_v7Precision: 1,
|
|
42640
43169
|
_v8Verdict: "DORMANT",
|
|
42641
|
-
_v8Lift: 1
|
|
43170
|
+
_v8Lift: 1,
|
|
43171
|
+
defaultOff: false
|
|
42642
43172
|
},
|
|
42643
43173
|
"security/hardcoded-secret": {
|
|
42644
43174
|
recall: 14e-4,
|
|
@@ -42828,6 +43358,96 @@ var signal_strength_default = {
|
|
|
42828
43358
|
_v8Verdict: "USEFUL",
|
|
42829
43359
|
_v8Lift: 651.94
|
|
42830
43360
|
},
|
|
43361
|
+
"ts/enum-vs-as-const": {
|
|
43362
|
+
recall: 0,
|
|
43363
|
+
fpRate: 0,
|
|
43364
|
+
ratio: 1,
|
|
43365
|
+
precision: 0,
|
|
43366
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43367
|
+
verdict: "DORMANT",
|
|
43368
|
+
_calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43369
|
+
aiSpecific: true,
|
|
43370
|
+
_v7Verdict: "DORMANT",
|
|
43371
|
+
_v7Lift: 1,
|
|
43372
|
+
_v7Recall: 0,
|
|
43373
|
+
_v7FpRate: 0,
|
|
43374
|
+
_v7Precision: 0,
|
|
43375
|
+
_v8Verdict: "DORMANT",
|
|
43376
|
+
_v8Lift: 1,
|
|
43377
|
+
defaultOff: true
|
|
43378
|
+
},
|
|
43379
|
+
"ts/excessive-type-assertion": {
|
|
43380
|
+
recall: 0,
|
|
43381
|
+
fpRate: 0,
|
|
43382
|
+
ratio: 1,
|
|
43383
|
+
precision: 0,
|
|
43384
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43385
|
+
verdict: "DORMANT",
|
|
43386
|
+
_calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43387
|
+
aiSpecific: true,
|
|
43388
|
+
_v7Verdict: "DORMANT",
|
|
43389
|
+
_v7Lift: 1,
|
|
43390
|
+
_v7Recall: 0,
|
|
43391
|
+
_v7FpRate: 0,
|
|
43392
|
+
_v7Precision: 0,
|
|
43393
|
+
_v8Verdict: "DORMANT",
|
|
43394
|
+
_v8Lift: 1,
|
|
43395
|
+
defaultOff: true
|
|
43396
|
+
},
|
|
43397
|
+
"ts/import-type-misuse": {
|
|
43398
|
+
recall: 0,
|
|
43399
|
+
fpRate: 0,
|
|
43400
|
+
ratio: 1,
|
|
43401
|
+
precision: 0,
|
|
43402
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43403
|
+
verdict: "DORMANT",
|
|
43404
|
+
_calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43405
|
+
aiSpecific: true,
|
|
43406
|
+
_v7Verdict: "DORMANT",
|
|
43407
|
+
_v7Lift: 1,
|
|
43408
|
+
_v7Recall: 0,
|
|
43409
|
+
_v7FpRate: 0,
|
|
43410
|
+
_v7Precision: 0,
|
|
43411
|
+
_v8Verdict: "DORMANT",
|
|
43412
|
+
_v8Lift: 1,
|
|
43413
|
+
defaultOff: true
|
|
43414
|
+
},
|
|
43415
|
+
"ts/never-vs-unknown": {
|
|
43416
|
+
recall: 0,
|
|
43417
|
+
fpRate: 0,
|
|
43418
|
+
ratio: 1,
|
|
43419
|
+
precision: 0,
|
|
43420
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43421
|
+
verdict: "DORMANT",
|
|
43422
|
+
_calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43423
|
+
aiSpecific: true,
|
|
43424
|
+
_v7Verdict: "DORMANT",
|
|
43425
|
+
_v7Lift: 1,
|
|
43426
|
+
_v7Recall: 0,
|
|
43427
|
+
_v7FpRate: 0,
|
|
43428
|
+
_v7Precision: 0,
|
|
43429
|
+
_v8Verdict: "DORMANT",
|
|
43430
|
+
_v8Lift: 1,
|
|
43431
|
+
defaultOff: true
|
|
43432
|
+
},
|
|
43433
|
+
"ts/optional-chain-overuse": {
|
|
43434
|
+
recall: 0,
|
|
43435
|
+
fpRate: 0,
|
|
43436
|
+
ratio: 1,
|
|
43437
|
+
precision: 0,
|
|
43438
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43439
|
+
verdict: "DORMANT",
|
|
43440
|
+
_calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43441
|
+
aiSpecific: true,
|
|
43442
|
+
_v7Verdict: "DORMANT",
|
|
43443
|
+
_v7Lift: 1,
|
|
43444
|
+
_v7Recall: 0,
|
|
43445
|
+
_v7FpRate: 0,
|
|
43446
|
+
_v7Precision: 0,
|
|
43447
|
+
_v8Verdict: "DORMANT",
|
|
43448
|
+
_v8Lift: 1,
|
|
43449
|
+
defaultOff: true
|
|
43450
|
+
},
|
|
42831
43451
|
"typo/calc-fontsize": {
|
|
42832
43452
|
recall: 0,
|
|
42833
43453
|
fpRate: 0,
|
|
@@ -43130,7 +43750,7 @@ var signal_strength_default = {
|
|
|
43130
43750
|
precision: 0.9774,
|
|
43131
43751
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43132
43752
|
verdict: "USEFUL",
|
|
43133
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=130, FP=3, P=97.7%, FPR=0.00%, lift=82131.33. v7 was USEFUL (TP=96, FP=3, lift=59285.01). v8 was USEFUL (TP=34, FP=0).",
|
|
43753
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=130, FP=3, P=97.7%, FPR=0.00%, lift=82131.33. v7 was USEFUL (TP=96, FP=3, lift=59285.01). v8 was USEFUL (TP=34, FP=0). v0.19 default-on (explicit defaultOff: false): P 97.7% / 82k lift \u2014 UI consistency.",
|
|
43134
43754
|
aiSpecific: false,
|
|
43135
43755
|
_v7Verdict: "USEFUL",
|
|
43136
43756
|
_v7Lift: 59285.01,
|
|
@@ -43138,7 +43758,8 @@ var signal_strength_default = {
|
|
|
43138
43758
|
_v7FpRate: 0,
|
|
43139
43759
|
_v7Precision: 0.9697,
|
|
43140
43760
|
_v8Verdict: "USEFUL",
|
|
43141
|
-
_v8Lift: 99999
|
|
43761
|
+
_v8Lift: 99999,
|
|
43762
|
+
defaultOff: false
|
|
43142
43763
|
},
|
|
43143
43764
|
"visual/spacing-scale-violation": {
|
|
43144
43765
|
recall: 86e-4,
|
|
@@ -43256,7 +43877,7 @@ function loadSignalStrength() {
|
|
|
43256
43877
|
function buildParserCacheConfig(cwd) {
|
|
43257
43878
|
const envVal = process.env.SLOP_AUDIT_CACHE;
|
|
43258
43879
|
const enabled = envVal === "1" || envVal === "true";
|
|
43259
|
-
const root = process.env.SLOP_AUDIT_CACHE_ROOT ??
|
|
43880
|
+
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? join11(cwd, ".slopbrick", "cache", "ast");
|
|
43260
43881
|
return { enabled, root };
|
|
43261
43882
|
}
|
|
43262
43883
|
function applyRuleOverrides(issues, rules) {
|
|
@@ -43274,7 +43895,7 @@ function applyRuleOverrides(issues, rules) {
|
|
|
43274
43895
|
}
|
|
43275
43896
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
43276
43897
|
const cache = buildParserCacheConfig(cwd);
|
|
43277
|
-
const ext =
|
|
43898
|
+
const ext = extname4(filePath).toLowerCase();
|
|
43278
43899
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
43279
43900
|
".swift",
|
|
43280
43901
|
".kt",
|