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.cjs
CHANGED
|
@@ -6,9 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __esm = (fn, res) => function __init() {
|
|
10
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
-
};
|
|
12
9
|
var __export = (target, all) => {
|
|
13
10
|
for (var name in all)
|
|
14
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -31,408 +28,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
28
|
));
|
|
32
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
30
|
|
|
34
|
-
// src/engine/parser-rust.ts
|
|
35
|
-
function getRustParser() {
|
|
36
|
-
if (cachedParser) return { ok: true, parser: cachedParser };
|
|
37
|
-
if (loadError) return { ok: false, error: loadError };
|
|
38
|
-
try {
|
|
39
|
-
rustLanguage = import_tree_sitter_rust.default;
|
|
40
|
-
if (!rustLanguage || typeof rustLanguage !== "object") {
|
|
41
|
-
throw new Error("tree-sitter-rust: language export is missing or malformed");
|
|
42
|
-
}
|
|
43
|
-
const parser = new import_tree_sitter.default();
|
|
44
|
-
parser.setLanguage(rustLanguage);
|
|
45
|
-
cachedParser = parser;
|
|
46
|
-
return { ok: true, parser };
|
|
47
|
-
} catch (err) {
|
|
48
|
-
loadError = err instanceof Error ? err : new Error(String(err));
|
|
49
|
-
return { ok: false, error: loadError };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function effectiveParser() {
|
|
53
|
-
if (forcedFailure) return { ok: false, error: forcedFailure };
|
|
54
|
-
return getRustParser();
|
|
55
|
-
}
|
|
56
|
-
function parseRust(source) {
|
|
57
|
-
const result = effectiveParser();
|
|
58
|
-
if (!result.ok) return null;
|
|
59
|
-
if (!source || source.trim() === "") return null;
|
|
60
|
-
try {
|
|
61
|
-
const rawTree = result.parser.parse(source);
|
|
62
|
-
if (!rawTree) return null;
|
|
63
|
-
const tree = rawTree;
|
|
64
|
-
if (!tree || !tree.rootNode) return null;
|
|
65
|
-
if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
return tree;
|
|
69
|
-
} catch {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function isRustParserAvailable() {
|
|
74
|
-
const result = getRustParser();
|
|
75
|
-
return result.ok;
|
|
76
|
-
}
|
|
77
|
-
var import_tree_sitter, import_tree_sitter_rust, cachedParser, rustLanguage, loadError, forcedFailure;
|
|
78
|
-
var init_parser_rust = __esm({
|
|
79
|
-
"src/engine/parser-rust.ts"() {
|
|
80
|
-
"use strict";
|
|
81
|
-
import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
82
|
-
import_tree_sitter_rust = __toESM(require("tree-sitter-rust"), 1);
|
|
83
|
-
cachedParser = null;
|
|
84
|
-
rustLanguage = null;
|
|
85
|
-
loadError = null;
|
|
86
|
-
forcedFailure = null;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// src/engine/visitors/rust.ts
|
|
91
|
-
function parseRustFile(filePath, source, options = {}) {
|
|
92
|
-
const empty = {
|
|
93
|
-
imports: [],
|
|
94
|
-
functions: [],
|
|
95
|
-
structs: [],
|
|
96
|
-
traits: [],
|
|
97
|
-
impls: []
|
|
98
|
-
};
|
|
99
|
-
if (!isRustParserAvailable() || options.forceFallback) {
|
|
100
|
-
return empty;
|
|
101
|
-
}
|
|
102
|
-
const tree = parseRust(source);
|
|
103
|
-
if (!tree) return empty;
|
|
104
|
-
return walkRustTree(tree);
|
|
105
|
-
}
|
|
106
|
-
function walkRustTree(tree) {
|
|
107
|
-
const ctx = { inTestConfig: false };
|
|
108
|
-
const out = {
|
|
109
|
-
imports: [],
|
|
110
|
-
functions: [],
|
|
111
|
-
structs: [],
|
|
112
|
-
traits: [],
|
|
113
|
-
impls: []
|
|
114
|
-
};
|
|
115
|
-
walkChildren(tree.rootNode, ctx, out);
|
|
116
|
-
return out;
|
|
117
|
-
}
|
|
118
|
-
function walkChildren(node, ctx, out) {
|
|
119
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
120
|
-
const child = node.namedChild(i);
|
|
121
|
-
if (!child) continue;
|
|
122
|
-
visitNode(child, ctx, out);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function visitNode(node, ctx, out) {
|
|
126
|
-
const innerCtx = { ...ctx };
|
|
127
|
-
const attrState = readPrecedingAttributes(node);
|
|
128
|
-
switch (node.type) {
|
|
129
|
-
case "use_declaration": {
|
|
130
|
-
out.imports.push(extractUse(node));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
case "function_item": {
|
|
134
|
-
const isMethod = isInImplBlock(node);
|
|
135
|
-
out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
|
|
136
|
-
walkChildren(getField(node, "body") ?? node, innerCtx, out);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
case "struct_item": {
|
|
140
|
-
out.structs.push(extractStruct(node, attrState));
|
|
141
|
-
const body = getField(node, "body");
|
|
142
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
case "trait_item": {
|
|
146
|
-
out.traits.push(extractTrait(node, attrState));
|
|
147
|
-
const body = getField(node, "body");
|
|
148
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
case "impl_item": {
|
|
152
|
-
const implEntry = extractImpl(node, attrState, innerCtx);
|
|
153
|
-
out.impls.push(implEntry.entry);
|
|
154
|
-
const body = getField(node, "body");
|
|
155
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
case "mod_item": {
|
|
159
|
-
const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
|
|
160
|
-
const modCtx = { ...innerCtx, inTestConfig: modIsTest };
|
|
161
|
-
const body = getField(node, "body");
|
|
162
|
-
if (body) walkChildren(body, modCtx, out);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
default: {
|
|
166
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
167
|
-
const child = node.namedChild(i);
|
|
168
|
-
if (child) visitNode(child, innerCtx, out);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
function readPrecedingAttributes(node) {
|
|
174
|
-
const state = {
|
|
175
|
-
isTestCfg: false,
|
|
176
|
-
isTest: false,
|
|
177
|
-
isPub: false,
|
|
178
|
-
derives: []
|
|
179
|
-
};
|
|
180
|
-
const parent = node.parent;
|
|
181
|
-
if (!parent) return state;
|
|
182
|
-
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
183
|
-
const sibling = parent.namedChild(i);
|
|
184
|
-
if (!sibling || sibling === node) continue;
|
|
185
|
-
if (sibling.endIndex > node.startIndex) continue;
|
|
186
|
-
if (sibling.type !== "attribute_item") continue;
|
|
187
|
-
decodeAttribute(sibling, state);
|
|
188
|
-
}
|
|
189
|
-
return state;
|
|
190
|
-
}
|
|
191
|
-
function decodeAttribute(attr, state) {
|
|
192
|
-
for (let i = 0; i < attr.namedChildCount; i++) {
|
|
193
|
-
const child = attr.namedChild(i);
|
|
194
|
-
if (!child || child.type !== "attribute") continue;
|
|
195
|
-
const name = firstIdentifier(child);
|
|
196
|
-
if (!name) continue;
|
|
197
|
-
if (name === "cfg") {
|
|
198
|
-
const cfgText = child.text;
|
|
199
|
-
if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
|
|
200
|
-
state.isTestCfg = true;
|
|
201
|
-
}
|
|
202
|
-
} else if (name === "test") {
|
|
203
|
-
state.isTest = true;
|
|
204
|
-
} else if (name === "derive") {
|
|
205
|
-
for (let j = 0; j < child.namedChildCount; j++) {
|
|
206
|
-
const inner = child.namedChild(j);
|
|
207
|
-
if (inner) {
|
|
208
|
-
const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
|
|
209
|
-
for (const m of matches) state.derives.push(m[1]);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
function firstIdentifier(node) {
|
|
216
|
-
const text = node.text;
|
|
217
|
-
const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
218
|
-
return idMatch ? idMatch[1] : null;
|
|
219
|
-
}
|
|
220
|
-
function extractUse(node) {
|
|
221
|
-
const text = node.text;
|
|
222
|
-
const argument = node.namedChild(0);
|
|
223
|
-
const argField = node.childForFieldName("argument");
|
|
224
|
-
const path = argField?.text ?? argument?.text ?? "";
|
|
225
|
-
const names = [];
|
|
226
|
-
let isGlob = false;
|
|
227
|
-
const useListNode = findUseListNode(argument);
|
|
228
|
-
if (useListNode) {
|
|
229
|
-
for (let i = 0; i < useListNode.namedChildCount; i++) {
|
|
230
|
-
const item = useListNode.namedChild(i);
|
|
231
|
-
if (item.type === "use_wildcard") {
|
|
232
|
-
isGlob = true;
|
|
233
|
-
} else if (item.type === "identifier") {
|
|
234
|
-
names.push({ name: item.text });
|
|
235
|
-
} else if (item.type === "use_as_clause") {
|
|
236
|
-
const alias = item.childForFieldName("alias");
|
|
237
|
-
const binding = item.childForFieldName("path");
|
|
238
|
-
if (binding) {
|
|
239
|
-
names.push({ name: binding.text, alias: alias?.text });
|
|
240
|
-
} else {
|
|
241
|
-
names.push({ name: item.text });
|
|
242
|
-
}
|
|
243
|
-
} else if (item.type === "scoped_identifier") {
|
|
244
|
-
const idents = collectIdentifiers(item);
|
|
245
|
-
names.push({ name: idents[idents.length - 1] ?? item.text });
|
|
246
|
-
} else if (item.text.includes(" as ")) {
|
|
247
|
-
const [head, alias] = item.text.split(/\s+as\s+/);
|
|
248
|
-
names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
|
|
249
|
-
} else {
|
|
250
|
-
names.push({ name: item.text });
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
} else if (argument?.type === "use_wildcard") {
|
|
254
|
-
isGlob = true;
|
|
255
|
-
} else if (argument) {
|
|
256
|
-
const idents = collectIdentifiers(argument);
|
|
257
|
-
const last = idents[idents.length - 1] ?? argument.text;
|
|
258
|
-
names.push({ name: last });
|
|
259
|
-
const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
|
|
260
|
-
if (asMatch) names[0].alias = asMatch[1];
|
|
261
|
-
}
|
|
262
|
-
return {
|
|
263
|
-
path: path.trim(),
|
|
264
|
-
names,
|
|
265
|
-
isGlob,
|
|
266
|
-
line: node.startPosition.row + 1,
|
|
267
|
-
column: node.startPosition.column
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
|
|
271
|
-
const nameNode = node.childForFieldName("name");
|
|
272
|
-
const name = nameNode?.text ?? "<anon>";
|
|
273
|
-
const params = node.childForFieldName("parameters");
|
|
274
|
-
const body = node.childForFieldName("body");
|
|
275
|
-
const visibilityMod = node.namedChild(0);
|
|
276
|
-
const isPublic = visibilityMod?.type === "visibility_modifier";
|
|
277
|
-
let receiver;
|
|
278
|
-
if (isMethod && params) {
|
|
279
|
-
const selfParam = findSelfParameter(params);
|
|
280
|
-
if (selfParam) receiver = selfParam.text;
|
|
281
|
-
}
|
|
282
|
-
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
283
|
-
const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
|
|
284
|
-
return {
|
|
285
|
-
name,
|
|
286
|
-
line: node.startPosition.row + 1,
|
|
287
|
-
column: node.startPosition.column,
|
|
288
|
-
isPublic,
|
|
289
|
-
isMethod,
|
|
290
|
-
receiver,
|
|
291
|
-
bodyLines,
|
|
292
|
-
inTestConfig
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
function extractStruct(node, attrs) {
|
|
296
|
-
const nameNode = node.childForFieldName("name");
|
|
297
|
-
return {
|
|
298
|
-
name: nameNode?.text ?? "<anon>",
|
|
299
|
-
line: node.startPosition.row + 1,
|
|
300
|
-
column: node.startPosition.column,
|
|
301
|
-
isPublic: hasVisibility(node),
|
|
302
|
-
isDerive: attrs.derives.length > 0,
|
|
303
|
-
derives: [...attrs.derives]
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
function extractTrait(node, _attrs) {
|
|
307
|
-
const nameNode = node.childForFieldName("name");
|
|
308
|
-
return {
|
|
309
|
-
name: nameNode?.text ?? "<anon>",
|
|
310
|
-
line: node.startPosition.row + 1,
|
|
311
|
-
column: node.startPosition.column,
|
|
312
|
-
isPublic: hasVisibility(node)
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
function extractImpl(node, _attrs, _ctx) {
|
|
316
|
-
const traitNode = node.childForFieldName("trait");
|
|
317
|
-
const typeNode = node.childForFieldName("type");
|
|
318
|
-
const typeText = typeNode?.text ?? "<anon>";
|
|
319
|
-
const traitText = traitNode?.text;
|
|
320
|
-
const body = node.childForFieldName("body");
|
|
321
|
-
const methods = [];
|
|
322
|
-
if (body) {
|
|
323
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
324
|
-
const child = body.namedChild(i);
|
|
325
|
-
if (child?.type === "function_item") {
|
|
326
|
-
const m = child.childForFieldName("name");
|
|
327
|
-
if (m) methods.push(m.text);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
entry: {
|
|
333
|
-
type: typeText,
|
|
334
|
-
trait: traitText ?? void 0,
|
|
335
|
-
methods,
|
|
336
|
-
line: node.startPosition.row + 1,
|
|
337
|
-
column: node.startPosition.column
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
function findUseListNode(argument) {
|
|
342
|
-
if (!argument) return null;
|
|
343
|
-
if (argument.type === "use_list") return argument;
|
|
344
|
-
if (argument.type === "scoped_use_list") {
|
|
345
|
-
for (let i = 0; i < argument.namedChildCount; i++) {
|
|
346
|
-
const c = argument.namedChild(i);
|
|
347
|
-
if (c?.type === "use_list") return c;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
function getField(node, fieldName) {
|
|
353
|
-
return node.childForFieldName(fieldName);
|
|
354
|
-
}
|
|
355
|
-
function collectIdentifiers(node) {
|
|
356
|
-
const out = [];
|
|
357
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
358
|
-
const child = node.namedChild(i);
|
|
359
|
-
if (!child) continue;
|
|
360
|
-
if (child.type === "identifier" || child.type === "type_identifier") {
|
|
361
|
-
out.push(child.text);
|
|
362
|
-
} else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
|
|
363
|
-
out.push(...collectIdentifiers(child));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return out;
|
|
367
|
-
}
|
|
368
|
-
function hasVisibility(node) {
|
|
369
|
-
return node.namedChild(0)?.type === "visibility_modifier";
|
|
370
|
-
}
|
|
371
|
-
function isInImplBlock(node) {
|
|
372
|
-
for (let p = node.parent; p; p = p.parent) {
|
|
373
|
-
if (p.type === "impl_item") return true;
|
|
374
|
-
if (p.type === "function_item" || p.type === "source_file") return false;
|
|
375
|
-
}
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
function findSelfParameter(params) {
|
|
379
|
-
for (let i = 0; i < params.namedChildCount; i++) {
|
|
380
|
-
const child = params.namedChild(i);
|
|
381
|
-
if (child?.type === "self_parameter") return child;
|
|
382
|
-
}
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
function isFunctionAttrTest(node) {
|
|
386
|
-
void node;
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
var SERVICE_SUFFIXES, SERVICE_SUFFIX_GROUP, RUST_SERVICE_STRUCT_RE, RUST_SERVICE_IMPL_RE;
|
|
390
|
-
var init_rust = __esm({
|
|
391
|
-
"src/engine/visitors/rust.ts"() {
|
|
392
|
-
"use strict";
|
|
393
|
-
init_parser_rust();
|
|
394
|
-
SERVICE_SUFFIXES = [
|
|
395
|
-
"Service",
|
|
396
|
-
"Manager",
|
|
397
|
-
"Handler",
|
|
398
|
-
"Repository",
|
|
399
|
-
"Controller",
|
|
400
|
-
"Helper",
|
|
401
|
-
"Factory",
|
|
402
|
-
"Provider",
|
|
403
|
-
"Store",
|
|
404
|
-
"API",
|
|
405
|
-
"Client",
|
|
406
|
-
"Adapter",
|
|
407
|
-
"Resolver",
|
|
408
|
-
"Mapper",
|
|
409
|
-
"Transformer",
|
|
410
|
-
"Serializer",
|
|
411
|
-
"Validator",
|
|
412
|
-
"Strategy",
|
|
413
|
-
"Facade",
|
|
414
|
-
"Decorator",
|
|
415
|
-
"Observer",
|
|
416
|
-
"Builder",
|
|
417
|
-
"Command",
|
|
418
|
-
"Processor",
|
|
419
|
-
"Worker",
|
|
420
|
-
"Job",
|
|
421
|
-
"Actor",
|
|
422
|
-
"Executor"
|
|
423
|
-
];
|
|
424
|
-
SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
|
|
425
|
-
RUST_SERVICE_STRUCT_RE = new RegExp(
|
|
426
|
-
`^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
427
|
-
"gm"
|
|
428
|
-
);
|
|
429
|
-
RUST_SERVICE_IMPL_RE = new RegExp(
|
|
430
|
-
`^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
431
|
-
"gm"
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
31
|
// src/engine/worker.ts
|
|
437
32
|
var worker_exports = {};
|
|
438
33
|
__export(worker_exports, {
|
|
@@ -440,8 +35,8 @@ __export(worker_exports, {
|
|
|
440
35
|
});
|
|
441
36
|
module.exports = __toCommonJS(worker_exports);
|
|
442
37
|
var import_node_worker_threads = require("worker_threads");
|
|
443
|
-
var
|
|
444
|
-
var
|
|
38
|
+
var import_node_path9 = require("path");
|
|
39
|
+
var import_node_path10 = require("path");
|
|
445
40
|
|
|
446
41
|
// ../engine/dist/index.js
|
|
447
42
|
var import_promises = require("fs/promises");
|
|
@@ -5463,76 +5058,6 @@ function tokenizeIdentifiers(source) {
|
|
|
5463
5058
|
return tokens;
|
|
5464
5059
|
}
|
|
5465
5060
|
var SQRT_2_TIMES_LN_2 = Math.sqrt(2 * Math.LN2);
|
|
5466
|
-
function ecdfAt(sortedSamples, x) {
|
|
5467
|
-
let lo = 0;
|
|
5468
|
-
let hi = sortedSamples.length;
|
|
5469
|
-
while (lo < hi) {
|
|
5470
|
-
const mid = lo + hi >>> 1;
|
|
5471
|
-
if (sortedSamples[mid] <= x) lo = mid + 1;
|
|
5472
|
-
else hi = mid;
|
|
5473
|
-
}
|
|
5474
|
-
return lo / sortedSamples.length;
|
|
5475
|
-
}
|
|
5476
|
-
function ksStatistic(sampleA, sampleB) {
|
|
5477
|
-
if (sampleA.length === 0 || sampleB.length === 0) return 1;
|
|
5478
|
-
const sortedA = [...sampleA].sort((a, b) => a - b);
|
|
5479
|
-
const sortedB = [...sampleB].sort((a, b) => a - b);
|
|
5480
|
-
const allPoints = [...sortedA, ...sortedB].sort((a, b) => a - b);
|
|
5481
|
-
let maxDiff = 0;
|
|
5482
|
-
for (const x of allPoints) {
|
|
5483
|
-
const fa = ecdfAt(sortedA, x);
|
|
5484
|
-
const fb = ecdfAt(sortedB, x);
|
|
5485
|
-
const diff = Math.abs(fa - fb);
|
|
5486
|
-
if (diff > maxDiff) maxDiff = diff;
|
|
5487
|
-
}
|
|
5488
|
-
return maxDiff;
|
|
5489
|
-
}
|
|
5490
|
-
function ksPValue(statistic, n, m) {
|
|
5491
|
-
if (n === 0 || m === 0) return 1;
|
|
5492
|
-
if (statistic < 0) return 1;
|
|
5493
|
-
if (statistic > 1) return 0;
|
|
5494
|
-
const lambda = Math.sqrt(n * m / (n + m)) * statistic;
|
|
5495
|
-
if (lambda > 3.6) return 0;
|
|
5496
|
-
let p = 0;
|
|
5497
|
-
for (let j = 1; j < 1e3; j++) {
|
|
5498
|
-
const term = 2 * Math.pow(-1, j - 1) * Math.exp(-2 * j * j * lambda * lambda);
|
|
5499
|
-
p += term;
|
|
5500
|
-
if (Math.abs(term) < 1e-15) break;
|
|
5501
|
-
}
|
|
5502
|
-
return Math.max(0, Math.min(1, p));
|
|
5503
|
-
}
|
|
5504
|
-
function ksTest(sampleA, sampleB, alpha = 0.05) {
|
|
5505
|
-
const statistic = ksStatistic(sampleA, sampleB);
|
|
5506
|
-
const pValue = ksPValue(statistic, sampleA.length, sampleB.length);
|
|
5507
|
-
return {
|
|
5508
|
-
statistic,
|
|
5509
|
-
pValue,
|
|
5510
|
-
significant: pValue < alpha,
|
|
5511
|
-
n: sampleA.length,
|
|
5512
|
-
m: sampleB.length
|
|
5513
|
-
};
|
|
5514
|
-
}
|
|
5515
|
-
function multiFeatureKsTest(features, baselines, alpha = 0.05) {
|
|
5516
|
-
const featureNames = [...features.keys()];
|
|
5517
|
-
const k = featureNames.length;
|
|
5518
|
-
const bonferroniAlpha = k > 0 ? alpha / k : alpha;
|
|
5519
|
-
const perFeature = /* @__PURE__ */ new Map();
|
|
5520
|
-
const significantFeatures = [];
|
|
5521
|
-
for (const name of featureNames) {
|
|
5522
|
-
const sample = features.get(name);
|
|
5523
|
-
const baseline = baselines.get(name);
|
|
5524
|
-
if (!sample || !baseline) continue;
|
|
5525
|
-
const result = ksTest(sample, baseline, bonferroniAlpha);
|
|
5526
|
-
perFeature.set(name, result);
|
|
5527
|
-
if (result.significant) significantFeatures.push(name);
|
|
5528
|
-
}
|
|
5529
|
-
return {
|
|
5530
|
-
perFeature,
|
|
5531
|
-
bonferroniAlpha,
|
|
5532
|
-
anySignificant: significantFeatures.length > 0,
|
|
5533
|
-
significantFeatures
|
|
5534
|
-
};
|
|
5535
|
-
}
|
|
5536
5061
|
|
|
5537
5062
|
// src/engine/visitors/react.ts
|
|
5538
5063
|
function isObject(node) {
|
|
@@ -7332,8 +6857,396 @@ function dispatchNode(node, parent, path, vctx) {
|
|
|
7332
6857
|
return false;
|
|
7333
6858
|
}
|
|
7334
6859
|
|
|
6860
|
+
// src/engine/parser-rust.ts
|
|
6861
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
6862
|
+
var import_tree_sitter_rust = __toESM(require("tree-sitter-rust"), 1);
|
|
6863
|
+
var cachedParser = null;
|
|
6864
|
+
var rustLanguage = null;
|
|
6865
|
+
var loadError = null;
|
|
6866
|
+
function getRustParser() {
|
|
6867
|
+
if (cachedParser) return { ok: true, parser: cachedParser };
|
|
6868
|
+
if (loadError) return { ok: false, error: loadError };
|
|
6869
|
+
try {
|
|
6870
|
+
rustLanguage = import_tree_sitter_rust.default;
|
|
6871
|
+
if (!rustLanguage || typeof rustLanguage !== "object") {
|
|
6872
|
+
throw new Error("tree-sitter-rust: language export is missing or malformed");
|
|
6873
|
+
}
|
|
6874
|
+
const parser = new import_tree_sitter.default();
|
|
6875
|
+
parser.setLanguage(rustLanguage);
|
|
6876
|
+
cachedParser = parser;
|
|
6877
|
+
return { ok: true, parser };
|
|
6878
|
+
} catch (err) {
|
|
6879
|
+
loadError = err instanceof Error ? err : new Error(String(err));
|
|
6880
|
+
return { ok: false, error: loadError };
|
|
6881
|
+
}
|
|
6882
|
+
}
|
|
6883
|
+
var forcedFailure = null;
|
|
6884
|
+
function effectiveParser() {
|
|
6885
|
+
if (forcedFailure) return { ok: false, error: forcedFailure };
|
|
6886
|
+
return getRustParser();
|
|
6887
|
+
}
|
|
6888
|
+
function parseRust(source) {
|
|
6889
|
+
const result = effectiveParser();
|
|
6890
|
+
if (!result.ok) return null;
|
|
6891
|
+
if (!source || source.trim() === "") return null;
|
|
6892
|
+
try {
|
|
6893
|
+
const rawTree = result.parser.parse(source);
|
|
6894
|
+
if (!rawTree) return null;
|
|
6895
|
+
const tree = rawTree;
|
|
6896
|
+
if (!tree || !tree.rootNode) return null;
|
|
6897
|
+
if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
|
|
6898
|
+
return null;
|
|
6899
|
+
}
|
|
6900
|
+
return tree;
|
|
6901
|
+
} catch {
|
|
6902
|
+
return null;
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
function isRustParserAvailable() {
|
|
6906
|
+
const result = getRustParser();
|
|
6907
|
+
return result.ok;
|
|
6908
|
+
}
|
|
6909
|
+
|
|
6910
|
+
// src/engine/visitors/rust.ts
|
|
6911
|
+
var SERVICE_SUFFIXES = [
|
|
6912
|
+
"Service",
|
|
6913
|
+
"Manager",
|
|
6914
|
+
"Handler",
|
|
6915
|
+
"Repository",
|
|
6916
|
+
"Controller",
|
|
6917
|
+
"Helper",
|
|
6918
|
+
"Factory",
|
|
6919
|
+
"Provider",
|
|
6920
|
+
"Store",
|
|
6921
|
+
"API",
|
|
6922
|
+
"Client",
|
|
6923
|
+
"Adapter",
|
|
6924
|
+
"Resolver",
|
|
6925
|
+
"Mapper",
|
|
6926
|
+
"Transformer",
|
|
6927
|
+
"Serializer",
|
|
6928
|
+
"Validator",
|
|
6929
|
+
"Strategy",
|
|
6930
|
+
"Facade",
|
|
6931
|
+
"Decorator",
|
|
6932
|
+
"Observer",
|
|
6933
|
+
"Builder",
|
|
6934
|
+
"Command",
|
|
6935
|
+
"Processor",
|
|
6936
|
+
"Worker",
|
|
6937
|
+
"Job",
|
|
6938
|
+
"Actor",
|
|
6939
|
+
"Executor"
|
|
6940
|
+
];
|
|
6941
|
+
var SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
|
|
6942
|
+
var RUST_SERVICE_STRUCT_RE = new RegExp(
|
|
6943
|
+
`^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
6944
|
+
"gm"
|
|
6945
|
+
);
|
|
6946
|
+
var RUST_SERVICE_IMPL_RE = new RegExp(
|
|
6947
|
+
`^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
6948
|
+
"gm"
|
|
6949
|
+
);
|
|
6950
|
+
function parseRustFile(filePath, source, options = {}) {
|
|
6951
|
+
const empty = {
|
|
6952
|
+
imports: [],
|
|
6953
|
+
functions: [],
|
|
6954
|
+
structs: [],
|
|
6955
|
+
traits: [],
|
|
6956
|
+
impls: []
|
|
6957
|
+
};
|
|
6958
|
+
if (!isRustParserAvailable() || options.forceFallback) {
|
|
6959
|
+
return empty;
|
|
6960
|
+
}
|
|
6961
|
+
const tree = parseRust(source);
|
|
6962
|
+
if (!tree) return empty;
|
|
6963
|
+
return walkRustTree(tree);
|
|
6964
|
+
}
|
|
6965
|
+
function walkRustTree(tree) {
|
|
6966
|
+
const ctx = { inTestConfig: false };
|
|
6967
|
+
const out = {
|
|
6968
|
+
imports: [],
|
|
6969
|
+
functions: [],
|
|
6970
|
+
structs: [],
|
|
6971
|
+
traits: [],
|
|
6972
|
+
impls: []
|
|
6973
|
+
};
|
|
6974
|
+
walkChildren(tree.rootNode, ctx, out);
|
|
6975
|
+
return out;
|
|
6976
|
+
}
|
|
6977
|
+
function walkChildren(node, ctx, out) {
|
|
6978
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6979
|
+
const child = node.namedChild(i);
|
|
6980
|
+
if (!child) continue;
|
|
6981
|
+
visitNode(child, ctx, out);
|
|
6982
|
+
}
|
|
6983
|
+
}
|
|
6984
|
+
function visitNode(node, ctx, out) {
|
|
6985
|
+
const innerCtx = { ...ctx };
|
|
6986
|
+
const attrState = readPrecedingAttributes(node);
|
|
6987
|
+
switch (node.type) {
|
|
6988
|
+
case "use_declaration": {
|
|
6989
|
+
out.imports.push(extractUse(node));
|
|
6990
|
+
return;
|
|
6991
|
+
}
|
|
6992
|
+
case "function_item": {
|
|
6993
|
+
const isMethod = isInImplBlock(node);
|
|
6994
|
+
out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
|
|
6995
|
+
walkChildren(getField(node, "body") ?? node, innerCtx, out);
|
|
6996
|
+
return;
|
|
6997
|
+
}
|
|
6998
|
+
case "struct_item": {
|
|
6999
|
+
out.structs.push(extractStruct(node, attrState));
|
|
7000
|
+
const body = getField(node, "body");
|
|
7001
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
7002
|
+
return;
|
|
7003
|
+
}
|
|
7004
|
+
case "trait_item": {
|
|
7005
|
+
out.traits.push(extractTrait(node, attrState));
|
|
7006
|
+
const body = getField(node, "body");
|
|
7007
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
7010
|
+
case "impl_item": {
|
|
7011
|
+
const implEntry = extractImpl(node, attrState, innerCtx);
|
|
7012
|
+
out.impls.push(implEntry.entry);
|
|
7013
|
+
const body = getField(node, "body");
|
|
7014
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
case "mod_item": {
|
|
7018
|
+
const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
|
|
7019
|
+
const modCtx = { ...innerCtx, inTestConfig: modIsTest };
|
|
7020
|
+
const body = getField(node, "body");
|
|
7021
|
+
if (body) walkChildren(body, modCtx, out);
|
|
7022
|
+
return;
|
|
7023
|
+
}
|
|
7024
|
+
default: {
|
|
7025
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7026
|
+
const child = node.namedChild(i);
|
|
7027
|
+
if (child) visitNode(child, innerCtx, out);
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
function readPrecedingAttributes(node) {
|
|
7033
|
+
const state = {
|
|
7034
|
+
isTestCfg: false,
|
|
7035
|
+
isTest: false,
|
|
7036
|
+
isPub: false,
|
|
7037
|
+
derives: []
|
|
7038
|
+
};
|
|
7039
|
+
const parent = node.parent;
|
|
7040
|
+
if (!parent) return state;
|
|
7041
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
7042
|
+
const sibling = parent.namedChild(i);
|
|
7043
|
+
if (!sibling || sibling === node) continue;
|
|
7044
|
+
if (sibling.endIndex > node.startIndex) continue;
|
|
7045
|
+
if (sibling.type !== "attribute_item") continue;
|
|
7046
|
+
decodeAttribute(sibling, state);
|
|
7047
|
+
}
|
|
7048
|
+
return state;
|
|
7049
|
+
}
|
|
7050
|
+
function decodeAttribute(attr, state) {
|
|
7051
|
+
for (let i = 0; i < attr.namedChildCount; i++) {
|
|
7052
|
+
const child = attr.namedChild(i);
|
|
7053
|
+
if (!child || child.type !== "attribute") continue;
|
|
7054
|
+
const name = firstIdentifier(child);
|
|
7055
|
+
if (!name) continue;
|
|
7056
|
+
if (name === "cfg") {
|
|
7057
|
+
const cfgText = child.text;
|
|
7058
|
+
if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
|
|
7059
|
+
state.isTestCfg = true;
|
|
7060
|
+
}
|
|
7061
|
+
} else if (name === "test") {
|
|
7062
|
+
state.isTest = true;
|
|
7063
|
+
} else if (name === "derive") {
|
|
7064
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
7065
|
+
const inner = child.namedChild(j);
|
|
7066
|
+
if (inner) {
|
|
7067
|
+
const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
|
|
7068
|
+
for (const m of matches) state.derives.push(m[1]);
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
function firstIdentifier(node) {
|
|
7075
|
+
const text = node.text;
|
|
7076
|
+
const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
7077
|
+
return idMatch ? idMatch[1] : null;
|
|
7078
|
+
}
|
|
7079
|
+
function extractUse(node) {
|
|
7080
|
+
const text = node.text;
|
|
7081
|
+
const argument = node.namedChild(0);
|
|
7082
|
+
const argField = node.childForFieldName("argument");
|
|
7083
|
+
const path = argField?.text ?? argument?.text ?? "";
|
|
7084
|
+
const names = [];
|
|
7085
|
+
let isGlob = false;
|
|
7086
|
+
const useListNode = findUseListNode(argument);
|
|
7087
|
+
if (useListNode) {
|
|
7088
|
+
for (let i = 0; i < useListNode.namedChildCount; i++) {
|
|
7089
|
+
const item = useListNode.namedChild(i);
|
|
7090
|
+
if (item.type === "use_wildcard") {
|
|
7091
|
+
isGlob = true;
|
|
7092
|
+
} else if (item.type === "identifier") {
|
|
7093
|
+
names.push({ name: item.text });
|
|
7094
|
+
} else if (item.type === "use_as_clause") {
|
|
7095
|
+
const alias = item.childForFieldName("alias");
|
|
7096
|
+
const binding = item.childForFieldName("path");
|
|
7097
|
+
if (binding) {
|
|
7098
|
+
names.push({ name: binding.text, alias: alias?.text });
|
|
7099
|
+
} else {
|
|
7100
|
+
names.push({ name: item.text });
|
|
7101
|
+
}
|
|
7102
|
+
} else if (item.type === "scoped_identifier") {
|
|
7103
|
+
const idents = collectIdentifiers(item);
|
|
7104
|
+
names.push({ name: idents[idents.length - 1] ?? item.text });
|
|
7105
|
+
} else if (item.text.includes(" as ")) {
|
|
7106
|
+
const [head, alias] = item.text.split(/\s+as\s+/);
|
|
7107
|
+
names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
|
|
7108
|
+
} else {
|
|
7109
|
+
names.push({ name: item.text });
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
7112
|
+
} else if (argument?.type === "use_wildcard") {
|
|
7113
|
+
isGlob = true;
|
|
7114
|
+
} else if (argument) {
|
|
7115
|
+
const idents = collectIdentifiers(argument);
|
|
7116
|
+
const last = idents[idents.length - 1] ?? argument.text;
|
|
7117
|
+
names.push({ name: last });
|
|
7118
|
+
const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
|
|
7119
|
+
if (asMatch) names[0].alias = asMatch[1];
|
|
7120
|
+
}
|
|
7121
|
+
return {
|
|
7122
|
+
path: path.trim(),
|
|
7123
|
+
names,
|
|
7124
|
+
isGlob,
|
|
7125
|
+
line: node.startPosition.row + 1,
|
|
7126
|
+
column: node.startPosition.column
|
|
7127
|
+
};
|
|
7128
|
+
}
|
|
7129
|
+
function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
|
|
7130
|
+
const nameNode = node.childForFieldName("name");
|
|
7131
|
+
const name = nameNode?.text ?? "<anon>";
|
|
7132
|
+
const params = node.childForFieldName("parameters");
|
|
7133
|
+
const body = node.childForFieldName("body");
|
|
7134
|
+
const visibilityMod = node.namedChild(0);
|
|
7135
|
+
const isPublic = visibilityMod?.type === "visibility_modifier";
|
|
7136
|
+
let receiver;
|
|
7137
|
+
if (isMethod && params) {
|
|
7138
|
+
const selfParam = findSelfParameter(params);
|
|
7139
|
+
if (selfParam) receiver = selfParam.text;
|
|
7140
|
+
}
|
|
7141
|
+
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
7142
|
+
const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
|
|
7143
|
+
return {
|
|
7144
|
+
name,
|
|
7145
|
+
line: node.startPosition.row + 1,
|
|
7146
|
+
column: node.startPosition.column,
|
|
7147
|
+
isPublic,
|
|
7148
|
+
isMethod,
|
|
7149
|
+
receiver,
|
|
7150
|
+
bodyLines,
|
|
7151
|
+
inTestConfig
|
|
7152
|
+
};
|
|
7153
|
+
}
|
|
7154
|
+
function extractStruct(node, attrs) {
|
|
7155
|
+
const nameNode = node.childForFieldName("name");
|
|
7156
|
+
return {
|
|
7157
|
+
name: nameNode?.text ?? "<anon>",
|
|
7158
|
+
line: node.startPosition.row + 1,
|
|
7159
|
+
column: node.startPosition.column,
|
|
7160
|
+
isPublic: hasVisibility(node),
|
|
7161
|
+
isDerive: attrs.derives.length > 0,
|
|
7162
|
+
derives: [...attrs.derives]
|
|
7163
|
+
};
|
|
7164
|
+
}
|
|
7165
|
+
function extractTrait(node, _attrs) {
|
|
7166
|
+
const nameNode = node.childForFieldName("name");
|
|
7167
|
+
return {
|
|
7168
|
+
name: nameNode?.text ?? "<anon>",
|
|
7169
|
+
line: node.startPosition.row + 1,
|
|
7170
|
+
column: node.startPosition.column,
|
|
7171
|
+
isPublic: hasVisibility(node)
|
|
7172
|
+
};
|
|
7173
|
+
}
|
|
7174
|
+
function extractImpl(node, _attrs, _ctx) {
|
|
7175
|
+
const traitNode = node.childForFieldName("trait");
|
|
7176
|
+
const typeNode = node.childForFieldName("type");
|
|
7177
|
+
const typeText = typeNode?.text ?? "<anon>";
|
|
7178
|
+
const traitText = traitNode?.text;
|
|
7179
|
+
const body = node.childForFieldName("body");
|
|
7180
|
+
const methods = [];
|
|
7181
|
+
if (body) {
|
|
7182
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
7183
|
+
const child = body.namedChild(i);
|
|
7184
|
+
if (child?.type === "function_item") {
|
|
7185
|
+
const m = child.childForFieldName("name");
|
|
7186
|
+
if (m) methods.push(m.text);
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
return {
|
|
7191
|
+
entry: {
|
|
7192
|
+
type: typeText,
|
|
7193
|
+
trait: traitText ?? void 0,
|
|
7194
|
+
methods,
|
|
7195
|
+
line: node.startPosition.row + 1,
|
|
7196
|
+
column: node.startPosition.column
|
|
7197
|
+
}
|
|
7198
|
+
};
|
|
7199
|
+
}
|
|
7200
|
+
function findUseListNode(argument) {
|
|
7201
|
+
if (!argument) return null;
|
|
7202
|
+
if (argument.type === "use_list") return argument;
|
|
7203
|
+
if (argument.type === "scoped_use_list") {
|
|
7204
|
+
for (let i = 0; i < argument.namedChildCount; i++) {
|
|
7205
|
+
const c = argument.namedChild(i);
|
|
7206
|
+
if (c?.type === "use_list") return c;
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
7209
|
+
return null;
|
|
7210
|
+
}
|
|
7211
|
+
function getField(node, fieldName) {
|
|
7212
|
+
return node.childForFieldName(fieldName);
|
|
7213
|
+
}
|
|
7214
|
+
function collectIdentifiers(node) {
|
|
7215
|
+
const out = [];
|
|
7216
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7217
|
+
const child = node.namedChild(i);
|
|
7218
|
+
if (!child) continue;
|
|
7219
|
+
if (child.type === "identifier" || child.type === "type_identifier") {
|
|
7220
|
+
out.push(child.text);
|
|
7221
|
+
} else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
|
|
7222
|
+
out.push(...collectIdentifiers(child));
|
|
7223
|
+
}
|
|
7224
|
+
}
|
|
7225
|
+
return out;
|
|
7226
|
+
}
|
|
7227
|
+
function hasVisibility(node) {
|
|
7228
|
+
return node.namedChild(0)?.type === "visibility_modifier";
|
|
7229
|
+
}
|
|
7230
|
+
function isInImplBlock(node) {
|
|
7231
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
7232
|
+
if (p.type === "impl_item") return true;
|
|
7233
|
+
if (p.type === "function_item" || p.type === "source_file") return false;
|
|
7234
|
+
}
|
|
7235
|
+
return false;
|
|
7236
|
+
}
|
|
7237
|
+
function findSelfParameter(params) {
|
|
7238
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
7239
|
+
const child = params.namedChild(i);
|
|
7240
|
+
if (child?.type === "self_parameter") return child;
|
|
7241
|
+
}
|
|
7242
|
+
return null;
|
|
7243
|
+
}
|
|
7244
|
+
function isFunctionAttrTest(node) {
|
|
7245
|
+
void node;
|
|
7246
|
+
return false;
|
|
7247
|
+
}
|
|
7248
|
+
|
|
7335
7249
|
// src/engine/visitors/v2-build.ts
|
|
7336
|
-
init_rust();
|
|
7337
7250
|
var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
|
|
7338
7251
|
var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
|
|
7339
7252
|
var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
|
|
@@ -35177,125 +35090,17 @@ var unusedParameterRule = createRule({
|
|
|
35177
35090
|
});
|
|
35178
35091
|
|
|
35179
35092
|
// src/rules/docs/broken-link.ts
|
|
35180
|
-
var import_node_fs6 = require("fs");
|
|
35181
|
-
var import_node_path6 = require("path");
|
|
35182
|
-
|
|
35183
|
-
// src/engine/doc-freshness.ts
|
|
35184
|
-
var import_node_fs5 = require("fs");
|
|
35185
|
-
var import_node_path5 = require("path");
|
|
35186
|
-
var import_globby2 = require("globby");
|
|
35187
|
-
|
|
35188
|
-
// src/mcp/patterns.ts
|
|
35189
35093
|
var import_node_fs3 = require("fs");
|
|
35190
35094
|
var import_node_path3 = require("path");
|
|
35191
35095
|
|
|
35192
|
-
// src/engine/
|
|
35193
|
-
var import_globby = require("globby");
|
|
35194
|
-
var import_minimatch = require("minimatch");
|
|
35195
|
-
var import_node_path = require("path");
|
|
35196
|
-
var import_node_fs = require("fs");
|
|
35197
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
|
|
35198
|
-
var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35199
|
-
".py",
|
|
35200
|
-
".go",
|
|
35201
|
-
// v0.14.0
|
|
35202
|
-
".swift",
|
|
35203
|
-
".kt",
|
|
35204
|
-
".kts",
|
|
35205
|
-
".dart",
|
|
35206
|
-
".rs",
|
|
35207
|
-
".cpp",
|
|
35208
|
-
".cc",
|
|
35209
|
-
".cxx",
|
|
35210
|
-
".c",
|
|
35211
|
-
".h",
|
|
35212
|
-
".hpp",
|
|
35213
|
-
".hxx",
|
|
35214
|
-
".java",
|
|
35215
|
-
".rb",
|
|
35216
|
-
".php"
|
|
35217
|
-
]);
|
|
35218
|
-
var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35219
|
-
...SOURCE_EXTENSIONS,
|
|
35220
|
-
...BACKEND_EXTENSIONS
|
|
35221
|
-
]);
|
|
35222
|
-
|
|
35223
|
-
// src/config/conventions.ts
|
|
35096
|
+
// src/engine/doc-freshness.ts
|
|
35224
35097
|
var import_node_fs2 = require("fs");
|
|
35225
35098
|
var import_node_path2 = require("path");
|
|
35226
|
-
|
|
35227
|
-
// src/mcp/patterns.ts
|
|
35228
|
-
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;
|
|
35229
|
-
var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35230
|
-
var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35231
|
-
function extractImports(source) {
|
|
35232
|
-
const seen = /* @__PURE__ */ new Set();
|
|
35233
|
-
const out = [];
|
|
35234
|
-
const push = (spec) => {
|
|
35235
|
-
if (spec.startsWith(".") || spec.startsWith("/")) return;
|
|
35236
|
-
if (seen.has(spec)) return;
|
|
35237
|
-
seen.add(spec);
|
|
35238
|
-
out.push(spec);
|
|
35239
|
-
};
|
|
35240
|
-
for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
|
|
35241
|
-
re.lastIndex = 0;
|
|
35242
|
-
let m;
|
|
35243
|
-
while ((m = re.exec(source)) !== null) {
|
|
35244
|
-
push(m[2]);
|
|
35245
|
-
}
|
|
35246
|
-
}
|
|
35247
|
-
return out;
|
|
35248
|
-
}
|
|
35249
|
-
|
|
35250
|
-
// src/rules/docs/expired-code-example.ts
|
|
35251
|
-
var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
|
|
35252
|
-
function stripSubpath(spec) {
|
|
35253
|
-
if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
|
|
35254
|
-
return spec.split("/")[0] ?? spec;
|
|
35255
|
-
}
|
|
35256
|
-
var expiredCodeExampleRule = createRule({
|
|
35257
|
-
id: "docs/expired-code-example",
|
|
35258
|
-
category: "docs",
|
|
35259
|
-
severity: "medium",
|
|
35260
|
-
aiSpecific: false,
|
|
35261
|
-
description: "A fenced code example imports a package that is not declared in package.json.",
|
|
35262
|
-
create(context) {
|
|
35263
|
-
return { ...context, packages: declaredPackages(context.cwd) };
|
|
35264
|
-
},
|
|
35265
|
-
analyze(context, facts) {
|
|
35266
|
-
const issues = [];
|
|
35267
|
-
const source = facts.v2?._source;
|
|
35268
|
-
if (!source) return issues;
|
|
35269
|
-
const packages = declaredPackages(context.cwd);
|
|
35270
|
-
const packageName = context.packageName;
|
|
35271
|
-
if (packageName) packages.add(packageName);
|
|
35272
|
-
const blocks = extractFencedCodeBlocks(source);
|
|
35273
|
-
for (const block of blocks) {
|
|
35274
|
-
if (!CODE_LANGS.has(block.lang)) continue;
|
|
35275
|
-
if (block.body.split("\n").length < 2) continue;
|
|
35276
|
-
const imports = extractImports(block.body);
|
|
35277
|
-
for (const imp of imports) {
|
|
35278
|
-
const pkgName = stripSubpath(imp);
|
|
35279
|
-
if (packages.has(pkgName)) continue;
|
|
35280
|
-
issues.push({
|
|
35281
|
-
ruleId: "docs/expired-code-example",
|
|
35282
|
-
category: "docs",
|
|
35283
|
-
severity: "medium",
|
|
35284
|
-
aiSpecific: false,
|
|
35285
|
-
message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
|
|
35286
|
-
line: block.line,
|
|
35287
|
-
column: block.column,
|
|
35288
|
-
advice: `Add \`${pkgName}\` to package.json or update the example.`
|
|
35289
|
-
});
|
|
35290
|
-
}
|
|
35291
|
-
}
|
|
35292
|
-
return issues;
|
|
35293
|
-
}
|
|
35294
|
-
});
|
|
35099
|
+
var import_globby = require("globby");
|
|
35295
35100
|
|
|
35296
35101
|
// src/rules/docs/stale-function-reference.ts
|
|
35297
|
-
var
|
|
35298
|
-
var
|
|
35102
|
+
var import_node_fs = require("fs");
|
|
35103
|
+
var import_node_path = require("path");
|
|
35299
35104
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
35300
35105
|
// JS reserved words
|
|
35301
35106
|
"true",
|
|
@@ -35868,29 +35673,29 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
|
|
|
35868
35673
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
35869
35674
|
var CAP = 200;
|
|
35870
35675
|
function walk(dir, out, cap) {
|
|
35871
|
-
if (!(0,
|
|
35676
|
+
if (!(0, import_node_fs.existsSync)(dir) || out.length >= cap) return;
|
|
35872
35677
|
let entries;
|
|
35873
35678
|
try {
|
|
35874
|
-
entries = (0,
|
|
35679
|
+
entries = (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
|
|
35875
35680
|
} catch {
|
|
35876
35681
|
return;
|
|
35877
35682
|
}
|
|
35878
35683
|
for (const entry of entries) {
|
|
35879
35684
|
if (out.length >= cap) return;
|
|
35880
35685
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
35881
|
-
const full = (0,
|
|
35686
|
+
const full = (0, import_node_path.join)(dir, entry.name);
|
|
35882
35687
|
if (entry.isDirectory()) walk(full, out, cap);
|
|
35883
|
-
else if (entry.isFile() && SOURCE_EXTS.has((0,
|
|
35688
|
+
else if (entry.isFile() && SOURCE_EXTS.has((0, import_node_path.extname)(entry.name))) out.push(full);
|
|
35884
35689
|
}
|
|
35885
35690
|
}
|
|
35886
35691
|
function collectExports(cwd) {
|
|
35887
35692
|
const out = /* @__PURE__ */ new Set();
|
|
35888
35693
|
const files = [];
|
|
35889
|
-
for (const root of SOURCE_ROOTS) walk((0,
|
|
35694
|
+
for (const root of SOURCE_ROOTS) walk((0, import_node_path.join)(cwd, root), files, CAP);
|
|
35890
35695
|
for (const file of files) {
|
|
35891
35696
|
let source;
|
|
35892
35697
|
try {
|
|
35893
|
-
source = (0,
|
|
35698
|
+
source = (0, import_node_fs.readFileSync)(file, "utf-8");
|
|
35894
35699
|
} catch {
|
|
35895
35700
|
continue;
|
|
35896
35701
|
}
|
|
@@ -36153,38 +35958,6 @@ function extractInlineCodeSpans(source) {
|
|
|
36153
35958
|
}
|
|
36154
35959
|
return hits;
|
|
36155
35960
|
}
|
|
36156
|
-
function extractFencedCodeBlocks(source) {
|
|
36157
|
-
const blocks = [];
|
|
36158
|
-
const lines = source.split("\n");
|
|
36159
|
-
let i = 0;
|
|
36160
|
-
while (i < lines.length) {
|
|
36161
|
-
const line = lines[i] ?? "";
|
|
36162
|
-
const fenceMatch = /^```(\w*)\s*$/.exec(line);
|
|
36163
|
-
if (!fenceMatch) {
|
|
36164
|
-
i++;
|
|
36165
|
-
continue;
|
|
36166
|
-
}
|
|
36167
|
-
const lang = fenceMatch[1] ?? "";
|
|
36168
|
-
const startLine = i + 1;
|
|
36169
|
-
const bodyLines = [];
|
|
36170
|
-
i++;
|
|
36171
|
-
while (i < lines.length) {
|
|
36172
|
-
if (/^```\s*$/.test(lines[i] ?? "")) {
|
|
36173
|
-
i++;
|
|
36174
|
-
break;
|
|
36175
|
-
}
|
|
36176
|
-
bodyLines.push(lines[i] ?? "");
|
|
36177
|
-
i++;
|
|
36178
|
-
}
|
|
36179
|
-
blocks.push({
|
|
36180
|
-
lang,
|
|
36181
|
-
body: bodyLines.join("\n"),
|
|
36182
|
-
line: startLine,
|
|
36183
|
-
column: 1
|
|
36184
|
-
});
|
|
36185
|
-
}
|
|
36186
|
-
return blocks;
|
|
36187
|
-
}
|
|
36188
35961
|
function extractMarkdownLinks(source) {
|
|
36189
35962
|
const hits = [];
|
|
36190
35963
|
const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
@@ -36201,10 +35974,10 @@ function extractMarkdownLinks(source) {
|
|
|
36201
35974
|
}
|
|
36202
35975
|
function declaredPackages(cwd) {
|
|
36203
35976
|
const out = /* @__PURE__ */ new Set();
|
|
36204
|
-
const pkgPath = (0,
|
|
36205
|
-
if (!(0,
|
|
35977
|
+
const pkgPath = (0, import_node_path2.join)(cwd, "package.json");
|
|
35978
|
+
if (!(0, import_node_fs2.existsSync)(pkgPath)) return out;
|
|
36206
35979
|
try {
|
|
36207
|
-
const raw = (0,
|
|
35980
|
+
const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
|
|
36208
35981
|
const pkg = JSON.parse(raw);
|
|
36209
35982
|
for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
36210
35983
|
const v = pkg[k];
|
|
@@ -36234,7 +36007,7 @@ var brokenLinkRule = createRule({
|
|
|
36234
36007
|
const source = facts.v2?._source;
|
|
36235
36008
|
if (!source) return issues;
|
|
36236
36009
|
const links = extractMarkdownLinks(source);
|
|
36237
|
-
const docDir = (0,
|
|
36010
|
+
const docDir = (0, import_node_path3.dirname)((0, import_node_path3.resolve)(context.cwd, context.filePath));
|
|
36238
36011
|
for (const link of links) {
|
|
36239
36012
|
const target = link.target;
|
|
36240
36013
|
if (target.startsWith("http://") || target.startsWith("https://")) continue;
|
|
@@ -36244,8 +36017,8 @@ var brokenLinkRule = createRule({
|
|
|
36244
36017
|
if (target.startsWith("/")) continue;
|
|
36245
36018
|
const filePart = target.split("#")[0] ?? target;
|
|
36246
36019
|
if (filePart === "") continue;
|
|
36247
|
-
const resolved = (0,
|
|
36248
|
-
if ((0,
|
|
36020
|
+
const resolved = (0, import_node_path3.join)(docDir, filePart);
|
|
36021
|
+
if ((0, import_node_fs3.existsSync)(resolved)) continue;
|
|
36249
36022
|
issues.push({
|
|
36250
36023
|
ruleId: "docs/broken-link",
|
|
36251
36024
|
category: "docs",
|
|
@@ -36262,6 +36035,466 @@ var brokenLinkRule = createRule({
|
|
|
36262
36035
|
}
|
|
36263
36036
|
});
|
|
36264
36037
|
|
|
36038
|
+
// src/rules/dup/identical-block.ts
|
|
36039
|
+
var crypto = __toESM(require("crypto"), 1);
|
|
36040
|
+
var WINDOW_SIZE = 10;
|
|
36041
|
+
var MIN_NORMALIZED_LENGTH = 40;
|
|
36042
|
+
var HASH_PREFIX_LENGTH = 16;
|
|
36043
|
+
var DEDUP_CACHE = /* @__PURE__ */ new Map();
|
|
36044
|
+
function normalizeAndHash(lines) {
|
|
36045
|
+
const normalized = lines.map(
|
|
36046
|
+
(line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
|
|
36047
|
+
).filter((line) => line.length > 0).join("\n");
|
|
36048
|
+
if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
|
|
36049
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
|
|
36050
|
+
}
|
|
36051
|
+
var dupIdenticalBlockRule = createRule({
|
|
36052
|
+
id: "dup/identical-block",
|
|
36053
|
+
category: "logic",
|
|
36054
|
+
severity: "medium",
|
|
36055
|
+
aiSpecific: false,
|
|
36056
|
+
description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
|
|
36057
|
+
create(_context) {
|
|
36058
|
+
return {};
|
|
36059
|
+
},
|
|
36060
|
+
analyze(_context, facts) {
|
|
36061
|
+
const issues = [];
|
|
36062
|
+
const source = facts.v2?._source;
|
|
36063
|
+
if (!source) return issues;
|
|
36064
|
+
const filePath = facts.filePath;
|
|
36065
|
+
const lines = source.split("\n");
|
|
36066
|
+
for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
|
|
36067
|
+
const window = lines.slice(i, i + WINDOW_SIZE);
|
|
36068
|
+
const hash = normalizeAndHash(window);
|
|
36069
|
+
if (!hash) continue;
|
|
36070
|
+
const existing = DEDUP_CACHE.get(hash) ?? [];
|
|
36071
|
+
const matches = existing.filter((m) => m.file !== filePath);
|
|
36072
|
+
for (const match of matches) {
|
|
36073
|
+
issues.push({
|
|
36074
|
+
ruleId: "dup/identical-block",
|
|
36075
|
+
category: "logic",
|
|
36076
|
+
severity: "medium",
|
|
36077
|
+
aiSpecific: false,
|
|
36078
|
+
message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
|
|
36079
|
+
line: i + 1,
|
|
36080
|
+
column: 0,
|
|
36081
|
+
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.",
|
|
36082
|
+
extras: {
|
|
36083
|
+
duplicateOf: {
|
|
36084
|
+
file: match.file,
|
|
36085
|
+
line: match.line + 1,
|
|
36086
|
+
hash
|
|
36087
|
+
}
|
|
36088
|
+
}
|
|
36089
|
+
});
|
|
36090
|
+
}
|
|
36091
|
+
existing.push({ file: filePath, line: i });
|
|
36092
|
+
DEDUP_CACHE.set(hash, existing);
|
|
36093
|
+
}
|
|
36094
|
+
return issues;
|
|
36095
|
+
}
|
|
36096
|
+
});
|
|
36097
|
+
|
|
36098
|
+
// src/rules/go/error-wrap-without-context.ts
|
|
36099
|
+
var ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
|
|
36100
|
+
var GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
|
|
36101
|
+
var goErrorWrapWithoutContextRule = createRule({
|
|
36102
|
+
id: "go/error-wrap-without-context",
|
|
36103
|
+
category: "typo",
|
|
36104
|
+
severity: "low",
|
|
36105
|
+
aiSpecific: true,
|
|
36106
|
+
description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
|
|
36107
|
+
create(_context) {
|
|
36108
|
+
return {};
|
|
36109
|
+
},
|
|
36110
|
+
analyze(_context, facts) {
|
|
36111
|
+
const issues = [];
|
|
36112
|
+
const source = facts.v2?._source;
|
|
36113
|
+
if (!source) return issues;
|
|
36114
|
+
let match;
|
|
36115
|
+
ERR_WRAP_REGEX.lastIndex = 0;
|
|
36116
|
+
while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
|
|
36117
|
+
const formatString = match[1];
|
|
36118
|
+
if (!formatString.includes("%w")) continue;
|
|
36119
|
+
if (formatString.length >= 30) continue;
|
|
36120
|
+
if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
|
|
36121
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
36122
|
+
issues.push({
|
|
36123
|
+
ruleId: "go/error-wrap-without-context",
|
|
36124
|
+
category: "typo",
|
|
36125
|
+
severity: "low",
|
|
36126
|
+
aiSpecific: true,
|
|
36127
|
+
message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
|
|
36128
|
+
line,
|
|
36129
|
+
column: match[0].indexOf("fmt") + 1,
|
|
36130
|
+
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'
|
|
36131
|
+
});
|
|
36132
|
+
}
|
|
36133
|
+
return issues;
|
|
36134
|
+
}
|
|
36135
|
+
});
|
|
36136
|
+
|
|
36137
|
+
// src/rules/go/nil-slice-vs-empty.ts
|
|
36138
|
+
var NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
|
|
36139
|
+
var EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
|
|
36140
|
+
var goNilSliceVsEmptyRule = createRule({
|
|
36141
|
+
id: "go/nil-slice-vs-empty",
|
|
36142
|
+
category: "typo",
|
|
36143
|
+
severity: "low",
|
|
36144
|
+
aiSpecific: true,
|
|
36145
|
+
description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
|
|
36146
|
+
create(_context) {
|
|
36147
|
+
return {};
|
|
36148
|
+
},
|
|
36149
|
+
analyze(_context, facts) {
|
|
36150
|
+
const issues = [];
|
|
36151
|
+
const source = facts.v2?._source;
|
|
36152
|
+
if (!source) return issues;
|
|
36153
|
+
const nilDecls = /* @__PURE__ */ new Map();
|
|
36154
|
+
let m;
|
|
36155
|
+
NIL_SLICE_DECL_REGEX.lastIndex = 0;
|
|
36156
|
+
while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
|
|
36157
|
+
nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
|
|
36158
|
+
}
|
|
36159
|
+
if (nilDecls.size === 0) return issues;
|
|
36160
|
+
EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
|
|
36161
|
+
while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
|
|
36162
|
+
const name = m[1];
|
|
36163
|
+
if (!nilDecls.has(name)) continue;
|
|
36164
|
+
const declLine = nilDecls.get(name);
|
|
36165
|
+
const assignLine = source.slice(0, m.index).split("\n").length;
|
|
36166
|
+
if (assignLine <= declLine) continue;
|
|
36167
|
+
issues.push({
|
|
36168
|
+
ruleId: "go/nil-slice-vs-empty",
|
|
36169
|
+
category: "typo",
|
|
36170
|
+
severity: "low",
|
|
36171
|
+
aiSpecific: true,
|
|
36172
|
+
message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
|
|
36173
|
+
line: declLine,
|
|
36174
|
+
column: 1,
|
|
36175
|
+
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."
|
|
36176
|
+
});
|
|
36177
|
+
}
|
|
36178
|
+
return issues;
|
|
36179
|
+
}
|
|
36180
|
+
});
|
|
36181
|
+
|
|
36182
|
+
// src/rules/go/struct-tag-inconsistency.ts
|
|
36183
|
+
var JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
|
|
36184
|
+
var goStructTagInconsistencyRule = createRule({
|
|
36185
|
+
id: "go/struct-tag-inconsistency",
|
|
36186
|
+
category: "typo",
|
|
36187
|
+
severity: "low",
|
|
36188
|
+
aiSpecific: true,
|
|
36189
|
+
description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
|
|
36190
|
+
create(_context) {
|
|
36191
|
+
return {};
|
|
36192
|
+
},
|
|
36193
|
+
analyze(_context, facts) {
|
|
36194
|
+
const issues = [];
|
|
36195
|
+
const source = facts.v2?._source;
|
|
36196
|
+
if (!source) return issues;
|
|
36197
|
+
const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
|
|
36198
|
+
let structMatch;
|
|
36199
|
+
while ((structMatch = structRegex.exec(source)) !== null) {
|
|
36200
|
+
const startIdx = structMatch.index;
|
|
36201
|
+
const openBrace = source.indexOf("{", startIdx);
|
|
36202
|
+
if (openBrace < 0) continue;
|
|
36203
|
+
let depth = 1;
|
|
36204
|
+
let i = openBrace + 1;
|
|
36205
|
+
while (i < source.length && depth > 0) {
|
|
36206
|
+
const ch = source[i];
|
|
36207
|
+
if (ch === "{") depth++;
|
|
36208
|
+
else if (ch === "}") depth--;
|
|
36209
|
+
i++;
|
|
36210
|
+
}
|
|
36211
|
+
const structBody = source.slice(openBrace, i);
|
|
36212
|
+
const structLine = source.slice(0, startIdx).split("\n").length;
|
|
36213
|
+
const styleCount = {};
|
|
36214
|
+
const tagMatches = [];
|
|
36215
|
+
let m;
|
|
36216
|
+
JSON_TAG_REGEX.lastIndex = 0;
|
|
36217
|
+
while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
|
|
36218
|
+
const tag = m[1];
|
|
36219
|
+
const options = m[2] ?? "";
|
|
36220
|
+
const style = options ? "with-options" : "no-options";
|
|
36221
|
+
styleCount[style] = (styleCount[style] ?? 0) + 1;
|
|
36222
|
+
tagMatches.push({ tag, style, idx: openBrace + m.index });
|
|
36223
|
+
}
|
|
36224
|
+
const styles = Object.keys(styleCount);
|
|
36225
|
+
if (styles.length < 2 || tagMatches.length < 2) continue;
|
|
36226
|
+
const dominant = styles.reduce(
|
|
36227
|
+
(a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
|
|
36228
|
+
);
|
|
36229
|
+
const minority = tagMatches.filter((t) => t.style !== dominant);
|
|
36230
|
+
if (minority.length === 0) continue;
|
|
36231
|
+
for (const m2 of minority) {
|
|
36232
|
+
const line = source.slice(0, m2.idx).split("\n").length;
|
|
36233
|
+
issues.push({
|
|
36234
|
+
ruleId: "go/struct-tag-inconsistency",
|
|
36235
|
+
category: "typo",
|
|
36236
|
+
severity: "low",
|
|
36237
|
+
aiSpecific: true,
|
|
36238
|
+
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"}`,
|
|
36239
|
+
line,
|
|
36240
|
+
column: 1,
|
|
36241
|
+
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.'
|
|
36242
|
+
});
|
|
36243
|
+
}
|
|
36244
|
+
if (issues.length > 0) break;
|
|
36245
|
+
}
|
|
36246
|
+
return issues;
|
|
36247
|
+
}
|
|
36248
|
+
});
|
|
36249
|
+
|
|
36250
|
+
// src/rules/java/arraylist-vs-linkedlist.ts
|
|
36251
|
+
var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
|
|
36252
|
+
var javaArraylistVsLinkedlistRule = createRule({
|
|
36253
|
+
id: "java/arraylist-vs-linkedlist",
|
|
36254
|
+
category: "typo",
|
|
36255
|
+
severity: "low",
|
|
36256
|
+
aiSpecific: true,
|
|
36257
|
+
description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
|
|
36258
|
+
create(_context) {
|
|
36259
|
+
return {};
|
|
36260
|
+
},
|
|
36261
|
+
analyze(_context, facts) {
|
|
36262
|
+
const issues = [];
|
|
36263
|
+
const source = facts.v2?._source;
|
|
36264
|
+
if (!source) return issues;
|
|
36265
|
+
let m;
|
|
36266
|
+
NEW_LINKED_LIST_REGEX.lastIndex = 0;
|
|
36267
|
+
while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
|
|
36268
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36269
|
+
issues.push({
|
|
36270
|
+
ruleId: "java/arraylist-vs-linkedlist",
|
|
36271
|
+
category: "typo",
|
|
36272
|
+
severity: "low",
|
|
36273
|
+
aiSpecific: true,
|
|
36274
|
+
message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
|
|
36275
|
+
line,
|
|
36276
|
+
column: 1,
|
|
36277
|
+
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."
|
|
36278
|
+
});
|
|
36279
|
+
}
|
|
36280
|
+
return issues;
|
|
36281
|
+
}
|
|
36282
|
+
});
|
|
36283
|
+
|
|
36284
|
+
// src/rules/java/empty-catch-block.ts
|
|
36285
|
+
var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
36286
|
+
var javaEmptyCatchBlockRule = createRule({
|
|
36287
|
+
id: "java/empty-catch-block",
|
|
36288
|
+
category: "logic",
|
|
36289
|
+
severity: "medium",
|
|
36290
|
+
aiSpecific: true,
|
|
36291
|
+
description: "Empty catch block \u2014 silently swallows exceptions",
|
|
36292
|
+
create(_context) {
|
|
36293
|
+
return {};
|
|
36294
|
+
},
|
|
36295
|
+
analyze(_context, facts) {
|
|
36296
|
+
const issues = [];
|
|
36297
|
+
const source = facts.v2?._source;
|
|
36298
|
+
if (!source) return issues;
|
|
36299
|
+
let m;
|
|
36300
|
+
SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
|
|
36301
|
+
while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
|
|
36302
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36303
|
+
issues.push({
|
|
36304
|
+
ruleId: "java/empty-catch-block",
|
|
36305
|
+
category: "logic",
|
|
36306
|
+
severity: "medium",
|
|
36307
|
+
aiSpecific: true,
|
|
36308
|
+
message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
|
|
36309
|
+
line,
|
|
36310
|
+
column: 1,
|
|
36311
|
+
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.'
|
|
36312
|
+
});
|
|
36313
|
+
}
|
|
36314
|
+
return issues;
|
|
36315
|
+
}
|
|
36316
|
+
});
|
|
36317
|
+
|
|
36318
|
+
// src/rules/java/legacy-date-api.ts
|
|
36319
|
+
var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
|
|
36320
|
+
var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
|
|
36321
|
+
var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
|
|
36322
|
+
var javaLegacyDateApiRule = createRule({
|
|
36323
|
+
id: "java/legacy-date-api",
|
|
36324
|
+
category: "typo",
|
|
36325
|
+
severity: "low",
|
|
36326
|
+
aiSpecific: true,
|
|
36327
|
+
description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
|
|
36328
|
+
create(_context) {
|
|
36329
|
+
return {};
|
|
36330
|
+
},
|
|
36331
|
+
analyze(_context, facts) {
|
|
36332
|
+
const issues = [];
|
|
36333
|
+
const source = facts.v2?._source;
|
|
36334
|
+
if (!source) return issues;
|
|
36335
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
36336
|
+
let m;
|
|
36337
|
+
LEGACY_IMPORT_REGEX.lastIndex = 0;
|
|
36338
|
+
while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
|
|
36339
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36340
|
+
flagged.add(line);
|
|
36341
|
+
issues.push({
|
|
36342
|
+
ruleId: "java/legacy-date-api",
|
|
36343
|
+
category: "typo",
|
|
36344
|
+
severity: "low",
|
|
36345
|
+
aiSpecific: true,
|
|
36346
|
+
message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
|
|
36347
|
+
line,
|
|
36348
|
+
column: 1,
|
|
36349
|
+
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."
|
|
36350
|
+
});
|
|
36351
|
+
}
|
|
36352
|
+
LEGACY_USAGE_REGEX.lastIndex = 0;
|
|
36353
|
+
while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
|
|
36354
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36355
|
+
if (flagged.has(line)) continue;
|
|
36356
|
+
issues.push({
|
|
36357
|
+
ruleId: "java/legacy-date-api",
|
|
36358
|
+
category: "typo",
|
|
36359
|
+
severity: "low",
|
|
36360
|
+
aiSpecific: true,
|
|
36361
|
+
message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
|
|
36362
|
+
line,
|
|
36363
|
+
column: 1,
|
|
36364
|
+
advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36365
|
+
});
|
|
36366
|
+
}
|
|
36367
|
+
CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
|
|
36368
|
+
while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
|
|
36369
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36370
|
+
if (flagged.has(line)) continue;
|
|
36371
|
+
issues.push({
|
|
36372
|
+
ruleId: "java/legacy-date-api",
|
|
36373
|
+
category: "typo",
|
|
36374
|
+
severity: "low",
|
|
36375
|
+
aiSpecific: true,
|
|
36376
|
+
message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
|
|
36377
|
+
line,
|
|
36378
|
+
column: 1,
|
|
36379
|
+
advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36380
|
+
});
|
|
36381
|
+
}
|
|
36382
|
+
return issues;
|
|
36383
|
+
}
|
|
36384
|
+
});
|
|
36385
|
+
|
|
36386
|
+
// src/rules/java/raw-type-overuse.ts
|
|
36387
|
+
var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
|
|
36388
|
+
var javaRawTypeOveruseRule = createRule({
|
|
36389
|
+
id: "java/raw-type-overuse",
|
|
36390
|
+
category: "typo",
|
|
36391
|
+
severity: "low",
|
|
36392
|
+
aiSpecific: true,
|
|
36393
|
+
description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
|
|
36394
|
+
create(_context) {
|
|
36395
|
+
return {};
|
|
36396
|
+
},
|
|
36397
|
+
analyze(_context, facts) {
|
|
36398
|
+
const issues = [];
|
|
36399
|
+
const source = facts.v2?._source;
|
|
36400
|
+
if (!source) return issues;
|
|
36401
|
+
let m;
|
|
36402
|
+
RAW_TYPE_REGEX.lastIndex = 0;
|
|
36403
|
+
while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
|
|
36404
|
+
const typeName = m[1];
|
|
36405
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36406
|
+
issues.push({
|
|
36407
|
+
ruleId: "java/raw-type-overuse",
|
|
36408
|
+
category: "typo",
|
|
36409
|
+
severity: "low",
|
|
36410
|
+
aiSpecific: true,
|
|
36411
|
+
message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
|
|
36412
|
+
line,
|
|
36413
|
+
column: 1,
|
|
36414
|
+
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.`
|
|
36415
|
+
});
|
|
36416
|
+
}
|
|
36417
|
+
return issues;
|
|
36418
|
+
}
|
|
36419
|
+
});
|
|
36420
|
+
|
|
36421
|
+
// src/rules/java/string-concat-loop.ts
|
|
36422
|
+
var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
|
|
36423
|
+
var javaStringConcatLoopRule = createRule({
|
|
36424
|
+
id: "java/string-concat-loop",
|
|
36425
|
+
category: "perf",
|
|
36426
|
+
severity: "low",
|
|
36427
|
+
aiSpecific: true,
|
|
36428
|
+
description: "String concatenation in a loop \u2014 use StringBuilder",
|
|
36429
|
+
create(_context) {
|
|
36430
|
+
return {};
|
|
36431
|
+
},
|
|
36432
|
+
analyze(_context, facts) {
|
|
36433
|
+
const issues = [];
|
|
36434
|
+
const source = facts.v2?._source;
|
|
36435
|
+
if (!source) return issues;
|
|
36436
|
+
if (!/\b(for|while|do)\b/.test(source)) return issues;
|
|
36437
|
+
let m;
|
|
36438
|
+
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
36439
|
+
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
36440
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36441
|
+
issues.push({
|
|
36442
|
+
ruleId: "java/string-concat-loop",
|
|
36443
|
+
category: "perf",
|
|
36444
|
+
severity: "low",
|
|
36445
|
+
aiSpecific: true,
|
|
36446
|
+
message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
|
|
36447
|
+
line,
|
|
36448
|
+
column: 1,
|
|
36449
|
+
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."
|
|
36450
|
+
});
|
|
36451
|
+
}
|
|
36452
|
+
return issues;
|
|
36453
|
+
}
|
|
36454
|
+
});
|
|
36455
|
+
|
|
36456
|
+
// src/rules/java/system-out-println.ts
|
|
36457
|
+
var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
|
|
36458
|
+
var DEFAULT_THRESHOLD = 1;
|
|
36459
|
+
var javaSystemOutPrintlnRule = createRule({
|
|
36460
|
+
id: "java/system-out-println",
|
|
36461
|
+
category: "typo",
|
|
36462
|
+
severity: "low",
|
|
36463
|
+
aiSpecific: true,
|
|
36464
|
+
description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
|
|
36465
|
+
create(_context) {
|
|
36466
|
+
return { threshold: DEFAULT_THRESHOLD };
|
|
36467
|
+
},
|
|
36468
|
+
analyze(context, facts) {
|
|
36469
|
+
const issues = [];
|
|
36470
|
+
const source = facts.v2?._source;
|
|
36471
|
+
if (!source) return issues;
|
|
36472
|
+
const matches = [];
|
|
36473
|
+
let m;
|
|
36474
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
36475
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
36476
|
+
matches.push(m.index);
|
|
36477
|
+
}
|
|
36478
|
+
if (matches.length <= context.threshold) return issues;
|
|
36479
|
+
const cap = Math.min(matches.length, 10);
|
|
36480
|
+
for (let i = 0; i < cap; i++) {
|
|
36481
|
+
const idx = matches[i];
|
|
36482
|
+
const line = source.slice(0, idx).split("\n").length;
|
|
36483
|
+
issues.push({
|
|
36484
|
+
ruleId: "java/system-out-println",
|
|
36485
|
+
category: "typo",
|
|
36486
|
+
severity: "low",
|
|
36487
|
+
aiSpecific: true,
|
|
36488
|
+
message: `System.out.println at line ${line} \u2014 use a logger for production output`,
|
|
36489
|
+
line,
|
|
36490
|
+
column: 1,
|
|
36491
|
+
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."
|
|
36492
|
+
});
|
|
36493
|
+
}
|
|
36494
|
+
return issues;
|
|
36495
|
+
}
|
|
36496
|
+
});
|
|
36497
|
+
|
|
36265
36498
|
// src/rules/layout/gap-monopoly.ts
|
|
36266
36499
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36267
36500
|
var gapMonopolyRule = createRule({
|
|
@@ -36614,16 +36847,16 @@ var DEFAULT_CONFIG = {
|
|
|
36614
36847
|
};
|
|
36615
36848
|
|
|
36616
36849
|
// src/config/detect/monorepo.ts
|
|
36617
|
-
var
|
|
36618
|
-
var
|
|
36850
|
+
var import_node_fs4 = require("fs");
|
|
36851
|
+
var import_node_path4 = require("path");
|
|
36619
36852
|
|
|
36620
36853
|
// src/config/detect/styling.ts
|
|
36621
|
-
var
|
|
36622
|
-
var
|
|
36854
|
+
var import_node_fs5 = require("fs");
|
|
36855
|
+
var import_node_path5 = require("path");
|
|
36623
36856
|
|
|
36624
36857
|
// src/config/detect/stack.ts
|
|
36625
|
-
var
|
|
36626
|
-
var
|
|
36858
|
+
var import_node_fs6 = require("fs");
|
|
36859
|
+
var import_node_path6 = require("path");
|
|
36627
36860
|
|
|
36628
36861
|
// src/config/presets.ts
|
|
36629
36862
|
var REACT_ONLY_RULES = {
|
|
@@ -36672,8 +36905,8 @@ var FRAMEWORK_PRESETS = {
|
|
|
36672
36905
|
};
|
|
36673
36906
|
|
|
36674
36907
|
// src/config/load.ts
|
|
36675
|
-
var
|
|
36676
|
-
var
|
|
36908
|
+
var import_node_fs8 = require("fs");
|
|
36909
|
+
var import_node_path8 = require("path");
|
|
36677
36910
|
var import_node_module = require("module");
|
|
36678
36911
|
|
|
36679
36912
|
// src/engine/logger.ts
|
|
@@ -36695,6 +36928,10 @@ function setLoggerQuiet(quiet) {
|
|
|
36695
36928
|
logger = createLogger(quiet);
|
|
36696
36929
|
}
|
|
36697
36930
|
|
|
36931
|
+
// src/config/conventions.ts
|
|
36932
|
+
var import_node_fs7 = require("fs");
|
|
36933
|
+
var import_node_path7 = require("path");
|
|
36934
|
+
|
|
36698
36935
|
// src/config/init.ts
|
|
36699
36936
|
var STRICTNESS_PRESETS = {
|
|
36700
36937
|
strict: {
|
|
@@ -37049,82 +37286,6 @@ var keyPropMissingRule = createRule({
|
|
|
37049
37286
|
}
|
|
37050
37287
|
});
|
|
37051
37288
|
|
|
37052
|
-
// src/rules/logic/ks-distribution-shift.ts
|
|
37053
|
-
var MIN_SAMPLES_PER_FEATURE = 20;
|
|
37054
|
-
function extractFileFeatures(source) {
|
|
37055
|
-
const lines = source.split("\n");
|
|
37056
|
-
const lineLengths = lines.map((l) => l.length);
|
|
37057
|
-
const identifierLengths = [];
|
|
37058
|
-
const idRe = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
37059
|
-
let m;
|
|
37060
|
-
while ((m = idRe.exec(source)) !== null) {
|
|
37061
|
-
identifierLengths.push(m[0].length);
|
|
37062
|
-
}
|
|
37063
|
-
const commentDensity = lines.map((l) => {
|
|
37064
|
-
const trimmed = l.trim();
|
|
37065
|
-
if (trimmed.length === 0) return 0;
|
|
37066
|
-
const commentChars = (trimmed.match(/^\/\/.*$/)?.[0]?.length ?? 0) + (trimmed.match(/^\s*\/\*.*?\*\/\s*$/)?.at(0)?.length ?? 0);
|
|
37067
|
-
return commentChars / trimmed.length;
|
|
37068
|
-
});
|
|
37069
|
-
return { lineLengths, identifierLengths, commentDensity };
|
|
37070
|
-
}
|
|
37071
|
-
var ksDistributionShiftRule = createRule({
|
|
37072
|
-
id: "logic/ks-distribution-shift",
|
|
37073
|
-
category: "logic",
|
|
37074
|
-
severity: "medium",
|
|
37075
|
-
aiSpecific: false,
|
|
37076
|
-
description: "Multi-feature Kolmogorov\u2013Smirnov distribution-shift vs corpus baseline (Bonferroni-corrected). Peer-reviewed ML distribution-shift detector (arXiv:2510.15996, Oct 2025).",
|
|
37077
|
-
create(context) {
|
|
37078
|
-
return context;
|
|
37079
|
-
},
|
|
37080
|
-
analyze(_context, facts) {
|
|
37081
|
-
const issues = [];
|
|
37082
|
-
if (!facts.v2) return issues;
|
|
37083
|
-
const source = facts.v2._source ?? "";
|
|
37084
|
-
if (source.length < 200) return issues;
|
|
37085
|
-
const features = extractFileFeatures(source);
|
|
37086
|
-
const samples = /* @__PURE__ */ new Map([
|
|
37087
|
-
["lineLengths", features.lineLengths],
|
|
37088
|
-
["identifierLengths", features.identifierLengths],
|
|
37089
|
-
["commentDensity", features.commentDensity]
|
|
37090
|
-
]);
|
|
37091
|
-
const baselines = getCorpusBaselines();
|
|
37092
|
-
const baselinesMap = /* @__PURE__ */ new Map();
|
|
37093
|
-
if (baselines) {
|
|
37094
|
-
baselinesMap.set("lineLengths", baselines.features.lineLengths.sample);
|
|
37095
|
-
baselinesMap.set("identifierLengths", baselines.features.identifierLengths.sample);
|
|
37096
|
-
baselinesMap.set("commentDensity", baselines.features.commentDensity.sample);
|
|
37097
|
-
} else {
|
|
37098
|
-
baselinesMap.set("lineLengths", [20, 25, 30, 32, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]);
|
|
37099
|
-
baselinesMap.set("identifierLengths", [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28]);
|
|
37100
|
-
baselinesMap.set("commentDensity", [0, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]);
|
|
37101
|
-
}
|
|
37102
|
-
for (const [name, vals] of samples) {
|
|
37103
|
-
if (vals.length < MIN_SAMPLES_PER_FEATURE) samples.delete(name);
|
|
37104
|
-
}
|
|
37105
|
-
if (samples.size === 0) return issues;
|
|
37106
|
-
const result = multiFeatureKsTest(samples, baselinesMap, 0.05);
|
|
37107
|
-
if (!result.anySignificant) return issues;
|
|
37108
|
-
const shifted = result.significantFeatures.join(", ");
|
|
37109
|
-
const details = result.significantFeatures.map((name) => {
|
|
37110
|
-
const r = result.perFeature.get(name);
|
|
37111
|
-
if (!r) return name;
|
|
37112
|
-
return `${name} (D=${r.statistic.toFixed(3)}, p=${r.pValue.toExponential(2)})`;
|
|
37113
|
-
}).join("; ");
|
|
37114
|
-
issues.push({
|
|
37115
|
-
ruleId: "logic/ks-distribution-shift",
|
|
37116
|
-
category: "logic",
|
|
37117
|
-
severity: "medium",
|
|
37118
|
-
aiSpecific: true,
|
|
37119
|
-
message: `Distribution shift detected on ${result.significantFeatures.length} of ${result.perFeature.size} features (Bonferroni \u03B1=${result.bonferroniAlpha.toExponential(2)}). Features: ${shifted}. Detail: ${details}.`,
|
|
37120
|
-
line: 1,
|
|
37121
|
-
column: 1,
|
|
37122
|
-
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."
|
|
37123
|
-
});
|
|
37124
|
-
return issues;
|
|
37125
|
-
}
|
|
37126
|
-
});
|
|
37127
|
-
|
|
37128
37289
|
// src/rules/logic/math-any-density.ts
|
|
37129
37290
|
var ANY_PER_100_LINES = 5;
|
|
37130
37291
|
var MIN_ABSOLUTE = 6;
|
|
@@ -37186,7 +37347,7 @@ var mathAnyDensityRule = createRule({
|
|
|
37186
37347
|
});
|
|
37187
37348
|
|
|
37188
37349
|
// src/rules/logic/math-console-log-storm.ts
|
|
37189
|
-
var
|
|
37350
|
+
var WINDOW_SIZE2 = 30;
|
|
37190
37351
|
var STORM_THRESHOLD = 5;
|
|
37191
37352
|
var CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
|
|
37192
37353
|
var mathConsoleLogStormRule = createRule({
|
|
@@ -37217,7 +37378,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37217
37378
|
let maxEndLine = 0;
|
|
37218
37379
|
let i = 0;
|
|
37219
37380
|
for (let j = 0; j < lines.length; j++) {
|
|
37220
|
-
while (lines[j] - lines[i] >
|
|
37381
|
+
while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
|
|
37221
37382
|
const count = j - i + 1;
|
|
37222
37383
|
if (count > maxCount) {
|
|
37223
37384
|
maxCount = count;
|
|
@@ -37231,7 +37392,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37231
37392
|
category: "logic",
|
|
37232
37393
|
severity: "high",
|
|
37233
37394
|
aiSpecific: true,
|
|
37234
|
-
message: `${maxCount} console.log calls clustered in a ${
|
|
37395
|
+
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.`,
|
|
37235
37396
|
line: firstIdx >= 0 ? lines[firstIdx] : 1,
|
|
37236
37397
|
column: firstIdx >= 0 ? columns[firstIdx] : 1,
|
|
37237
37398
|
advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
|
|
@@ -38136,7 +38297,6 @@ function scanForStringlyParams(paramText) {
|
|
|
38136
38297
|
}
|
|
38137
38298
|
|
|
38138
38299
|
// src/rules/rust/todo-macro.ts
|
|
38139
|
-
init_parser_rust();
|
|
38140
38300
|
var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
|
|
38141
38301
|
var rustTodoMacroRule = createRule({
|
|
38142
38302
|
id: "rust/todo-macro",
|
|
@@ -38272,7 +38432,6 @@ function collectReferencedNames(source) {
|
|
|
38272
38432
|
}
|
|
38273
38433
|
|
|
38274
38434
|
// src/rules/rust/unwrap-in-production.ts
|
|
38275
|
-
init_parser_rust();
|
|
38276
38435
|
var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
|
|
38277
38436
|
var rustUnwrapInProductionRule = createRule({
|
|
38278
38437
|
id: "rust/unwrap-in-production",
|
|
@@ -39409,7 +39568,7 @@ function looksRealistic(value) {
|
|
|
39409
39568
|
|
|
39410
39569
|
// src/rules/test/missing-edge-case.ts
|
|
39411
39570
|
var import_core4 = require("@swc/core");
|
|
39412
|
-
var
|
|
39571
|
+
var import_node_fs9 = require("fs");
|
|
39413
39572
|
var MAX_PER_FILE = 20;
|
|
39414
39573
|
var missingEdgeCaseRule = createRule({
|
|
39415
39574
|
id: "test/missing-edge-case",
|
|
@@ -39431,7 +39590,7 @@ var missingEdgeCaseRule = createRule({
|
|
|
39431
39590
|
const testFileSources = /* @__PURE__ */ new Map();
|
|
39432
39591
|
for (const testFile of discoverTestFiles(cwd)) {
|
|
39433
39592
|
try {
|
|
39434
|
-
testFileSources.set(testFile, (0,
|
|
39593
|
+
testFileSources.set(testFile, (0, import_node_fs9.readFileSync)(testFile, "utf-8"));
|
|
39435
39594
|
} catch {
|
|
39436
39595
|
}
|
|
39437
39596
|
}
|
|
@@ -39685,8 +39844,8 @@ function discoverTestFiles(cwd) {
|
|
|
39685
39844
|
const found = [];
|
|
39686
39845
|
for (const root of roots) {
|
|
39687
39846
|
const abs = `${cwd}/${root}`;
|
|
39688
|
-
if (!(0,
|
|
39689
|
-
walk2(abs, found,
|
|
39847
|
+
if (!(0, import_node_fs9.existsSync)(abs)) continue;
|
|
39848
|
+
walk2(abs, found, import_node_fs9.readdirSync, import_node_fs9.statSync);
|
|
39690
39849
|
if (found.length > 200) break;
|
|
39691
39850
|
}
|
|
39692
39851
|
return found;
|
|
@@ -39805,6 +39964,216 @@ function isTautologicalAssertion(hit) {
|
|
|
39805
39964
|
return false;
|
|
39806
39965
|
}
|
|
39807
39966
|
|
|
39967
|
+
// src/rules/ts/enum-vs-as-const.ts
|
|
39968
|
+
var ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
|
|
39969
|
+
var tsEnumVsAsConstRule = createRule({
|
|
39970
|
+
id: "ts/enum-vs-as-const",
|
|
39971
|
+
category: "typo",
|
|
39972
|
+
severity: "low",
|
|
39973
|
+
aiSpecific: true,
|
|
39974
|
+
description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
|
|
39975
|
+
create(_context) {
|
|
39976
|
+
return {};
|
|
39977
|
+
},
|
|
39978
|
+
analyze(_context, facts) {
|
|
39979
|
+
const issues = [];
|
|
39980
|
+
const source = facts.v2?._source;
|
|
39981
|
+
if (!source) return issues;
|
|
39982
|
+
let match;
|
|
39983
|
+
ENUM_DECL_REGEX.lastIndex = 0;
|
|
39984
|
+
while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
|
|
39985
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
39986
|
+
issues.push({
|
|
39987
|
+
ruleId: "ts/enum-vs-as-const",
|
|
39988
|
+
category: "typo",
|
|
39989
|
+
severity: "low",
|
|
39990
|
+
aiSpecific: true,
|
|
39991
|
+
message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
|
|
39992
|
+
line,
|
|
39993
|
+
column: match[0].indexOf("enum") + 1,
|
|
39994
|
+
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.'
|
|
39995
|
+
});
|
|
39996
|
+
}
|
|
39997
|
+
return issues;
|
|
39998
|
+
}
|
|
39999
|
+
});
|
|
40000
|
+
|
|
40001
|
+
// src/rules/ts/excessive-type-assertion.ts
|
|
40002
|
+
var DEFAULT_MAX = 3;
|
|
40003
|
+
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;
|
|
40004
|
+
var AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
|
|
40005
|
+
var tsExcessiveTypeAssertionRule = createRule({
|
|
40006
|
+
id: "ts/excessive-type-assertion",
|
|
40007
|
+
category: "typo",
|
|
40008
|
+
severity: "low",
|
|
40009
|
+
aiSpecific: true,
|
|
40010
|
+
description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
|
|
40011
|
+
create(_context) {
|
|
40012
|
+
return { maxAssertionsPerFunction: DEFAULT_MAX };
|
|
40013
|
+
},
|
|
40014
|
+
analyze(context, facts) {
|
|
40015
|
+
const issues = [];
|
|
40016
|
+
const source = facts.v2?._source;
|
|
40017
|
+
if (!source) return issues;
|
|
40018
|
+
let match;
|
|
40019
|
+
FN_DECL_REGEX.lastIndex = 0;
|
|
40020
|
+
while ((match = FN_DECL_REGEX.exec(source)) !== null) {
|
|
40021
|
+
const startIdx = match.index;
|
|
40022
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
40023
|
+
if (openBraceIdx < 0) continue;
|
|
40024
|
+
let depth = 1;
|
|
40025
|
+
let i = openBraceIdx + 1;
|
|
40026
|
+
while (i < source.length && depth > 0) {
|
|
40027
|
+
const ch = source[i];
|
|
40028
|
+
if (ch === "{") depth++;
|
|
40029
|
+
else if (ch === "}") depth--;
|
|
40030
|
+
i++;
|
|
40031
|
+
}
|
|
40032
|
+
const body = source.slice(openBraceIdx, i);
|
|
40033
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
40034
|
+
let asCount = 0;
|
|
40035
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40036
|
+
let asMatch;
|
|
40037
|
+
AS_REGEX.lastIndex = 0;
|
|
40038
|
+
while ((asMatch = AS_REGEX.exec(body)) !== null) {
|
|
40039
|
+
const captured = asMatch[1];
|
|
40040
|
+
if (seen.has(captured)) continue;
|
|
40041
|
+
seen.add(captured);
|
|
40042
|
+
asCount++;
|
|
40043
|
+
}
|
|
40044
|
+
if (asCount > context.maxAssertionsPerFunction) {
|
|
40045
|
+
issues.push({
|
|
40046
|
+
ruleId: "ts/excessive-type-assertion",
|
|
40047
|
+
category: "typo",
|
|
40048
|
+
severity: "low",
|
|
40049
|
+
aiSpecific: true,
|
|
40050
|
+
message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
|
|
40051
|
+
line,
|
|
40052
|
+
column: 1,
|
|
40053
|
+
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."
|
|
40054
|
+
});
|
|
40055
|
+
}
|
|
40056
|
+
}
|
|
40057
|
+
return issues;
|
|
40058
|
+
}
|
|
40059
|
+
});
|
|
40060
|
+
|
|
40061
|
+
// src/rules/ts/import-type-misuse.ts
|
|
40062
|
+
var INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
|
|
40063
|
+
var tsImportTypeMisuseRule = createRule({
|
|
40064
|
+
id: "ts/import-type-misuse",
|
|
40065
|
+
category: "typo",
|
|
40066
|
+
severity: "low",
|
|
40067
|
+
aiSpecific: true,
|
|
40068
|
+
description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
|
|
40069
|
+
create(_context) {
|
|
40070
|
+
return {};
|
|
40071
|
+
},
|
|
40072
|
+
analyze(_context, facts) {
|
|
40073
|
+
const issues = [];
|
|
40074
|
+
const source = facts.v2?._source;
|
|
40075
|
+
if (!source) return issues;
|
|
40076
|
+
let match;
|
|
40077
|
+
INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
|
|
40078
|
+
while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
|
|
40079
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
40080
|
+
issues.push({
|
|
40081
|
+
ruleId: "ts/import-type-misuse",
|
|
40082
|
+
category: "typo",
|
|
40083
|
+
severity: "low",
|
|
40084
|
+
aiSpecific: true,
|
|
40085
|
+
message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
|
|
40086
|
+
line,
|
|
40087
|
+
column: match[0].indexOf("type") + 1,
|
|
40088
|
+
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.'
|
|
40089
|
+
});
|
|
40090
|
+
}
|
|
40091
|
+
return issues;
|
|
40092
|
+
}
|
|
40093
|
+
});
|
|
40094
|
+
|
|
40095
|
+
// src/rules/ts/never-vs-unknown.ts
|
|
40096
|
+
var NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
|
|
40097
|
+
var THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
|
|
40098
|
+
var tsNeverVsUnknownRule = createRule({
|
|
40099
|
+
id: "ts/never-vs-unknown",
|
|
40100
|
+
category: "typo",
|
|
40101
|
+
severity: "low",
|
|
40102
|
+
aiSpecific: true,
|
|
40103
|
+
description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
|
|
40104
|
+
create(_context) {
|
|
40105
|
+
return {};
|
|
40106
|
+
},
|
|
40107
|
+
analyze(_context, facts) {
|
|
40108
|
+
const issues = [];
|
|
40109
|
+
const source = facts.v2?._source;
|
|
40110
|
+
if (!source) return issues;
|
|
40111
|
+
let match;
|
|
40112
|
+
NEVER_RETURN_REGEX.lastIndex = 0;
|
|
40113
|
+
while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
|
|
40114
|
+
const fnName = match[1];
|
|
40115
|
+
const startIdx = match.index;
|
|
40116
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
40117
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
40118
|
+
if (openBraceIdx < 0) continue;
|
|
40119
|
+
let depth = 1;
|
|
40120
|
+
let i = openBraceIdx + 1;
|
|
40121
|
+
while (i < source.length && depth > 0) {
|
|
40122
|
+
const ch = source[i];
|
|
40123
|
+
if (ch === "{") depth++;
|
|
40124
|
+
else if (ch === "}") depth--;
|
|
40125
|
+
i++;
|
|
40126
|
+
}
|
|
40127
|
+
const body = source.slice(openBraceIdx, i);
|
|
40128
|
+
if (THROW_OR_LOOP_REGEX.test(body)) continue;
|
|
40129
|
+
issues.push({
|
|
40130
|
+
ruleId: "ts/never-vs-unknown",
|
|
40131
|
+
category: "typo",
|
|
40132
|
+
severity: "low",
|
|
40133
|
+
aiSpecific: true,
|
|
40134
|
+
message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
|
|
40135
|
+
line,
|
|
40136
|
+
column: match[0].indexOf("never") + 1,
|
|
40137
|
+
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.'
|
|
40138
|
+
});
|
|
40139
|
+
}
|
|
40140
|
+
return issues;
|
|
40141
|
+
}
|
|
40142
|
+
});
|
|
40143
|
+
|
|
40144
|
+
// src/rules/ts/optional-chain-overuse.ts
|
|
40145
|
+
var DEFAULT_MIN_CHAIN_LENGTH = 5;
|
|
40146
|
+
var tsOptionalChainOveruseRule = createRule({
|
|
40147
|
+
id: "ts/optional-chain-overuse",
|
|
40148
|
+
category: "logic",
|
|
40149
|
+
severity: "low",
|
|
40150
|
+
aiSpecific: true,
|
|
40151
|
+
description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
|
|
40152
|
+
create(_context) {
|
|
40153
|
+
return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
|
|
40154
|
+
},
|
|
40155
|
+
analyze(context, facts) {
|
|
40156
|
+
const issues = [];
|
|
40157
|
+
const expressions = facts.v2.logic?.logicalExpressions;
|
|
40158
|
+
if (!expressions) return issues;
|
|
40159
|
+
for (const expression of expressions) {
|
|
40160
|
+
if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
|
|
40161
|
+
issues.push({
|
|
40162
|
+
ruleId: "ts/optional-chain-overuse",
|
|
40163
|
+
category: "logic",
|
|
40164
|
+
severity: "low",
|
|
40165
|
+
aiSpecific: true,
|
|
40166
|
+
message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
|
|
40167
|
+
line: expression.line,
|
|
40168
|
+
column: expression.column,
|
|
40169
|
+
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."
|
|
40170
|
+
});
|
|
40171
|
+
}
|
|
40172
|
+
}
|
|
40173
|
+
return issues;
|
|
40174
|
+
}
|
|
40175
|
+
});
|
|
40176
|
+
|
|
39808
40177
|
// src/rules/typo/calc-fontsize.ts
|
|
39809
40178
|
var FONT_SIZE_RE = /\bfont-size\s*:\s*[^;]*\bcalc\s*\(/i;
|
|
39810
40179
|
var calcFontsizeRule = createRule({
|
|
@@ -41342,9 +41711,18 @@ var builtinRules = [
|
|
|
41342
41711
|
unusedLocalRule,
|
|
41343
41712
|
unusedParameterRule,
|
|
41344
41713
|
brokenLinkRule,
|
|
41345
|
-
expiredCodeExampleRule,
|
|
41346
41714
|
staleFunctionReferenceRule,
|
|
41347
41715
|
stalePackageReferenceRule,
|
|
41716
|
+
dupIdenticalBlockRule,
|
|
41717
|
+
goErrorWrapWithoutContextRule,
|
|
41718
|
+
goNilSliceVsEmptyRule,
|
|
41719
|
+
goStructTagInconsistencyRule,
|
|
41720
|
+
javaArraylistVsLinkedlistRule,
|
|
41721
|
+
javaEmptyCatchBlockRule,
|
|
41722
|
+
javaLegacyDateApiRule,
|
|
41723
|
+
javaRawTypeOveruseRule,
|
|
41724
|
+
javaStringConcatLoopRule,
|
|
41725
|
+
javaSystemOutPrintlnRule,
|
|
41348
41726
|
gapMonopolyRule,
|
|
41349
41727
|
mathElementUniformityRule,
|
|
41350
41728
|
mathGridUniformityRule,
|
|
@@ -41354,7 +41732,6 @@ var builtinRules = [
|
|
|
41354
41732
|
ghostDefensiveRule,
|
|
41355
41733
|
heapsDeviationRule,
|
|
41356
41734
|
keyPropMissingRule,
|
|
41357
|
-
ksDistributionShiftRule,
|
|
41358
41735
|
mathAnyDensityRule,
|
|
41359
41736
|
mathConsoleLogStormRule,
|
|
41360
41737
|
mathGiniClassUsageRule,
|
|
@@ -41388,6 +41765,11 @@ var builtinRules = [
|
|
|
41388
41765
|
fakePlaceholderRule,
|
|
41389
41766
|
missingEdgeCaseRule,
|
|
41390
41767
|
weakAssertionRule,
|
|
41768
|
+
tsEnumVsAsConstRule,
|
|
41769
|
+
tsExcessiveTypeAssertionRule,
|
|
41770
|
+
tsImportTypeMisuseRule,
|
|
41771
|
+
tsNeverVsUnknownRule,
|
|
41772
|
+
tsOptionalChainOveruseRule,
|
|
41391
41773
|
calcFontsizeRule,
|
|
41392
41774
|
calcRawPxRule,
|
|
41393
41775
|
clampOffscaleRule,
|
|
@@ -41558,7 +41940,7 @@ var signal_strength_default = {
|
|
|
41558
41940
|
precision: 0.9966,
|
|
41559
41941
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41560
41942
|
verdict: "USEFUL",
|
|
41561
|
-
_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).",
|
|
41943
|
+
_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.",
|
|
41562
41944
|
aiSpecific: true,
|
|
41563
41945
|
_v7Verdict: "USEFUL",
|
|
41564
41946
|
_v7Lift: 182622.43,
|
|
@@ -41566,7 +41948,8 @@ var signal_strength_default = {
|
|
|
41566
41948
|
_v7FpRate: 0,
|
|
41567
41949
|
_v7Precision: 0.9957,
|
|
41568
41950
|
_v8Verdict: "USEFUL",
|
|
41569
|
-
_v8Lift: 99999
|
|
41951
|
+
_v8Lift: 99999,
|
|
41952
|
+
defaultOff: false
|
|
41570
41953
|
},
|
|
41571
41954
|
"ai/errors-near-eof": {
|
|
41572
41955
|
recall: 0.0948,
|
|
@@ -41815,7 +42198,7 @@ var signal_strength_default = {
|
|
|
41815
42198
|
precision: 0.9512,
|
|
41816
42199
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41817
42200
|
verdict: "USEFUL",
|
|
41818
|
-
_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).",
|
|
42201
|
+
_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.",
|
|
41819
42202
|
aiSpecific: true,
|
|
41820
42203
|
_v7Verdict: "USEFUL",
|
|
41821
42204
|
_v7Lift: 7099.57,
|
|
@@ -41823,7 +42206,8 @@ var signal_strength_default = {
|
|
|
41823
42206
|
_v7FpRate: 1e-4,
|
|
41824
42207
|
_v7Precision: 0.929,
|
|
41825
42208
|
_v8Verdict: "USEFUL",
|
|
41826
|
-
_v8Lift: 99999
|
|
42209
|
+
_v8Lift: 99999,
|
|
42210
|
+
defaultOff: false
|
|
41827
42211
|
},
|
|
41828
42212
|
"context/import-path-mismatch": {
|
|
41829
42213
|
recall: 0.0681,
|
|
@@ -42007,7 +42391,7 @@ var signal_strength_default = {
|
|
|
42007
42391
|
precision: 0.8834,
|
|
42008
42392
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42009
42393
|
verdict: "USEFUL",
|
|
42010
|
-
_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.",
|
|
42394
|
+
_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.",
|
|
42011
42395
|
aiSpecific: true,
|
|
42012
42396
|
_v7Verdict: "DORMANT",
|
|
42013
42397
|
_v7Lift: 1,
|
|
@@ -42015,7 +42399,8 @@ var signal_strength_default = {
|
|
|
42015
42399
|
_v7FpRate: 0,
|
|
42016
42400
|
_v7Precision: 0,
|
|
42017
42401
|
_v8Verdict: "USEFUL",
|
|
42018
|
-
_v8Lift: 32.74
|
|
42402
|
+
_v8Lift: 32.74,
|
|
42403
|
+
defaultOff: false
|
|
42019
42404
|
},
|
|
42020
42405
|
"dead/unused-parameter": {
|
|
42021
42406
|
recall: 8e-4,
|
|
@@ -42051,24 +42436,6 @@ var signal_strength_default = {
|
|
|
42051
42436
|
_v8Verdict: "OK",
|
|
42052
42437
|
_v8Lift: 48.52
|
|
42053
42438
|
},
|
|
42054
|
-
"docs/expired-code-example": {
|
|
42055
|
-
recall: 0,
|
|
42056
|
-
fpRate: 0,
|
|
42057
|
-
ratio: 0,
|
|
42058
|
-
precision: 0,
|
|
42059
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42060
|
-
verdict: "INVERTED",
|
|
42061
|
-
_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).",
|
|
42062
|
-
aiSpecific: false,
|
|
42063
|
-
_v7Verdict: "DORMANT",
|
|
42064
|
-
_v7Lift: 1,
|
|
42065
|
-
_v7Recall: 0,
|
|
42066
|
-
_v7FpRate: 0,
|
|
42067
|
-
_v7Precision: 0,
|
|
42068
|
-
_v8Verdict: "INVERTED",
|
|
42069
|
-
_v8Lift: 0,
|
|
42070
|
-
defaultOff: true
|
|
42071
|
-
},
|
|
42072
42439
|
"docs/stale-function-reference": {
|
|
42073
42440
|
recall: 26e-4,
|
|
42074
42441
|
fpRate: 5e-4,
|
|
@@ -42103,6 +42470,186 @@ var signal_strength_default = {
|
|
|
42103
42470
|
_v8Verdict: "OK",
|
|
42104
42471
|
_v8Lift: 255.9
|
|
42105
42472
|
},
|
|
42473
|
+
"dup/identical-block": {
|
|
42474
|
+
recall: 0,
|
|
42475
|
+
fpRate: 0,
|
|
42476
|
+
ratio: 1,
|
|
42477
|
+
precision: 0,
|
|
42478
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42479
|
+
verdict: "DORMANT",
|
|
42480
|
+
_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.",
|
|
42481
|
+
aiSpecific: false,
|
|
42482
|
+
_v7Verdict: "DORMANT",
|
|
42483
|
+
_v7Lift: 1,
|
|
42484
|
+
_v7Recall: 0,
|
|
42485
|
+
_v7FpRate: 0,
|
|
42486
|
+
_v7Precision: 0,
|
|
42487
|
+
_v8Verdict: "DORMANT",
|
|
42488
|
+
_v8Lift: 1,
|
|
42489
|
+
defaultOff: true
|
|
42490
|
+
},
|
|
42491
|
+
"go/error-wrap-without-context": {
|
|
42492
|
+
recall: 0,
|
|
42493
|
+
fpRate: 0,
|
|
42494
|
+
ratio: 1,
|
|
42495
|
+
precision: 0,
|
|
42496
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42497
|
+
verdict: "DORMANT",
|
|
42498
|
+
_calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42499
|
+
aiSpecific: true,
|
|
42500
|
+
_v7Verdict: "DORMANT",
|
|
42501
|
+
_v7Lift: 1,
|
|
42502
|
+
_v7Recall: 0,
|
|
42503
|
+
_v7FpRate: 0,
|
|
42504
|
+
_v7Precision: 0,
|
|
42505
|
+
_v8Verdict: "DORMANT",
|
|
42506
|
+
_v8Lift: 1,
|
|
42507
|
+
defaultOff: true
|
|
42508
|
+
},
|
|
42509
|
+
"go/nil-slice-vs-empty": {
|
|
42510
|
+
recall: 0,
|
|
42511
|
+
fpRate: 0,
|
|
42512
|
+
ratio: 1,
|
|
42513
|
+
precision: 0,
|
|
42514
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42515
|
+
verdict: "DORMANT",
|
|
42516
|
+
_calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42517
|
+
aiSpecific: true,
|
|
42518
|
+
_v7Verdict: "DORMANT",
|
|
42519
|
+
_v7Lift: 1,
|
|
42520
|
+
_v7Recall: 0,
|
|
42521
|
+
_v7FpRate: 0,
|
|
42522
|
+
_v7Precision: 0,
|
|
42523
|
+
_v8Verdict: "DORMANT",
|
|
42524
|
+
_v8Lift: 1,
|
|
42525
|
+
defaultOff: true
|
|
42526
|
+
},
|
|
42527
|
+
"go/struct-tag-inconsistency": {
|
|
42528
|
+
recall: 0,
|
|
42529
|
+
fpRate: 0,
|
|
42530
|
+
ratio: 1,
|
|
42531
|
+
precision: 0,
|
|
42532
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42533
|
+
verdict: "DORMANT",
|
|
42534
|
+
_calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42535
|
+
aiSpecific: true,
|
|
42536
|
+
_v7Verdict: "DORMANT",
|
|
42537
|
+
_v7Lift: 1,
|
|
42538
|
+
_v7Recall: 0,
|
|
42539
|
+
_v7FpRate: 0,
|
|
42540
|
+
_v7Precision: 0,
|
|
42541
|
+
_v8Verdict: "DORMANT",
|
|
42542
|
+
_v8Lift: 1,
|
|
42543
|
+
defaultOff: true
|
|
42544
|
+
},
|
|
42545
|
+
"java/arraylist-vs-linkedlist": {
|
|
42546
|
+
recall: 0,
|
|
42547
|
+
fpRate: 0,
|
|
42548
|
+
ratio: 1,
|
|
42549
|
+
precision: 0,
|
|
42550
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42551
|
+
verdict: "DORMANT",
|
|
42552
|
+
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42553
|
+
aiSpecific: true,
|
|
42554
|
+
_v7Verdict: "DORMANT",
|
|
42555
|
+
_v7Lift: 1,
|
|
42556
|
+
_v7Recall: 0,
|
|
42557
|
+
_v7FpRate: 0,
|
|
42558
|
+
_v7Precision: 0,
|
|
42559
|
+
_v8Verdict: "DORMANT",
|
|
42560
|
+
_v8Lift: 1,
|
|
42561
|
+
defaultOff: true
|
|
42562
|
+
},
|
|
42563
|
+
"java/empty-catch-block": {
|
|
42564
|
+
recall: 0,
|
|
42565
|
+
fpRate: 0,
|
|
42566
|
+
ratio: 1,
|
|
42567
|
+
precision: 0,
|
|
42568
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42569
|
+
verdict: "DORMANT",
|
|
42570
|
+
_calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42571
|
+
aiSpecific: true,
|
|
42572
|
+
_v7Verdict: "DORMANT",
|
|
42573
|
+
_v7Lift: 1,
|
|
42574
|
+
_v7Recall: 0,
|
|
42575
|
+
_v7FpRate: 0,
|
|
42576
|
+
_v7Precision: 0,
|
|
42577
|
+
_v8Verdict: "DORMANT",
|
|
42578
|
+
_v8Lift: 1,
|
|
42579
|
+
defaultOff: true
|
|
42580
|
+
},
|
|
42581
|
+
"java/legacy-date-api": {
|
|
42582
|
+
recall: 0,
|
|
42583
|
+
fpRate: 0,
|
|
42584
|
+
ratio: 1,
|
|
42585
|
+
precision: 0,
|
|
42586
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42587
|
+
verdict: "DORMANT",
|
|
42588
|
+
_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.",
|
|
42589
|
+
aiSpecific: true,
|
|
42590
|
+
_v7Verdict: "DORMANT",
|
|
42591
|
+
_v7Lift: 1,
|
|
42592
|
+
_v7Recall: 0,
|
|
42593
|
+
_v7FpRate: 0,
|
|
42594
|
+
_v7Precision: 0,
|
|
42595
|
+
_v8Verdict: "DORMANT",
|
|
42596
|
+
_v8Lift: 1,
|
|
42597
|
+
defaultOff: true
|
|
42598
|
+
},
|
|
42599
|
+
"java/raw-type-overuse": {
|
|
42600
|
+
recall: 0,
|
|
42601
|
+
fpRate: 0,
|
|
42602
|
+
ratio: 1,
|
|
42603
|
+
precision: 0,
|
|
42604
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42605
|
+
verdict: "DORMANT",
|
|
42606
|
+
_calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
|
|
42607
|
+
aiSpecific: true,
|
|
42608
|
+
_v7Verdict: "DORMANT",
|
|
42609
|
+
_v7Lift: 1,
|
|
42610
|
+
_v7Recall: 0,
|
|
42611
|
+
_v7FpRate: 0,
|
|
42612
|
+
_v7Precision: 0,
|
|
42613
|
+
_v8Verdict: "DORMANT",
|
|
42614
|
+
_v8Lift: 1,
|
|
42615
|
+
defaultOff: true
|
|
42616
|
+
},
|
|
42617
|
+
"java/string-concat-loop": {
|
|
42618
|
+
recall: 0,
|
|
42619
|
+
fpRate: 0,
|
|
42620
|
+
ratio: 1,
|
|
42621
|
+
precision: 0,
|
|
42622
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42623
|
+
verdict: "DORMANT",
|
|
42624
|
+
_calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
|
|
42625
|
+
aiSpecific: true,
|
|
42626
|
+
_v7Verdict: "DORMANT",
|
|
42627
|
+
_v7Lift: 1,
|
|
42628
|
+
_v7Recall: 0,
|
|
42629
|
+
_v7FpRate: 0,
|
|
42630
|
+
_v7Precision: 0,
|
|
42631
|
+
_v8Verdict: "DORMANT",
|
|
42632
|
+
_v8Lift: 1,
|
|
42633
|
+
defaultOff: true
|
|
42634
|
+
},
|
|
42635
|
+
"java/system-out-println": {
|
|
42636
|
+
recall: 0,
|
|
42637
|
+
fpRate: 0,
|
|
42638
|
+
ratio: 1,
|
|
42639
|
+
precision: 0,
|
|
42640
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42641
|
+
verdict: "DORMANT",
|
|
42642
|
+
_calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42643
|
+
aiSpecific: true,
|
|
42644
|
+
_v7Verdict: "DORMANT",
|
|
42645
|
+
_v7Lift: 1,
|
|
42646
|
+
_v7Recall: 0,
|
|
42647
|
+
_v7FpRate: 0,
|
|
42648
|
+
_v7Precision: 0,
|
|
42649
|
+
_v8Verdict: "DORMANT",
|
|
42650
|
+
_v8Lift: 1,
|
|
42651
|
+
defaultOff: true
|
|
42652
|
+
},
|
|
42106
42653
|
"layout/forced-layout": {
|
|
42107
42654
|
recall: 0,
|
|
42108
42655
|
fpRate: 0,
|
|
@@ -42231,7 +42778,7 @@ var signal_strength_default = {
|
|
|
42231
42778
|
precision: 0.8889,
|
|
42232
42779
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42233
42780
|
verdict: "USEFUL",
|
|
42234
|
-
_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).",
|
|
42781
|
+
_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.",
|
|
42235
42782
|
aiSpecific: true,
|
|
42236
42783
|
_v7Verdict: "USEFUL",
|
|
42237
42784
|
_v7Lift: 80917.5,
|
|
@@ -42239,7 +42786,8 @@ var signal_strength_default = {
|
|
|
42239
42786
|
_v7FpRate: 0,
|
|
42240
42787
|
_v7Precision: 0.8824,
|
|
42241
42788
|
_v8Verdict: "USEFUL",
|
|
42242
|
-
_v8Lift: 99999
|
|
42789
|
+
_v8Lift: 99999,
|
|
42790
|
+
defaultOff: false
|
|
42243
42791
|
},
|
|
42244
42792
|
"logic/heaps-deviation": {
|
|
42245
42793
|
recall: 0.0126,
|
|
@@ -42275,24 +42823,6 @@ var signal_strength_default = {
|
|
|
42275
42823
|
_v8Verdict: "USEFUL",
|
|
42276
42824
|
_v8Lift: 1760.69
|
|
42277
42825
|
},
|
|
42278
|
-
"logic/ks-distribution-shift": {
|
|
42279
|
-
recall: 0.6889,
|
|
42280
|
-
fpRate: 0.4411,
|
|
42281
|
-
ratio: 1.46,
|
|
42282
|
-
precision: 0.6457,
|
|
42283
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42284
|
-
verdict: "NOISY",
|
|
42285
|
-
_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).",
|
|
42286
|
-
aiSpecific: false,
|
|
42287
|
-
_v7Verdict: "USEFUL",
|
|
42288
|
-
_v7Lift: 2.09,
|
|
42289
|
-
_v7Recall: 0.6431,
|
|
42290
|
-
_v7FpRate: 0.34,
|
|
42291
|
-
_v7Precision: 0.7096,
|
|
42292
|
-
_v8Verdict: "INVERTED",
|
|
42293
|
-
_v8Lift: 0.71,
|
|
42294
|
-
defaultOff: true
|
|
42295
|
-
},
|
|
42296
42826
|
"logic/math-any-density": {
|
|
42297
42827
|
recall: 17e-4,
|
|
42298
42828
|
fpRate: 13e-4,
|
|
@@ -42658,7 +43188,7 @@ var signal_strength_default = {
|
|
|
42658
43188
|
precision: 1,
|
|
42659
43189
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42660
43190
|
verdict: "USEFUL",
|
|
42661
|
-
_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).",
|
|
43191
|
+
_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.",
|
|
42662
43192
|
aiSpecific: true,
|
|
42663
43193
|
_v7Verdict: "USEFUL",
|
|
42664
43194
|
_v7Lift: 99999,
|
|
@@ -42666,7 +43196,8 @@ var signal_strength_default = {
|
|
|
42666
43196
|
_v7FpRate: 0,
|
|
42667
43197
|
_v7Precision: 1,
|
|
42668
43198
|
_v8Verdict: "DORMANT",
|
|
42669
|
-
_v8Lift: 1
|
|
43199
|
+
_v8Lift: 1,
|
|
43200
|
+
defaultOff: false
|
|
42670
43201
|
},
|
|
42671
43202
|
"security/hardcoded-secret": {
|
|
42672
43203
|
recall: 14e-4,
|
|
@@ -42856,6 +43387,96 @@ var signal_strength_default = {
|
|
|
42856
43387
|
_v8Verdict: "USEFUL",
|
|
42857
43388
|
_v8Lift: 651.94
|
|
42858
43389
|
},
|
|
43390
|
+
"ts/enum-vs-as-const": {
|
|
43391
|
+
recall: 0,
|
|
43392
|
+
fpRate: 0,
|
|
43393
|
+
ratio: 1,
|
|
43394
|
+
precision: 0,
|
|
43395
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43396
|
+
verdict: "DORMANT",
|
|
43397
|
+
_calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43398
|
+
aiSpecific: true,
|
|
43399
|
+
_v7Verdict: "DORMANT",
|
|
43400
|
+
_v7Lift: 1,
|
|
43401
|
+
_v7Recall: 0,
|
|
43402
|
+
_v7FpRate: 0,
|
|
43403
|
+
_v7Precision: 0,
|
|
43404
|
+
_v8Verdict: "DORMANT",
|
|
43405
|
+
_v8Lift: 1,
|
|
43406
|
+
defaultOff: true
|
|
43407
|
+
},
|
|
43408
|
+
"ts/excessive-type-assertion": {
|
|
43409
|
+
recall: 0,
|
|
43410
|
+
fpRate: 0,
|
|
43411
|
+
ratio: 1,
|
|
43412
|
+
precision: 0,
|
|
43413
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43414
|
+
verdict: "DORMANT",
|
|
43415
|
+
_calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43416
|
+
aiSpecific: true,
|
|
43417
|
+
_v7Verdict: "DORMANT",
|
|
43418
|
+
_v7Lift: 1,
|
|
43419
|
+
_v7Recall: 0,
|
|
43420
|
+
_v7FpRate: 0,
|
|
43421
|
+
_v7Precision: 0,
|
|
43422
|
+
_v8Verdict: "DORMANT",
|
|
43423
|
+
_v8Lift: 1,
|
|
43424
|
+
defaultOff: true
|
|
43425
|
+
},
|
|
43426
|
+
"ts/import-type-misuse": {
|
|
43427
|
+
recall: 0,
|
|
43428
|
+
fpRate: 0,
|
|
43429
|
+
ratio: 1,
|
|
43430
|
+
precision: 0,
|
|
43431
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43432
|
+
verdict: "DORMANT",
|
|
43433
|
+
_calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43434
|
+
aiSpecific: true,
|
|
43435
|
+
_v7Verdict: "DORMANT",
|
|
43436
|
+
_v7Lift: 1,
|
|
43437
|
+
_v7Recall: 0,
|
|
43438
|
+
_v7FpRate: 0,
|
|
43439
|
+
_v7Precision: 0,
|
|
43440
|
+
_v8Verdict: "DORMANT",
|
|
43441
|
+
_v8Lift: 1,
|
|
43442
|
+
defaultOff: true
|
|
43443
|
+
},
|
|
43444
|
+
"ts/never-vs-unknown": {
|
|
43445
|
+
recall: 0,
|
|
43446
|
+
fpRate: 0,
|
|
43447
|
+
ratio: 1,
|
|
43448
|
+
precision: 0,
|
|
43449
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43450
|
+
verdict: "DORMANT",
|
|
43451
|
+
_calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43452
|
+
aiSpecific: true,
|
|
43453
|
+
_v7Verdict: "DORMANT",
|
|
43454
|
+
_v7Lift: 1,
|
|
43455
|
+
_v7Recall: 0,
|
|
43456
|
+
_v7FpRate: 0,
|
|
43457
|
+
_v7Precision: 0,
|
|
43458
|
+
_v8Verdict: "DORMANT",
|
|
43459
|
+
_v8Lift: 1,
|
|
43460
|
+
defaultOff: true
|
|
43461
|
+
},
|
|
43462
|
+
"ts/optional-chain-overuse": {
|
|
43463
|
+
recall: 0,
|
|
43464
|
+
fpRate: 0,
|
|
43465
|
+
ratio: 1,
|
|
43466
|
+
precision: 0,
|
|
43467
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43468
|
+
verdict: "DORMANT",
|
|
43469
|
+
_calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43470
|
+
aiSpecific: true,
|
|
43471
|
+
_v7Verdict: "DORMANT",
|
|
43472
|
+
_v7Lift: 1,
|
|
43473
|
+
_v7Recall: 0,
|
|
43474
|
+
_v7FpRate: 0,
|
|
43475
|
+
_v7Precision: 0,
|
|
43476
|
+
_v8Verdict: "DORMANT",
|
|
43477
|
+
_v8Lift: 1,
|
|
43478
|
+
defaultOff: true
|
|
43479
|
+
},
|
|
42859
43480
|
"typo/calc-fontsize": {
|
|
42860
43481
|
recall: 0,
|
|
42861
43482
|
fpRate: 0,
|
|
@@ -43158,7 +43779,7 @@ var signal_strength_default = {
|
|
|
43158
43779
|
precision: 0.9774,
|
|
43159
43780
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43160
43781
|
verdict: "USEFUL",
|
|
43161
|
-
_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).",
|
|
43782
|
+
_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.",
|
|
43162
43783
|
aiSpecific: false,
|
|
43163
43784
|
_v7Verdict: "USEFUL",
|
|
43164
43785
|
_v7Lift: 59285.01,
|
|
@@ -43166,7 +43787,8 @@ var signal_strength_default = {
|
|
|
43166
43787
|
_v7FpRate: 0,
|
|
43167
43788
|
_v7Precision: 0.9697,
|
|
43168
43789
|
_v8Verdict: "USEFUL",
|
|
43169
|
-
_v8Lift: 99999
|
|
43790
|
+
_v8Lift: 99999,
|
|
43791
|
+
defaultOff: false
|
|
43170
43792
|
},
|
|
43171
43793
|
"visual/spacing-scale-violation": {
|
|
43172
43794
|
recall: 86e-4,
|
|
@@ -43284,7 +43906,7 @@ function loadSignalStrength() {
|
|
|
43284
43906
|
function buildParserCacheConfig(cwd) {
|
|
43285
43907
|
const envVal = process.env.SLOP_AUDIT_CACHE;
|
|
43286
43908
|
const enabled = envVal === "1" || envVal === "true";
|
|
43287
|
-
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0,
|
|
43909
|
+
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0, import_node_path10.join)(cwd, ".slopbrick", "cache", "ast");
|
|
43288
43910
|
return { enabled, root };
|
|
43289
43911
|
}
|
|
43290
43912
|
function applyRuleOverrides(issues, rules) {
|
|
@@ -43302,7 +43924,7 @@ function applyRuleOverrides(issues, rules) {
|
|
|
43302
43924
|
}
|
|
43303
43925
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
43304
43926
|
const cache = buildParserCacheConfig(cwd);
|
|
43305
|
-
const ext = (0,
|
|
43927
|
+
const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
|
|
43306
43928
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
43307
43929
|
".swift",
|
|
43308
43930
|
".kt",
|