slopbrick 0.19.0 → 0.21.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/README.md +5 -5
- package/dist/engine/worker.cjs +863 -616
- package/dist/engine/worker.js +862 -616
- package/dist/index.cjs +3468 -2937
- package/dist/index.d.cts +30 -12
- package/dist/index.d.ts +30 -12
- package/dist/index.js +3457 -2926
- package/package.json +1 -1
package/dist/engine/worker.js
CHANGED
|
@@ -1,419 +1,13 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __esm = (fn, res) => function __init() {
|
|
4
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
-
};
|
|
6
2
|
var __export = (target, all) => {
|
|
7
3
|
for (var name in all)
|
|
8
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
5
|
};
|
|
10
6
|
|
|
11
|
-
// src/engine/parser-rust.ts
|
|
12
|
-
import Parser from "tree-sitter";
|
|
13
|
-
import Rust from "tree-sitter-rust";
|
|
14
|
-
function getRustParser() {
|
|
15
|
-
if (cachedParser) return { ok: true, parser: cachedParser };
|
|
16
|
-
if (loadError) return { ok: false, error: loadError };
|
|
17
|
-
try {
|
|
18
|
-
rustLanguage = Rust;
|
|
19
|
-
if (!rustLanguage || typeof rustLanguage !== "object") {
|
|
20
|
-
throw new Error("tree-sitter-rust: language export is missing or malformed");
|
|
21
|
-
}
|
|
22
|
-
const parser = new Parser();
|
|
23
|
-
parser.setLanguage(rustLanguage);
|
|
24
|
-
cachedParser = parser;
|
|
25
|
-
return { ok: true, parser };
|
|
26
|
-
} catch (err) {
|
|
27
|
-
loadError = err instanceof Error ? err : new Error(String(err));
|
|
28
|
-
return { ok: false, error: loadError };
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function effectiveParser() {
|
|
32
|
-
if (forcedFailure) return { ok: false, error: forcedFailure };
|
|
33
|
-
return getRustParser();
|
|
34
|
-
}
|
|
35
|
-
function parseRust(source) {
|
|
36
|
-
const result = effectiveParser();
|
|
37
|
-
if (!result.ok) return null;
|
|
38
|
-
if (!source || source.trim() === "") return null;
|
|
39
|
-
try {
|
|
40
|
-
const rawTree = result.parser.parse(source);
|
|
41
|
-
if (!rawTree) return null;
|
|
42
|
-
const tree = rawTree;
|
|
43
|
-
if (!tree || !tree.rootNode) return null;
|
|
44
|
-
if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
return tree;
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function isRustParserAvailable() {
|
|
53
|
-
const result = getRustParser();
|
|
54
|
-
return result.ok;
|
|
55
|
-
}
|
|
56
|
-
var cachedParser, rustLanguage, loadError, forcedFailure;
|
|
57
|
-
var init_parser_rust = __esm({
|
|
58
|
-
"src/engine/parser-rust.ts"() {
|
|
59
|
-
"use strict";
|
|
60
|
-
cachedParser = null;
|
|
61
|
-
rustLanguage = null;
|
|
62
|
-
loadError = null;
|
|
63
|
-
forcedFailure = null;
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// src/engine/visitors/rust.ts
|
|
68
|
-
function parseRustFile(filePath, source, options = {}) {
|
|
69
|
-
const empty = {
|
|
70
|
-
imports: [],
|
|
71
|
-
functions: [],
|
|
72
|
-
structs: [],
|
|
73
|
-
traits: [],
|
|
74
|
-
impls: []
|
|
75
|
-
};
|
|
76
|
-
if (!isRustParserAvailable() || options.forceFallback) {
|
|
77
|
-
return empty;
|
|
78
|
-
}
|
|
79
|
-
const tree = parseRust(source);
|
|
80
|
-
if (!tree) return empty;
|
|
81
|
-
return walkRustTree(tree);
|
|
82
|
-
}
|
|
83
|
-
function walkRustTree(tree) {
|
|
84
|
-
const ctx = { inTestConfig: false };
|
|
85
|
-
const out = {
|
|
86
|
-
imports: [],
|
|
87
|
-
functions: [],
|
|
88
|
-
structs: [],
|
|
89
|
-
traits: [],
|
|
90
|
-
impls: []
|
|
91
|
-
};
|
|
92
|
-
walkChildren(tree.rootNode, ctx, out);
|
|
93
|
-
return out;
|
|
94
|
-
}
|
|
95
|
-
function walkChildren(node, ctx, out) {
|
|
96
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
97
|
-
const child = node.namedChild(i);
|
|
98
|
-
if (!child) continue;
|
|
99
|
-
visitNode(child, ctx, out);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function visitNode(node, ctx, out) {
|
|
103
|
-
const innerCtx = { ...ctx };
|
|
104
|
-
const attrState = readPrecedingAttributes(node);
|
|
105
|
-
switch (node.type) {
|
|
106
|
-
case "use_declaration": {
|
|
107
|
-
out.imports.push(extractUse(node));
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
case "function_item": {
|
|
111
|
-
const isMethod = isInImplBlock(node);
|
|
112
|
-
out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
|
|
113
|
-
walkChildren(getField(node, "body") ?? node, innerCtx, out);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
case "struct_item": {
|
|
117
|
-
out.structs.push(extractStruct(node, attrState));
|
|
118
|
-
const body = getField(node, "body");
|
|
119
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
case "trait_item": {
|
|
123
|
-
out.traits.push(extractTrait(node, attrState));
|
|
124
|
-
const body = getField(node, "body");
|
|
125
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
case "impl_item": {
|
|
129
|
-
const implEntry = extractImpl(node, attrState, innerCtx);
|
|
130
|
-
out.impls.push(implEntry.entry);
|
|
131
|
-
const body = getField(node, "body");
|
|
132
|
-
if (body) walkChildren(body, innerCtx, out);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
case "mod_item": {
|
|
136
|
-
const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
|
|
137
|
-
const modCtx = { ...innerCtx, inTestConfig: modIsTest };
|
|
138
|
-
const body = getField(node, "body");
|
|
139
|
-
if (body) walkChildren(body, modCtx, out);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
default: {
|
|
143
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
144
|
-
const child = node.namedChild(i);
|
|
145
|
-
if (child) visitNode(child, innerCtx, out);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function readPrecedingAttributes(node) {
|
|
151
|
-
const state = {
|
|
152
|
-
isTestCfg: false,
|
|
153
|
-
isTest: false,
|
|
154
|
-
isPub: false,
|
|
155
|
-
derives: []
|
|
156
|
-
};
|
|
157
|
-
const parent = node.parent;
|
|
158
|
-
if (!parent) return state;
|
|
159
|
-
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
160
|
-
const sibling = parent.namedChild(i);
|
|
161
|
-
if (!sibling || sibling === node) continue;
|
|
162
|
-
if (sibling.endIndex > node.startIndex) continue;
|
|
163
|
-
if (sibling.type !== "attribute_item") continue;
|
|
164
|
-
decodeAttribute(sibling, state);
|
|
165
|
-
}
|
|
166
|
-
return state;
|
|
167
|
-
}
|
|
168
|
-
function decodeAttribute(attr, state) {
|
|
169
|
-
for (let i = 0; i < attr.namedChildCount; i++) {
|
|
170
|
-
const child = attr.namedChild(i);
|
|
171
|
-
if (!child || child.type !== "attribute") continue;
|
|
172
|
-
const name = firstIdentifier(child);
|
|
173
|
-
if (!name) continue;
|
|
174
|
-
if (name === "cfg") {
|
|
175
|
-
const cfgText = child.text;
|
|
176
|
-
if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
|
|
177
|
-
state.isTestCfg = true;
|
|
178
|
-
}
|
|
179
|
-
} else if (name === "test") {
|
|
180
|
-
state.isTest = true;
|
|
181
|
-
} else if (name === "derive") {
|
|
182
|
-
for (let j = 0; j < child.namedChildCount; j++) {
|
|
183
|
-
const inner = child.namedChild(j);
|
|
184
|
-
if (inner) {
|
|
185
|
-
const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
|
|
186
|
-
for (const m of matches) state.derives.push(m[1]);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function firstIdentifier(node) {
|
|
193
|
-
const text = node.text;
|
|
194
|
-
const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
195
|
-
return idMatch ? idMatch[1] : null;
|
|
196
|
-
}
|
|
197
|
-
function extractUse(node) {
|
|
198
|
-
const text = node.text;
|
|
199
|
-
const argument = node.namedChild(0);
|
|
200
|
-
const argField = node.childForFieldName("argument");
|
|
201
|
-
const path = argField?.text ?? argument?.text ?? "";
|
|
202
|
-
const names = [];
|
|
203
|
-
let isGlob = false;
|
|
204
|
-
const useListNode = findUseListNode(argument);
|
|
205
|
-
if (useListNode) {
|
|
206
|
-
for (let i = 0; i < useListNode.namedChildCount; i++) {
|
|
207
|
-
const item = useListNode.namedChild(i);
|
|
208
|
-
if (item.type === "use_wildcard") {
|
|
209
|
-
isGlob = true;
|
|
210
|
-
} else if (item.type === "identifier") {
|
|
211
|
-
names.push({ name: item.text });
|
|
212
|
-
} else if (item.type === "use_as_clause") {
|
|
213
|
-
const alias = item.childForFieldName("alias");
|
|
214
|
-
const binding = item.childForFieldName("path");
|
|
215
|
-
if (binding) {
|
|
216
|
-
names.push({ name: binding.text, alias: alias?.text });
|
|
217
|
-
} else {
|
|
218
|
-
names.push({ name: item.text });
|
|
219
|
-
}
|
|
220
|
-
} else if (item.type === "scoped_identifier") {
|
|
221
|
-
const idents = collectIdentifiers(item);
|
|
222
|
-
names.push({ name: idents[idents.length - 1] ?? item.text });
|
|
223
|
-
} else if (item.text.includes(" as ")) {
|
|
224
|
-
const [head, alias] = item.text.split(/\s+as\s+/);
|
|
225
|
-
names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
|
|
226
|
-
} else {
|
|
227
|
-
names.push({ name: item.text });
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
} else if (argument?.type === "use_wildcard") {
|
|
231
|
-
isGlob = true;
|
|
232
|
-
} else if (argument) {
|
|
233
|
-
const idents = collectIdentifiers(argument);
|
|
234
|
-
const last = idents[idents.length - 1] ?? argument.text;
|
|
235
|
-
names.push({ name: last });
|
|
236
|
-
const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
|
|
237
|
-
if (asMatch) names[0].alias = asMatch[1];
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
path: path.trim(),
|
|
241
|
-
names,
|
|
242
|
-
isGlob,
|
|
243
|
-
line: node.startPosition.row + 1,
|
|
244
|
-
column: node.startPosition.column
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
|
|
248
|
-
const nameNode = node.childForFieldName("name");
|
|
249
|
-
const name = nameNode?.text ?? "<anon>";
|
|
250
|
-
const params = node.childForFieldName("parameters");
|
|
251
|
-
const body = node.childForFieldName("body");
|
|
252
|
-
const visibilityMod = node.namedChild(0);
|
|
253
|
-
const isPublic = visibilityMod?.type === "visibility_modifier";
|
|
254
|
-
let receiver;
|
|
255
|
-
if (isMethod && params) {
|
|
256
|
-
const selfParam = findSelfParameter(params);
|
|
257
|
-
if (selfParam) receiver = selfParam.text;
|
|
258
|
-
}
|
|
259
|
-
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
260
|
-
const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
|
|
261
|
-
return {
|
|
262
|
-
name,
|
|
263
|
-
line: node.startPosition.row + 1,
|
|
264
|
-
column: node.startPosition.column,
|
|
265
|
-
isPublic,
|
|
266
|
-
isMethod,
|
|
267
|
-
receiver,
|
|
268
|
-
bodyLines,
|
|
269
|
-
inTestConfig
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
function extractStruct(node, attrs) {
|
|
273
|
-
const nameNode = node.childForFieldName("name");
|
|
274
|
-
return {
|
|
275
|
-
name: nameNode?.text ?? "<anon>",
|
|
276
|
-
line: node.startPosition.row + 1,
|
|
277
|
-
column: node.startPosition.column,
|
|
278
|
-
isPublic: hasVisibility(node),
|
|
279
|
-
isDerive: attrs.derives.length > 0,
|
|
280
|
-
derives: [...attrs.derives]
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
function extractTrait(node, _attrs) {
|
|
284
|
-
const nameNode = node.childForFieldName("name");
|
|
285
|
-
return {
|
|
286
|
-
name: nameNode?.text ?? "<anon>",
|
|
287
|
-
line: node.startPosition.row + 1,
|
|
288
|
-
column: node.startPosition.column,
|
|
289
|
-
isPublic: hasVisibility(node)
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
function extractImpl(node, _attrs, _ctx) {
|
|
293
|
-
const traitNode = node.childForFieldName("trait");
|
|
294
|
-
const typeNode = node.childForFieldName("type");
|
|
295
|
-
const typeText = typeNode?.text ?? "<anon>";
|
|
296
|
-
const traitText = traitNode?.text;
|
|
297
|
-
const body = node.childForFieldName("body");
|
|
298
|
-
const methods = [];
|
|
299
|
-
if (body) {
|
|
300
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
301
|
-
const child = body.namedChild(i);
|
|
302
|
-
if (child?.type === "function_item") {
|
|
303
|
-
const m = child.childForFieldName("name");
|
|
304
|
-
if (m) methods.push(m.text);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return {
|
|
309
|
-
entry: {
|
|
310
|
-
type: typeText,
|
|
311
|
-
trait: traitText ?? void 0,
|
|
312
|
-
methods,
|
|
313
|
-
line: node.startPosition.row + 1,
|
|
314
|
-
column: node.startPosition.column
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
function findUseListNode(argument) {
|
|
319
|
-
if (!argument) return null;
|
|
320
|
-
if (argument.type === "use_list") return argument;
|
|
321
|
-
if (argument.type === "scoped_use_list") {
|
|
322
|
-
for (let i = 0; i < argument.namedChildCount; i++) {
|
|
323
|
-
const c = argument.namedChild(i);
|
|
324
|
-
if (c?.type === "use_list") return c;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
function getField(node, fieldName) {
|
|
330
|
-
return node.childForFieldName(fieldName);
|
|
331
|
-
}
|
|
332
|
-
function collectIdentifiers(node) {
|
|
333
|
-
const out = [];
|
|
334
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
335
|
-
const child = node.namedChild(i);
|
|
336
|
-
if (!child) continue;
|
|
337
|
-
if (child.type === "identifier" || child.type === "type_identifier") {
|
|
338
|
-
out.push(child.text);
|
|
339
|
-
} else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
|
|
340
|
-
out.push(...collectIdentifiers(child));
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return out;
|
|
344
|
-
}
|
|
345
|
-
function hasVisibility(node) {
|
|
346
|
-
return node.namedChild(0)?.type === "visibility_modifier";
|
|
347
|
-
}
|
|
348
|
-
function isInImplBlock(node) {
|
|
349
|
-
for (let p = node.parent; p; p = p.parent) {
|
|
350
|
-
if (p.type === "impl_item") return true;
|
|
351
|
-
if (p.type === "function_item" || p.type === "source_file") return false;
|
|
352
|
-
}
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
function findSelfParameter(params) {
|
|
356
|
-
for (let i = 0; i < params.namedChildCount; i++) {
|
|
357
|
-
const child = params.namedChild(i);
|
|
358
|
-
if (child?.type === "self_parameter") return child;
|
|
359
|
-
}
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
function isFunctionAttrTest(node) {
|
|
363
|
-
void node;
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
var SERVICE_SUFFIXES, SERVICE_SUFFIX_GROUP, RUST_SERVICE_STRUCT_RE, RUST_SERVICE_IMPL_RE;
|
|
367
|
-
var init_rust = __esm({
|
|
368
|
-
"src/engine/visitors/rust.ts"() {
|
|
369
|
-
"use strict";
|
|
370
|
-
init_parser_rust();
|
|
371
|
-
SERVICE_SUFFIXES = [
|
|
372
|
-
"Service",
|
|
373
|
-
"Manager",
|
|
374
|
-
"Handler",
|
|
375
|
-
"Repository",
|
|
376
|
-
"Controller",
|
|
377
|
-
"Helper",
|
|
378
|
-
"Factory",
|
|
379
|
-
"Provider",
|
|
380
|
-
"Store",
|
|
381
|
-
"API",
|
|
382
|
-
"Client",
|
|
383
|
-
"Adapter",
|
|
384
|
-
"Resolver",
|
|
385
|
-
"Mapper",
|
|
386
|
-
"Transformer",
|
|
387
|
-
"Serializer",
|
|
388
|
-
"Validator",
|
|
389
|
-
"Strategy",
|
|
390
|
-
"Facade",
|
|
391
|
-
"Decorator",
|
|
392
|
-
"Observer",
|
|
393
|
-
"Builder",
|
|
394
|
-
"Command",
|
|
395
|
-
"Processor",
|
|
396
|
-
"Worker",
|
|
397
|
-
"Job",
|
|
398
|
-
"Actor",
|
|
399
|
-
"Executor"
|
|
400
|
-
];
|
|
401
|
-
SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
|
|
402
|
-
RUST_SERVICE_STRUCT_RE = new RegExp(
|
|
403
|
-
`^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
404
|
-
"gm"
|
|
405
|
-
);
|
|
406
|
-
RUST_SERVICE_IMPL_RE = new RegExp(
|
|
407
|
-
`^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
408
|
-
"gm"
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
7
|
// src/engine/worker.ts
|
|
414
8
|
import { isMainThread, parentPort, workerData } from "worker_threads";
|
|
415
|
-
import { extname as
|
|
416
|
-
import { join as
|
|
9
|
+
import { extname as extname4 } from "path";
|
|
10
|
+
import { join as join11 } from "path";
|
|
417
11
|
|
|
418
12
|
// ../engine/dist/index.js
|
|
419
13
|
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
@@ -7234,8 +6828,396 @@ function dispatchNode(node, parent, path, vctx) {
|
|
|
7234
6828
|
return false;
|
|
7235
6829
|
}
|
|
7236
6830
|
|
|
6831
|
+
// src/engine/parser-rust.ts
|
|
6832
|
+
import Parser from "tree-sitter";
|
|
6833
|
+
import Rust from "tree-sitter-rust";
|
|
6834
|
+
var cachedParser = null;
|
|
6835
|
+
var rustLanguage = null;
|
|
6836
|
+
var loadError = null;
|
|
6837
|
+
function getRustParser() {
|
|
6838
|
+
if (cachedParser) return { ok: true, parser: cachedParser };
|
|
6839
|
+
if (loadError) return { ok: false, error: loadError };
|
|
6840
|
+
try {
|
|
6841
|
+
rustLanguage = Rust;
|
|
6842
|
+
if (!rustLanguage || typeof rustLanguage !== "object") {
|
|
6843
|
+
throw new Error("tree-sitter-rust: language export is missing or malformed");
|
|
6844
|
+
}
|
|
6845
|
+
const parser = new Parser();
|
|
6846
|
+
parser.setLanguage(rustLanguage);
|
|
6847
|
+
cachedParser = parser;
|
|
6848
|
+
return { ok: true, parser };
|
|
6849
|
+
} catch (err) {
|
|
6850
|
+
loadError = err instanceof Error ? err : new Error(String(err));
|
|
6851
|
+
return { ok: false, error: loadError };
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
var forcedFailure = null;
|
|
6855
|
+
function effectiveParser() {
|
|
6856
|
+
if (forcedFailure) return { ok: false, error: forcedFailure };
|
|
6857
|
+
return getRustParser();
|
|
6858
|
+
}
|
|
6859
|
+
function parseRust(source) {
|
|
6860
|
+
const result = effectiveParser();
|
|
6861
|
+
if (!result.ok) return null;
|
|
6862
|
+
if (!source || source.trim() === "") return null;
|
|
6863
|
+
try {
|
|
6864
|
+
const rawTree = result.parser.parse(source);
|
|
6865
|
+
if (!rawTree) return null;
|
|
6866
|
+
const tree = rawTree;
|
|
6867
|
+
if (!tree || !tree.rootNode) return null;
|
|
6868
|
+
if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
|
|
6869
|
+
return null;
|
|
6870
|
+
}
|
|
6871
|
+
return tree;
|
|
6872
|
+
} catch {
|
|
6873
|
+
return null;
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
function isRustParserAvailable() {
|
|
6877
|
+
const result = getRustParser();
|
|
6878
|
+
return result.ok;
|
|
6879
|
+
}
|
|
6880
|
+
|
|
6881
|
+
// src/engine/visitors/rust.ts
|
|
6882
|
+
var SERVICE_SUFFIXES = [
|
|
6883
|
+
"Service",
|
|
6884
|
+
"Manager",
|
|
6885
|
+
"Handler",
|
|
6886
|
+
"Repository",
|
|
6887
|
+
"Controller",
|
|
6888
|
+
"Helper",
|
|
6889
|
+
"Factory",
|
|
6890
|
+
"Provider",
|
|
6891
|
+
"Store",
|
|
6892
|
+
"API",
|
|
6893
|
+
"Client",
|
|
6894
|
+
"Adapter",
|
|
6895
|
+
"Resolver",
|
|
6896
|
+
"Mapper",
|
|
6897
|
+
"Transformer",
|
|
6898
|
+
"Serializer",
|
|
6899
|
+
"Validator",
|
|
6900
|
+
"Strategy",
|
|
6901
|
+
"Facade",
|
|
6902
|
+
"Decorator",
|
|
6903
|
+
"Observer",
|
|
6904
|
+
"Builder",
|
|
6905
|
+
"Command",
|
|
6906
|
+
"Processor",
|
|
6907
|
+
"Worker",
|
|
6908
|
+
"Job",
|
|
6909
|
+
"Actor",
|
|
6910
|
+
"Executor"
|
|
6911
|
+
];
|
|
6912
|
+
var SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
|
|
6913
|
+
var RUST_SERVICE_STRUCT_RE = new RegExp(
|
|
6914
|
+
`^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
6915
|
+
"gm"
|
|
6916
|
+
);
|
|
6917
|
+
var RUST_SERVICE_IMPL_RE = new RegExp(
|
|
6918
|
+
`^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
|
|
6919
|
+
"gm"
|
|
6920
|
+
);
|
|
6921
|
+
function parseRustFile(filePath, source, options = {}) {
|
|
6922
|
+
const empty = {
|
|
6923
|
+
imports: [],
|
|
6924
|
+
functions: [],
|
|
6925
|
+
structs: [],
|
|
6926
|
+
traits: [],
|
|
6927
|
+
impls: []
|
|
6928
|
+
};
|
|
6929
|
+
if (!isRustParserAvailable() || options.forceFallback) {
|
|
6930
|
+
return empty;
|
|
6931
|
+
}
|
|
6932
|
+
const tree = parseRust(source);
|
|
6933
|
+
if (!tree) return empty;
|
|
6934
|
+
return walkRustTree(tree);
|
|
6935
|
+
}
|
|
6936
|
+
function walkRustTree(tree) {
|
|
6937
|
+
const ctx = { inTestConfig: false };
|
|
6938
|
+
const out = {
|
|
6939
|
+
imports: [],
|
|
6940
|
+
functions: [],
|
|
6941
|
+
structs: [],
|
|
6942
|
+
traits: [],
|
|
6943
|
+
impls: []
|
|
6944
|
+
};
|
|
6945
|
+
walkChildren(tree.rootNode, ctx, out);
|
|
6946
|
+
return out;
|
|
6947
|
+
}
|
|
6948
|
+
function walkChildren(node, ctx, out) {
|
|
6949
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6950
|
+
const child = node.namedChild(i);
|
|
6951
|
+
if (!child) continue;
|
|
6952
|
+
visitNode(child, ctx, out);
|
|
6953
|
+
}
|
|
6954
|
+
}
|
|
6955
|
+
function visitNode(node, ctx, out) {
|
|
6956
|
+
const innerCtx = { ...ctx };
|
|
6957
|
+
const attrState = readPrecedingAttributes(node);
|
|
6958
|
+
switch (node.type) {
|
|
6959
|
+
case "use_declaration": {
|
|
6960
|
+
out.imports.push(extractUse(node));
|
|
6961
|
+
return;
|
|
6962
|
+
}
|
|
6963
|
+
case "function_item": {
|
|
6964
|
+
const isMethod = isInImplBlock(node);
|
|
6965
|
+
out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
|
|
6966
|
+
walkChildren(getField(node, "body") ?? node, innerCtx, out);
|
|
6967
|
+
return;
|
|
6968
|
+
}
|
|
6969
|
+
case "struct_item": {
|
|
6970
|
+
out.structs.push(extractStruct(node, attrState));
|
|
6971
|
+
const body = getField(node, "body");
|
|
6972
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6975
|
+
case "trait_item": {
|
|
6976
|
+
out.traits.push(extractTrait(node, attrState));
|
|
6977
|
+
const body = getField(node, "body");
|
|
6978
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
case "impl_item": {
|
|
6982
|
+
const implEntry = extractImpl(node, attrState, innerCtx);
|
|
6983
|
+
out.impls.push(implEntry.entry);
|
|
6984
|
+
const body = getField(node, "body");
|
|
6985
|
+
if (body) walkChildren(body, innerCtx, out);
|
|
6986
|
+
return;
|
|
6987
|
+
}
|
|
6988
|
+
case "mod_item": {
|
|
6989
|
+
const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
|
|
6990
|
+
const modCtx = { ...innerCtx, inTestConfig: modIsTest };
|
|
6991
|
+
const body = getField(node, "body");
|
|
6992
|
+
if (body) walkChildren(body, modCtx, out);
|
|
6993
|
+
return;
|
|
6994
|
+
}
|
|
6995
|
+
default: {
|
|
6996
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6997
|
+
const child = node.namedChild(i);
|
|
6998
|
+
if (child) visitNode(child, innerCtx, out);
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
function readPrecedingAttributes(node) {
|
|
7004
|
+
const state = {
|
|
7005
|
+
isTestCfg: false,
|
|
7006
|
+
isTest: false,
|
|
7007
|
+
isPub: false,
|
|
7008
|
+
derives: []
|
|
7009
|
+
};
|
|
7010
|
+
const parent = node.parent;
|
|
7011
|
+
if (!parent) return state;
|
|
7012
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
7013
|
+
const sibling = parent.namedChild(i);
|
|
7014
|
+
if (!sibling || sibling === node) continue;
|
|
7015
|
+
if (sibling.endIndex > node.startIndex) continue;
|
|
7016
|
+
if (sibling.type !== "attribute_item") continue;
|
|
7017
|
+
decodeAttribute(sibling, state);
|
|
7018
|
+
}
|
|
7019
|
+
return state;
|
|
7020
|
+
}
|
|
7021
|
+
function decodeAttribute(attr, state) {
|
|
7022
|
+
for (let i = 0; i < attr.namedChildCount; i++) {
|
|
7023
|
+
const child = attr.namedChild(i);
|
|
7024
|
+
if (!child || child.type !== "attribute") continue;
|
|
7025
|
+
const name = firstIdentifier(child);
|
|
7026
|
+
if (!name) continue;
|
|
7027
|
+
if (name === "cfg") {
|
|
7028
|
+
const cfgText = child.text;
|
|
7029
|
+
if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
|
|
7030
|
+
state.isTestCfg = true;
|
|
7031
|
+
}
|
|
7032
|
+
} else if (name === "test") {
|
|
7033
|
+
state.isTest = true;
|
|
7034
|
+
} else if (name === "derive") {
|
|
7035
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
7036
|
+
const inner = child.namedChild(j);
|
|
7037
|
+
if (inner) {
|
|
7038
|
+
const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
|
|
7039
|
+
for (const m of matches) state.derives.push(m[1]);
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
function firstIdentifier(node) {
|
|
7046
|
+
const text = node.text;
|
|
7047
|
+
const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
7048
|
+
return idMatch ? idMatch[1] : null;
|
|
7049
|
+
}
|
|
7050
|
+
function extractUse(node) {
|
|
7051
|
+
const text = node.text;
|
|
7052
|
+
const argument = node.namedChild(0);
|
|
7053
|
+
const argField = node.childForFieldName("argument");
|
|
7054
|
+
const path = argField?.text ?? argument?.text ?? "";
|
|
7055
|
+
const names = [];
|
|
7056
|
+
let isGlob = false;
|
|
7057
|
+
const useListNode = findUseListNode(argument);
|
|
7058
|
+
if (useListNode) {
|
|
7059
|
+
for (let i = 0; i < useListNode.namedChildCount; i++) {
|
|
7060
|
+
const item = useListNode.namedChild(i);
|
|
7061
|
+
if (item.type === "use_wildcard") {
|
|
7062
|
+
isGlob = true;
|
|
7063
|
+
} else if (item.type === "identifier") {
|
|
7064
|
+
names.push({ name: item.text });
|
|
7065
|
+
} else if (item.type === "use_as_clause") {
|
|
7066
|
+
const alias = item.childForFieldName("alias");
|
|
7067
|
+
const binding = item.childForFieldName("path");
|
|
7068
|
+
if (binding) {
|
|
7069
|
+
names.push({ name: binding.text, alias: alias?.text });
|
|
7070
|
+
} else {
|
|
7071
|
+
names.push({ name: item.text });
|
|
7072
|
+
}
|
|
7073
|
+
} else if (item.type === "scoped_identifier") {
|
|
7074
|
+
const idents = collectIdentifiers(item);
|
|
7075
|
+
names.push({ name: idents[idents.length - 1] ?? item.text });
|
|
7076
|
+
} else if (item.text.includes(" as ")) {
|
|
7077
|
+
const [head, alias] = item.text.split(/\s+as\s+/);
|
|
7078
|
+
names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
|
|
7079
|
+
} else {
|
|
7080
|
+
names.push({ name: item.text });
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
} else if (argument?.type === "use_wildcard") {
|
|
7084
|
+
isGlob = true;
|
|
7085
|
+
} else if (argument) {
|
|
7086
|
+
const idents = collectIdentifiers(argument);
|
|
7087
|
+
const last = idents[idents.length - 1] ?? argument.text;
|
|
7088
|
+
names.push({ name: last });
|
|
7089
|
+
const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
|
|
7090
|
+
if (asMatch) names[0].alias = asMatch[1];
|
|
7091
|
+
}
|
|
7092
|
+
return {
|
|
7093
|
+
path: path.trim(),
|
|
7094
|
+
names,
|
|
7095
|
+
isGlob,
|
|
7096
|
+
line: node.startPosition.row + 1,
|
|
7097
|
+
column: node.startPosition.column
|
|
7098
|
+
};
|
|
7099
|
+
}
|
|
7100
|
+
function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
|
|
7101
|
+
const nameNode = node.childForFieldName("name");
|
|
7102
|
+
const name = nameNode?.text ?? "<anon>";
|
|
7103
|
+
const params = node.childForFieldName("parameters");
|
|
7104
|
+
const body = node.childForFieldName("body");
|
|
7105
|
+
const visibilityMod = node.namedChild(0);
|
|
7106
|
+
const isPublic = visibilityMod?.type === "visibility_modifier";
|
|
7107
|
+
let receiver;
|
|
7108
|
+
if (isMethod && params) {
|
|
7109
|
+
const selfParam = findSelfParameter(params);
|
|
7110
|
+
if (selfParam) receiver = selfParam.text;
|
|
7111
|
+
}
|
|
7112
|
+
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
7113
|
+
const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
|
|
7114
|
+
return {
|
|
7115
|
+
name,
|
|
7116
|
+
line: node.startPosition.row + 1,
|
|
7117
|
+
column: node.startPosition.column,
|
|
7118
|
+
isPublic,
|
|
7119
|
+
isMethod,
|
|
7120
|
+
receiver,
|
|
7121
|
+
bodyLines,
|
|
7122
|
+
inTestConfig
|
|
7123
|
+
};
|
|
7124
|
+
}
|
|
7125
|
+
function extractStruct(node, attrs) {
|
|
7126
|
+
const nameNode = node.childForFieldName("name");
|
|
7127
|
+
return {
|
|
7128
|
+
name: nameNode?.text ?? "<anon>",
|
|
7129
|
+
line: node.startPosition.row + 1,
|
|
7130
|
+
column: node.startPosition.column,
|
|
7131
|
+
isPublic: hasVisibility(node),
|
|
7132
|
+
isDerive: attrs.derives.length > 0,
|
|
7133
|
+
derives: [...attrs.derives]
|
|
7134
|
+
};
|
|
7135
|
+
}
|
|
7136
|
+
function extractTrait(node, _attrs) {
|
|
7137
|
+
const nameNode = node.childForFieldName("name");
|
|
7138
|
+
return {
|
|
7139
|
+
name: nameNode?.text ?? "<anon>",
|
|
7140
|
+
line: node.startPosition.row + 1,
|
|
7141
|
+
column: node.startPosition.column,
|
|
7142
|
+
isPublic: hasVisibility(node)
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
function extractImpl(node, _attrs, _ctx) {
|
|
7146
|
+
const traitNode = node.childForFieldName("trait");
|
|
7147
|
+
const typeNode = node.childForFieldName("type");
|
|
7148
|
+
const typeText = typeNode?.text ?? "<anon>";
|
|
7149
|
+
const traitText = traitNode?.text;
|
|
7150
|
+
const body = node.childForFieldName("body");
|
|
7151
|
+
const methods = [];
|
|
7152
|
+
if (body) {
|
|
7153
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
7154
|
+
const child = body.namedChild(i);
|
|
7155
|
+
if (child?.type === "function_item") {
|
|
7156
|
+
const m = child.childForFieldName("name");
|
|
7157
|
+
if (m) methods.push(m.text);
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
return {
|
|
7162
|
+
entry: {
|
|
7163
|
+
type: typeText,
|
|
7164
|
+
trait: traitText ?? void 0,
|
|
7165
|
+
methods,
|
|
7166
|
+
line: node.startPosition.row + 1,
|
|
7167
|
+
column: node.startPosition.column
|
|
7168
|
+
}
|
|
7169
|
+
};
|
|
7170
|
+
}
|
|
7171
|
+
function findUseListNode(argument) {
|
|
7172
|
+
if (!argument) return null;
|
|
7173
|
+
if (argument.type === "use_list") return argument;
|
|
7174
|
+
if (argument.type === "scoped_use_list") {
|
|
7175
|
+
for (let i = 0; i < argument.namedChildCount; i++) {
|
|
7176
|
+
const c = argument.namedChild(i);
|
|
7177
|
+
if (c?.type === "use_list") return c;
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
return null;
|
|
7181
|
+
}
|
|
7182
|
+
function getField(node, fieldName) {
|
|
7183
|
+
return node.childForFieldName(fieldName);
|
|
7184
|
+
}
|
|
7185
|
+
function collectIdentifiers(node) {
|
|
7186
|
+
const out = [];
|
|
7187
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7188
|
+
const child = node.namedChild(i);
|
|
7189
|
+
if (!child) continue;
|
|
7190
|
+
if (child.type === "identifier" || child.type === "type_identifier") {
|
|
7191
|
+
out.push(child.text);
|
|
7192
|
+
} else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
|
|
7193
|
+
out.push(...collectIdentifiers(child));
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
return out;
|
|
7197
|
+
}
|
|
7198
|
+
function hasVisibility(node) {
|
|
7199
|
+
return node.namedChild(0)?.type === "visibility_modifier";
|
|
7200
|
+
}
|
|
7201
|
+
function isInImplBlock(node) {
|
|
7202
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
7203
|
+
if (p.type === "impl_item") return true;
|
|
7204
|
+
if (p.type === "function_item" || p.type === "source_file") return false;
|
|
7205
|
+
}
|
|
7206
|
+
return false;
|
|
7207
|
+
}
|
|
7208
|
+
function findSelfParameter(params) {
|
|
7209
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
7210
|
+
const child = params.namedChild(i);
|
|
7211
|
+
if (child?.type === "self_parameter") return child;
|
|
7212
|
+
}
|
|
7213
|
+
return null;
|
|
7214
|
+
}
|
|
7215
|
+
function isFunctionAttrTest(node) {
|
|
7216
|
+
void node;
|
|
7217
|
+
return false;
|
|
7218
|
+
}
|
|
7219
|
+
|
|
7237
7220
|
// src/engine/visitors/v2-build.ts
|
|
7238
|
-
init_rust();
|
|
7239
7221
|
var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
|
|
7240
7222
|
var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
|
|
7241
7223
|
var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
|
|
@@ -32871,8 +32853,8 @@ function getCorpusBaselines() {
|
|
|
32871
32853
|
|
|
32872
32854
|
// src/rules/ai/comment-ratio.ts
|
|
32873
32855
|
var MIN_FILE_LINES = 20;
|
|
32874
|
-
var FALLBACK_LOW = 0.
|
|
32875
|
-
var FALLBACK_HIGH = 0.
|
|
32856
|
+
var FALLBACK_LOW = 0.02;
|
|
32857
|
+
var FALLBACK_HIGH = 0.55;
|
|
32876
32858
|
var LINE_COMMENT_RE = /^\s*(?:\/\/|#|--|;|%)/;
|
|
32877
32859
|
var BLOCK_COMMENT_OPEN_RE = /\/\*/;
|
|
32878
32860
|
var aiCommentRatioRule = createRule({
|
|
@@ -33001,9 +32983,9 @@ function std(xs) {
|
|
|
33001
32983
|
|
|
33002
32984
|
// src/rules/ai/compression-profile.ts
|
|
33003
32985
|
var MIN_BYTES = 2e3;
|
|
33004
|
-
var GZIP_RATIO_AI_THRESHOLD = 0.
|
|
33005
|
-
var MEAN_LINE_NCD_HUMAN_MAX = 0.
|
|
33006
|
-
var NCD_CV_AI_MAX = 0.
|
|
32986
|
+
var GZIP_RATIO_AI_THRESHOLD = 0.6;
|
|
32987
|
+
var MEAN_LINE_NCD_HUMAN_MAX = 0.25;
|
|
32988
|
+
var NCD_CV_AI_MAX = 0.4;
|
|
33007
32989
|
var aiCompressionProfileRule = createRule({
|
|
33008
32990
|
id: "ai/compression-profile",
|
|
33009
32991
|
category: "ai",
|
|
@@ -33158,6 +33140,13 @@ var aiDefaultReactStackRule = createRule({
|
|
|
33158
33140
|
category: "ai",
|
|
33159
33141
|
severity: "low",
|
|
33160
33142
|
aiSpecific: true,
|
|
33143
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.001
|
|
33144
|
+
// (fires 1 time per 1,000 files). The "≥3 of default stack"
|
|
33145
|
+
// threshold catches almost nothing on real codebases — the
|
|
33146
|
+
// default stack is now ubiquitous, so the signal is near-zero.
|
|
33147
|
+
// Disable until the threshold is lowered or the rule is split
|
|
33148
|
+
// into per-library detectors.
|
|
33149
|
+
defaultOff: true,
|
|
33161
33150
|
description: "File uses \u22653 of the default React stack (Next.js + TanStack Query + Zustand + shadcn/ui) \u2014 Sascha 2025 + Nam et al. MSR 2026",
|
|
33162
33151
|
create(context) {
|
|
33163
33152
|
return context;
|
|
@@ -33410,6 +33399,10 @@ var aiLibraryReinventionRule = createRule({
|
|
|
33410
33399
|
category: "ai",
|
|
33411
33400
|
severity: "medium",
|
|
33412
33401
|
aiSpecific: true,
|
|
33402
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
33403
|
+
// (fires never on the corpus). The "≥2 reinvented patterns"
|
|
33404
|
+
// threshold is too strict. Disable until rewritten.
|
|
33405
|
+
defaultOff: true,
|
|
33413
33406
|
description: "File reinvents \u22652 common patterns (date picker, form validation, chart, modal, etc.) without importing the canonical library \u2014 GitClear 2025 + Cui et al. 2025",
|
|
33414
33407
|
create(context) {
|
|
33415
33408
|
return context;
|
|
@@ -33596,6 +33589,12 @@ var aiMarkdownLeakageRule = createRule({
|
|
|
33596
33589
|
category: "ai",
|
|
33597
33590
|
severity: "high",
|
|
33598
33591
|
aiSpecific: true,
|
|
33592
|
+
// v0.20.0 calibration: v8.5 verdict = INVERTED (lift 65,504× but
|
|
33593
|
+
// recall 0.000 — fires never on the corpus, and when it does fire
|
|
33594
|
+
// it's wrong). The rule was on by default, contributing false
|
|
33595
|
+
// positives to the self-scan. Disable until the rule is rewritten
|
|
33596
|
+
// or the v9 corpus shows it works.
|
|
33597
|
+
defaultOff: true,
|
|
33599
33598
|
description: "Stray Markdown fence or bare language name at top of file \u2014 AI tools leak the fenced-block format into code",
|
|
33600
33599
|
create(context) {
|
|
33601
33600
|
return context;
|
|
@@ -34021,6 +34020,11 @@ var aiTextLikeRatioRule = createRule({
|
|
|
34021
34020
|
category: "ai",
|
|
34022
34021
|
severity: "low",
|
|
34023
34022
|
aiSpecific: true,
|
|
34023
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
34024
|
+
// (fires never on the corpus). The rule is too specific to ever
|
|
34025
|
+
// fire on real code — the prose-detection heuristic is too
|
|
34026
|
+
// conservative. Disable until rewritten.
|
|
34027
|
+
defaultOff: true,
|
|
34024
34028
|
description: "High ratio of prose-like lines embedded in code \u2014 AI tools often leave natural-language explanations in source files (Yotkova 2026)",
|
|
34025
34029
|
create(context) {
|
|
34026
34030
|
return context;
|
|
@@ -35079,125 +35083,17 @@ var unusedParameterRule = createRule({
|
|
|
35079
35083
|
});
|
|
35080
35084
|
|
|
35081
35085
|
// src/rules/docs/broken-link.ts
|
|
35082
|
-
import { existsSync as
|
|
35083
|
-
import { dirname as
|
|
35086
|
+
import { existsSync as existsSync3 } from "fs";
|
|
35087
|
+
import { dirname as dirname3, join as join5, resolve } from "path";
|
|
35084
35088
|
|
|
35085
35089
|
// src/engine/doc-freshness.ts
|
|
35086
|
-
import { readFileSync as
|
|
35087
|
-
import { join as
|
|
35088
|
-
import { globby as globby2 } from "globby";
|
|
35089
|
-
|
|
35090
|
-
// src/mcp/patterns.ts
|
|
35091
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
35092
|
-
import { basename as basename2, extname as extname2 } from "path";
|
|
35093
|
-
|
|
35094
|
-
// src/engine/discover.ts
|
|
35090
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
35091
|
+
import { join as join4, dirname as dirname2, relative } from "path";
|
|
35095
35092
|
import { globby } from "globby";
|
|
35096
|
-
import { minimatch } from "minimatch";
|
|
35097
|
-
import { resolve, extname, relative, sep, basename, dirname as dirname2, join as join3 } from "path";
|
|
35098
|
-
import { readFileSync } from "fs";
|
|
35099
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
|
|
35100
|
-
var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35101
|
-
".py",
|
|
35102
|
-
".go",
|
|
35103
|
-
// v0.14.0
|
|
35104
|
-
".swift",
|
|
35105
|
-
".kt",
|
|
35106
|
-
".kts",
|
|
35107
|
-
".dart",
|
|
35108
|
-
".rs",
|
|
35109
|
-
".cpp",
|
|
35110
|
-
".cc",
|
|
35111
|
-
".cxx",
|
|
35112
|
-
".c",
|
|
35113
|
-
".h",
|
|
35114
|
-
".hpp",
|
|
35115
|
-
".hxx",
|
|
35116
|
-
".java",
|
|
35117
|
-
".rb",
|
|
35118
|
-
".php"
|
|
35119
|
-
]);
|
|
35120
|
-
var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35121
|
-
...SOURCE_EXTENSIONS,
|
|
35122
|
-
...BACKEND_EXTENSIONS
|
|
35123
|
-
]);
|
|
35124
|
-
|
|
35125
|
-
// src/config/conventions.ts
|
|
35126
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
35127
|
-
import { join as join4 } from "path";
|
|
35128
|
-
|
|
35129
|
-
// src/mcp/patterns.ts
|
|
35130
|
-
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;
|
|
35131
|
-
var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35132
|
-
var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35133
|
-
function extractImports(source) {
|
|
35134
|
-
const seen = /* @__PURE__ */ new Set();
|
|
35135
|
-
const out = [];
|
|
35136
|
-
const push = (spec) => {
|
|
35137
|
-
if (spec.startsWith(".") || spec.startsWith("/")) return;
|
|
35138
|
-
if (seen.has(spec)) return;
|
|
35139
|
-
seen.add(spec);
|
|
35140
|
-
out.push(spec);
|
|
35141
|
-
};
|
|
35142
|
-
for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
|
|
35143
|
-
re.lastIndex = 0;
|
|
35144
|
-
let m;
|
|
35145
|
-
while ((m = re.exec(source)) !== null) {
|
|
35146
|
-
push(m[2]);
|
|
35147
|
-
}
|
|
35148
|
-
}
|
|
35149
|
-
return out;
|
|
35150
|
-
}
|
|
35151
|
-
|
|
35152
|
-
// src/rules/docs/expired-code-example.ts
|
|
35153
|
-
var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
|
|
35154
|
-
function stripSubpath(spec) {
|
|
35155
|
-
if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
|
|
35156
|
-
return spec.split("/")[0] ?? spec;
|
|
35157
|
-
}
|
|
35158
|
-
var expiredCodeExampleRule = createRule({
|
|
35159
|
-
id: "docs/expired-code-example",
|
|
35160
|
-
category: "docs",
|
|
35161
|
-
severity: "medium",
|
|
35162
|
-
aiSpecific: false,
|
|
35163
|
-
description: "A fenced code example imports a package that is not declared in package.json.",
|
|
35164
|
-
create(context) {
|
|
35165
|
-
return { ...context, packages: declaredPackages(context.cwd) };
|
|
35166
|
-
},
|
|
35167
|
-
analyze(context, facts) {
|
|
35168
|
-
const issues = [];
|
|
35169
|
-
const source = facts.v2?._source;
|
|
35170
|
-
if (!source) return issues;
|
|
35171
|
-
const packages = declaredPackages(context.cwd);
|
|
35172
|
-
const packageName = context.packageName;
|
|
35173
|
-
if (packageName) packages.add(packageName);
|
|
35174
|
-
const blocks = extractFencedCodeBlocks(source);
|
|
35175
|
-
for (const block of blocks) {
|
|
35176
|
-
if (!CODE_LANGS.has(block.lang)) continue;
|
|
35177
|
-
if (block.body.split("\n").length < 2) continue;
|
|
35178
|
-
const imports = extractImports(block.body);
|
|
35179
|
-
for (const imp of imports) {
|
|
35180
|
-
const pkgName = stripSubpath(imp);
|
|
35181
|
-
if (packages.has(pkgName)) continue;
|
|
35182
|
-
issues.push({
|
|
35183
|
-
ruleId: "docs/expired-code-example",
|
|
35184
|
-
category: "docs",
|
|
35185
|
-
severity: "medium",
|
|
35186
|
-
aiSpecific: false,
|
|
35187
|
-
message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
|
|
35188
|
-
line: block.line,
|
|
35189
|
-
column: block.column,
|
|
35190
|
-
advice: `Add \`${pkgName}\` to package.json or update the example.`
|
|
35191
|
-
});
|
|
35192
|
-
}
|
|
35193
|
-
}
|
|
35194
|
-
return issues;
|
|
35195
|
-
}
|
|
35196
|
-
});
|
|
35197
35093
|
|
|
35198
35094
|
// src/rules/docs/stale-function-reference.ts
|
|
35199
|
-
import { readFileSync
|
|
35200
|
-
import { join as
|
|
35095
|
+
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
35096
|
+
import { join as join3, extname } from "path";
|
|
35201
35097
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
35202
35098
|
// JS reserved words
|
|
35203
35099
|
"true",
|
|
@@ -35770,7 +35666,7 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
|
|
|
35770
35666
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
35771
35667
|
var CAP = 200;
|
|
35772
35668
|
function walk(dir, out, cap) {
|
|
35773
|
-
if (!
|
|
35669
|
+
if (!existsSync(dir) || out.length >= cap) return;
|
|
35774
35670
|
let entries;
|
|
35775
35671
|
try {
|
|
35776
35672
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -35780,19 +35676,19 @@ function walk(dir, out, cap) {
|
|
|
35780
35676
|
for (const entry of entries) {
|
|
35781
35677
|
if (out.length >= cap) return;
|
|
35782
35678
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
35783
|
-
const full =
|
|
35679
|
+
const full = join3(dir, entry.name);
|
|
35784
35680
|
if (entry.isDirectory()) walk(full, out, cap);
|
|
35785
|
-
else if (entry.isFile() && SOURCE_EXTS.has(
|
|
35681
|
+
else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name))) out.push(full);
|
|
35786
35682
|
}
|
|
35787
35683
|
}
|
|
35788
35684
|
function collectExports(cwd) {
|
|
35789
35685
|
const out = /* @__PURE__ */ new Set();
|
|
35790
35686
|
const files = [];
|
|
35791
|
-
for (const root of SOURCE_ROOTS) walk(
|
|
35687
|
+
for (const root of SOURCE_ROOTS) walk(join3(cwd, root), files, CAP);
|
|
35792
35688
|
for (const file of files) {
|
|
35793
35689
|
let source;
|
|
35794
35690
|
try {
|
|
35795
|
-
source =
|
|
35691
|
+
source = readFileSync(file, "utf-8");
|
|
35796
35692
|
} catch {
|
|
35797
35693
|
continue;
|
|
35798
35694
|
}
|
|
@@ -35805,7 +35701,7 @@ function collectExports(cwd) {
|
|
|
35805
35701
|
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
|
|
35806
35702
|
// v0.18.6: also collect field names from `export interface` and
|
|
35807
35703
|
// `export type` declarations. Without this, fields like
|
|
35808
|
-
// `crossFileDrift`, `
|
|
35704
|
+
// `crossFileDrift`, `aiSlopScore`, `engineeringHygiene` are
|
|
35809
35705
|
// flagged as stale even though they're valid type fields.
|
|
35810
35706
|
/^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
|
|
35811
35707
|
]) {
|
|
@@ -36055,38 +35951,6 @@ function extractInlineCodeSpans(source) {
|
|
|
36055
35951
|
}
|
|
36056
35952
|
return hits;
|
|
36057
35953
|
}
|
|
36058
|
-
function extractFencedCodeBlocks(source) {
|
|
36059
|
-
const blocks = [];
|
|
36060
|
-
const lines = source.split("\n");
|
|
36061
|
-
let i = 0;
|
|
36062
|
-
while (i < lines.length) {
|
|
36063
|
-
const line = lines[i] ?? "";
|
|
36064
|
-
const fenceMatch = /^```(\w*)\s*$/.exec(line);
|
|
36065
|
-
if (!fenceMatch) {
|
|
36066
|
-
i++;
|
|
36067
|
-
continue;
|
|
36068
|
-
}
|
|
36069
|
-
const lang = fenceMatch[1] ?? "";
|
|
36070
|
-
const startLine = i + 1;
|
|
36071
|
-
const bodyLines = [];
|
|
36072
|
-
i++;
|
|
36073
|
-
while (i < lines.length) {
|
|
36074
|
-
if (/^```\s*$/.test(lines[i] ?? "")) {
|
|
36075
|
-
i++;
|
|
36076
|
-
break;
|
|
36077
|
-
}
|
|
36078
|
-
bodyLines.push(lines[i] ?? "");
|
|
36079
|
-
i++;
|
|
36080
|
-
}
|
|
36081
|
-
blocks.push({
|
|
36082
|
-
lang,
|
|
36083
|
-
body: bodyLines.join("\n"),
|
|
36084
|
-
line: startLine,
|
|
36085
|
-
column: 1
|
|
36086
|
-
});
|
|
36087
|
-
}
|
|
36088
|
-
return blocks;
|
|
36089
|
-
}
|
|
36090
35954
|
function extractMarkdownLinks(source) {
|
|
36091
35955
|
const hits = [];
|
|
36092
35956
|
const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
@@ -36103,10 +35967,10 @@ function extractMarkdownLinks(source) {
|
|
|
36103
35967
|
}
|
|
36104
35968
|
function declaredPackages(cwd) {
|
|
36105
35969
|
const out = /* @__PURE__ */ new Set();
|
|
36106
|
-
const pkgPath =
|
|
36107
|
-
if (!
|
|
35970
|
+
const pkgPath = join4(cwd, "package.json");
|
|
35971
|
+
if (!existsSync2(pkgPath)) return out;
|
|
36108
35972
|
try {
|
|
36109
|
-
const raw =
|
|
35973
|
+
const raw = readFileSync2(pkgPath, "utf-8");
|
|
36110
35974
|
const pkg = JSON.parse(raw);
|
|
36111
35975
|
for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
36112
35976
|
const v = pkg[k];
|
|
@@ -36136,7 +36000,7 @@ var brokenLinkRule = createRule({
|
|
|
36136
36000
|
const source = facts.v2?._source;
|
|
36137
36001
|
if (!source) return issues;
|
|
36138
36002
|
const links = extractMarkdownLinks(source);
|
|
36139
|
-
const docDir =
|
|
36003
|
+
const docDir = dirname3(resolve(context.cwd, context.filePath));
|
|
36140
36004
|
for (const link of links) {
|
|
36141
36005
|
const target = link.target;
|
|
36142
36006
|
if (target.startsWith("http://") || target.startsWith("https://")) continue;
|
|
@@ -36146,8 +36010,8 @@ var brokenLinkRule = createRule({
|
|
|
36146
36010
|
if (target.startsWith("/")) continue;
|
|
36147
36011
|
const filePart = target.split("#")[0] ?? target;
|
|
36148
36012
|
if (filePart === "") continue;
|
|
36149
|
-
const resolved =
|
|
36150
|
-
if (
|
|
36013
|
+
const resolved = join5(docDir, filePart);
|
|
36014
|
+
if (existsSync3(resolved)) continue;
|
|
36151
36015
|
issues.push({
|
|
36152
36016
|
ruleId: "docs/broken-link",
|
|
36153
36017
|
category: "docs",
|
|
@@ -36376,10 +36240,261 @@ var goStructTagInconsistencyRule = createRule({
|
|
|
36376
36240
|
}
|
|
36377
36241
|
});
|
|
36378
36242
|
|
|
36243
|
+
// src/rules/java/arraylist-vs-linkedlist.ts
|
|
36244
|
+
var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
|
|
36245
|
+
var javaArraylistVsLinkedlistRule = createRule({
|
|
36246
|
+
id: "java/arraylist-vs-linkedlist",
|
|
36247
|
+
category: "typo",
|
|
36248
|
+
severity: "low",
|
|
36249
|
+
aiSpecific: true,
|
|
36250
|
+
description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
|
|
36251
|
+
create(_context) {
|
|
36252
|
+
return {};
|
|
36253
|
+
},
|
|
36254
|
+
analyze(_context, facts) {
|
|
36255
|
+
const issues = [];
|
|
36256
|
+
const source = facts.v2?._source;
|
|
36257
|
+
if (!source) return issues;
|
|
36258
|
+
let m;
|
|
36259
|
+
NEW_LINKED_LIST_REGEX.lastIndex = 0;
|
|
36260
|
+
while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
|
|
36261
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36262
|
+
issues.push({
|
|
36263
|
+
ruleId: "java/arraylist-vs-linkedlist",
|
|
36264
|
+
category: "typo",
|
|
36265
|
+
severity: "low",
|
|
36266
|
+
aiSpecific: true,
|
|
36267
|
+
message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
|
|
36268
|
+
line,
|
|
36269
|
+
column: 1,
|
|
36270
|
+
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."
|
|
36271
|
+
});
|
|
36272
|
+
}
|
|
36273
|
+
return issues;
|
|
36274
|
+
}
|
|
36275
|
+
});
|
|
36276
|
+
|
|
36277
|
+
// src/rules/java/empty-catch-block.ts
|
|
36278
|
+
var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
36279
|
+
var javaEmptyCatchBlockRule = createRule({
|
|
36280
|
+
id: "java/empty-catch-block",
|
|
36281
|
+
category: "logic",
|
|
36282
|
+
severity: "medium",
|
|
36283
|
+
aiSpecific: true,
|
|
36284
|
+
description: "Empty catch block \u2014 silently swallows exceptions",
|
|
36285
|
+
create(_context) {
|
|
36286
|
+
return {};
|
|
36287
|
+
},
|
|
36288
|
+
analyze(_context, facts) {
|
|
36289
|
+
const issues = [];
|
|
36290
|
+
const source = facts.v2?._source;
|
|
36291
|
+
if (!source) return issues;
|
|
36292
|
+
let m;
|
|
36293
|
+
SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
|
|
36294
|
+
while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
|
|
36295
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36296
|
+
issues.push({
|
|
36297
|
+
ruleId: "java/empty-catch-block",
|
|
36298
|
+
category: "logic",
|
|
36299
|
+
severity: "medium",
|
|
36300
|
+
aiSpecific: true,
|
|
36301
|
+
message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
|
|
36302
|
+
line,
|
|
36303
|
+
column: 1,
|
|
36304
|
+
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.'
|
|
36305
|
+
});
|
|
36306
|
+
}
|
|
36307
|
+
return issues;
|
|
36308
|
+
}
|
|
36309
|
+
});
|
|
36310
|
+
|
|
36311
|
+
// src/rules/java/legacy-date-api.ts
|
|
36312
|
+
var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
|
|
36313
|
+
var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
|
|
36314
|
+
var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
|
|
36315
|
+
var javaLegacyDateApiRule = createRule({
|
|
36316
|
+
id: "java/legacy-date-api",
|
|
36317
|
+
category: "typo",
|
|
36318
|
+
severity: "low",
|
|
36319
|
+
aiSpecific: true,
|
|
36320
|
+
description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
|
|
36321
|
+
create(_context) {
|
|
36322
|
+
return {};
|
|
36323
|
+
},
|
|
36324
|
+
analyze(_context, facts) {
|
|
36325
|
+
const issues = [];
|
|
36326
|
+
const source = facts.v2?._source;
|
|
36327
|
+
if (!source) return issues;
|
|
36328
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
36329
|
+
let m;
|
|
36330
|
+
LEGACY_IMPORT_REGEX.lastIndex = 0;
|
|
36331
|
+
while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
|
|
36332
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36333
|
+
flagged.add(line);
|
|
36334
|
+
issues.push({
|
|
36335
|
+
ruleId: "java/legacy-date-api",
|
|
36336
|
+
category: "typo",
|
|
36337
|
+
severity: "low",
|
|
36338
|
+
aiSpecific: true,
|
|
36339
|
+
message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
|
|
36340
|
+
line,
|
|
36341
|
+
column: 1,
|
|
36342
|
+
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."
|
|
36343
|
+
});
|
|
36344
|
+
}
|
|
36345
|
+
LEGACY_USAGE_REGEX.lastIndex = 0;
|
|
36346
|
+
while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
|
|
36347
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36348
|
+
if (flagged.has(line)) continue;
|
|
36349
|
+
issues.push({
|
|
36350
|
+
ruleId: "java/legacy-date-api",
|
|
36351
|
+
category: "typo",
|
|
36352
|
+
severity: "low",
|
|
36353
|
+
aiSpecific: true,
|
|
36354
|
+
message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
|
|
36355
|
+
line,
|
|
36356
|
+
column: 1,
|
|
36357
|
+
advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36358
|
+
});
|
|
36359
|
+
}
|
|
36360
|
+
CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
|
|
36361
|
+
while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
|
|
36362
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36363
|
+
if (flagged.has(line)) continue;
|
|
36364
|
+
issues.push({
|
|
36365
|
+
ruleId: "java/legacy-date-api",
|
|
36366
|
+
category: "typo",
|
|
36367
|
+
severity: "low",
|
|
36368
|
+
aiSpecific: true,
|
|
36369
|
+
message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
|
|
36370
|
+
line,
|
|
36371
|
+
column: 1,
|
|
36372
|
+
advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36373
|
+
});
|
|
36374
|
+
}
|
|
36375
|
+
return issues;
|
|
36376
|
+
}
|
|
36377
|
+
});
|
|
36378
|
+
|
|
36379
|
+
// src/rules/java/raw-type-overuse.ts
|
|
36380
|
+
var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
|
|
36381
|
+
var javaRawTypeOveruseRule = createRule({
|
|
36382
|
+
id: "java/raw-type-overuse",
|
|
36383
|
+
category: "typo",
|
|
36384
|
+
severity: "low",
|
|
36385
|
+
aiSpecific: true,
|
|
36386
|
+
description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
|
|
36387
|
+
create(_context) {
|
|
36388
|
+
return {};
|
|
36389
|
+
},
|
|
36390
|
+
analyze(_context, facts) {
|
|
36391
|
+
const issues = [];
|
|
36392
|
+
const source = facts.v2?._source;
|
|
36393
|
+
if (!source) return issues;
|
|
36394
|
+
let m;
|
|
36395
|
+
RAW_TYPE_REGEX.lastIndex = 0;
|
|
36396
|
+
while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
|
|
36397
|
+
const typeName = m[1];
|
|
36398
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36399
|
+
issues.push({
|
|
36400
|
+
ruleId: "java/raw-type-overuse",
|
|
36401
|
+
category: "typo",
|
|
36402
|
+
severity: "low",
|
|
36403
|
+
aiSpecific: true,
|
|
36404
|
+
message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
|
|
36405
|
+
line,
|
|
36406
|
+
column: 1,
|
|
36407
|
+
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.`
|
|
36408
|
+
});
|
|
36409
|
+
}
|
|
36410
|
+
return issues;
|
|
36411
|
+
}
|
|
36412
|
+
});
|
|
36413
|
+
|
|
36414
|
+
// src/rules/java/string-concat-loop.ts
|
|
36415
|
+
var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
|
|
36416
|
+
var javaStringConcatLoopRule = createRule({
|
|
36417
|
+
id: "java/string-concat-loop",
|
|
36418
|
+
category: "perf",
|
|
36419
|
+
severity: "low",
|
|
36420
|
+
aiSpecific: true,
|
|
36421
|
+
description: "String concatenation in a loop \u2014 use StringBuilder",
|
|
36422
|
+
create(_context) {
|
|
36423
|
+
return {};
|
|
36424
|
+
},
|
|
36425
|
+
analyze(_context, facts) {
|
|
36426
|
+
const issues = [];
|
|
36427
|
+
const source = facts.v2?._source;
|
|
36428
|
+
if (!source) return issues;
|
|
36429
|
+
if (!/\b(for|while|do)\b/.test(source)) return issues;
|
|
36430
|
+
let m;
|
|
36431
|
+
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
36432
|
+
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
36433
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36434
|
+
issues.push({
|
|
36435
|
+
ruleId: "java/string-concat-loop",
|
|
36436
|
+
category: "perf",
|
|
36437
|
+
severity: "low",
|
|
36438
|
+
aiSpecific: true,
|
|
36439
|
+
message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
|
|
36440
|
+
line,
|
|
36441
|
+
column: 1,
|
|
36442
|
+
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."
|
|
36443
|
+
});
|
|
36444
|
+
}
|
|
36445
|
+
return issues;
|
|
36446
|
+
}
|
|
36447
|
+
});
|
|
36448
|
+
|
|
36449
|
+
// src/rules/java/system-out-println.ts
|
|
36450
|
+
var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
|
|
36451
|
+
var DEFAULT_THRESHOLD = 1;
|
|
36452
|
+
var javaSystemOutPrintlnRule = createRule({
|
|
36453
|
+
id: "java/system-out-println",
|
|
36454
|
+
category: "typo",
|
|
36455
|
+
severity: "low",
|
|
36456
|
+
aiSpecific: true,
|
|
36457
|
+
description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
|
|
36458
|
+
create(_context) {
|
|
36459
|
+
return { threshold: DEFAULT_THRESHOLD };
|
|
36460
|
+
},
|
|
36461
|
+
analyze(context, facts) {
|
|
36462
|
+
const issues = [];
|
|
36463
|
+
const source = facts.v2?._source;
|
|
36464
|
+
if (!source) return issues;
|
|
36465
|
+
const matches = [];
|
|
36466
|
+
let m;
|
|
36467
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
36468
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
36469
|
+
matches.push(m.index);
|
|
36470
|
+
}
|
|
36471
|
+
if (matches.length <= context.threshold) return issues;
|
|
36472
|
+
const cap = Math.min(matches.length, 10);
|
|
36473
|
+
for (let i = 0; i < cap; i++) {
|
|
36474
|
+
const idx = matches[i];
|
|
36475
|
+
const line = source.slice(0, idx).split("\n").length;
|
|
36476
|
+
issues.push({
|
|
36477
|
+
ruleId: "java/system-out-println",
|
|
36478
|
+
category: "typo",
|
|
36479
|
+
severity: "low",
|
|
36480
|
+
aiSpecific: true,
|
|
36481
|
+
message: `System.out.println at line ${line} \u2014 use a logger for production output`,
|
|
36482
|
+
line,
|
|
36483
|
+
column: 1,
|
|
36484
|
+
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."
|
|
36485
|
+
});
|
|
36486
|
+
}
|
|
36487
|
+
return issues;
|
|
36488
|
+
}
|
|
36489
|
+
});
|
|
36490
|
+
|
|
36379
36491
|
// src/rules/layout/gap-monopoly.ts
|
|
36380
36492
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36381
36493
|
var gapMonopolyRule = createRule({
|
|
36382
36494
|
id: "layout/gap-monopoly",
|
|
36495
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
36496
|
+
// until rewritten.
|
|
36497
|
+
defaultOff: true,
|
|
36383
36498
|
category: "layout",
|
|
36384
36499
|
severity: "medium",
|
|
36385
36500
|
aiSpecific: false,
|
|
@@ -36476,6 +36591,9 @@ var mathElementUniformityRule = createRule({
|
|
|
36476
36591
|
var GRID_COLS_RE = /\bgrid-cols-(\d+)\b/g;
|
|
36477
36592
|
var mathGridUniformityRule = createRule({
|
|
36478
36593
|
id: "layout/math-grid-uniformity",
|
|
36594
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
36595
|
+
// until rewritten.
|
|
36596
|
+
defaultOff: true,
|
|
36479
36597
|
category: "layout",
|
|
36480
36598
|
severity: "high",
|
|
36481
36599
|
aiSpecific: true,
|
|
@@ -36728,16 +36846,16 @@ var DEFAULT_CONFIG = {
|
|
|
36728
36846
|
};
|
|
36729
36847
|
|
|
36730
36848
|
// src/config/detect/monorepo.ts
|
|
36731
|
-
import { existsSync as
|
|
36732
|
-
import { dirname as
|
|
36849
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
36850
|
+
import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
|
|
36733
36851
|
|
|
36734
36852
|
// src/config/detect/styling.ts
|
|
36735
|
-
import { existsSync as
|
|
36736
|
-
import { join as
|
|
36853
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
36854
|
+
import { join as join7, resolve as resolve3 } from "path";
|
|
36737
36855
|
|
|
36738
36856
|
// src/config/detect/stack.ts
|
|
36739
|
-
import { existsSync as
|
|
36740
|
-
import { extname as
|
|
36857
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
36858
|
+
import { extname as extname2, join as join8, resolve as resolve4 } from "path";
|
|
36741
36859
|
|
|
36742
36860
|
// src/config/presets.ts
|
|
36743
36861
|
var REACT_ONLY_RULES = {
|
|
@@ -36786,8 +36904,8 @@ var FRAMEWORK_PRESETS = {
|
|
|
36786
36904
|
};
|
|
36787
36905
|
|
|
36788
36906
|
// src/config/load.ts
|
|
36789
|
-
import { existsSync as existsSync8, readFileSync as
|
|
36790
|
-
import { dirname as
|
|
36907
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
36908
|
+
import { dirname as dirname5, extname as extname3, join as join10, resolve as resolve5 } from "path";
|
|
36791
36909
|
import { createRequire } from "module";
|
|
36792
36910
|
|
|
36793
36911
|
// src/engine/logger.ts
|
|
@@ -36809,6 +36927,10 @@ function setLoggerQuiet(quiet) {
|
|
|
36809
36927
|
logger = createLogger(quiet);
|
|
36810
36928
|
}
|
|
36811
36929
|
|
|
36930
|
+
// src/config/conventions.ts
|
|
36931
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
36932
|
+
import { join as join9 } from "path";
|
|
36933
|
+
|
|
36812
36934
|
// src/config/init.ts
|
|
36813
36935
|
var STRICTNESS_PRESETS = {
|
|
36814
36936
|
strict: {
|
|
@@ -36923,6 +37045,9 @@ function checkInlineValue(value, scale) {
|
|
|
36923
37045
|
}
|
|
36924
37046
|
var spacingGridRule = createRule({
|
|
36925
37047
|
id: "layout/spacing-grid",
|
|
37048
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
37049
|
+
// until rewritten.
|
|
37050
|
+
defaultOff: true,
|
|
36926
37051
|
category: "layout",
|
|
36927
37052
|
severity: "medium",
|
|
36928
37053
|
aiSpecific: false,
|
|
@@ -37032,15 +37157,9 @@ var boundaryViolationRule = createRule({
|
|
|
37032
37157
|
if (context.supportsRsc === false) return [];
|
|
37033
37158
|
if (!facts.v2) return [];
|
|
37034
37159
|
const issues = [];
|
|
37035
|
-
const hookLines = /* @__PURE__ */ new Map();
|
|
37036
|
-
for (const h of facts.v2.logic.hooks) {
|
|
37037
|
-
const arr = hookLines.get(h.name) ?? [];
|
|
37038
|
-
arr.push({ line: h.line, column: h.column });
|
|
37039
|
-
hookLines.set(h.name, arr);
|
|
37040
|
-
}
|
|
37041
37160
|
for (const component of facts.v2.components) {
|
|
37042
37161
|
if (!component.isServerComponent) continue;
|
|
37043
|
-
for (const hook of
|
|
37162
|
+
for (const hook of component.hookCalls) {
|
|
37044
37163
|
if (!context.clientHooks.has(hook.name)) continue;
|
|
37045
37164
|
issues.push({
|
|
37046
37165
|
ruleId: "logic/boundary-violation",
|
|
@@ -37048,8 +37167,8 @@ var boundaryViolationRule = createRule({
|
|
|
37048
37167
|
severity: "high",
|
|
37049
37168
|
aiSpecific: true,
|
|
37050
37169
|
message: `'${hook.name}' only works in interactive client components. This component runs on the server.`,
|
|
37051
|
-
line:
|
|
37052
|
-
column:
|
|
37170
|
+
line: hook.line,
|
|
37171
|
+
column: hook.column,
|
|
37053
37172
|
advice: "Add the 'use client' directive at the top of this file, or move the stateful part to a separate client component.",
|
|
37054
37173
|
fix: {
|
|
37055
37174
|
kind: "insert",
|
|
@@ -38174,7 +38293,6 @@ function scanForStringlyParams(paramText) {
|
|
|
38174
38293
|
}
|
|
38175
38294
|
|
|
38176
38295
|
// src/rules/rust/todo-macro.ts
|
|
38177
|
-
init_parser_rust();
|
|
38178
38296
|
var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
|
|
38179
38297
|
var rustTodoMacroRule = createRule({
|
|
38180
38298
|
id: "rust/todo-macro",
|
|
@@ -38310,7 +38428,6 @@ function collectReferencedNames(source) {
|
|
|
38310
38428
|
}
|
|
38311
38429
|
|
|
38312
38430
|
// src/rules/rust/unwrap-in-production.ts
|
|
38313
|
-
init_parser_rust();
|
|
38314
38431
|
var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
|
|
38315
38432
|
var rustUnwrapInProductionRule = createRule({
|
|
38316
38433
|
id: "rust/unwrap-in-production",
|
|
@@ -39447,7 +39564,7 @@ function looksRealistic(value) {
|
|
|
39447
39564
|
|
|
39448
39565
|
// src/rules/test/missing-edge-case.ts
|
|
39449
39566
|
import { parseSync as parseSync2 } from "@swc/core";
|
|
39450
|
-
import { existsSync as existsSync9, readFileSync as
|
|
39567
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
39451
39568
|
var MAX_PER_FILE = 20;
|
|
39452
39569
|
var missingEdgeCaseRule = createRule({
|
|
39453
39570
|
id: "test/missing-edge-case",
|
|
@@ -39469,7 +39586,7 @@ var missingEdgeCaseRule = createRule({
|
|
|
39469
39586
|
const testFileSources = /* @__PURE__ */ new Map();
|
|
39470
39587
|
for (const testFile of discoverTestFiles(cwd)) {
|
|
39471
39588
|
try {
|
|
39472
|
-
testFileSources.set(testFile,
|
|
39589
|
+
testFileSources.set(testFile, readFileSync8(testFile, "utf-8"));
|
|
39473
39590
|
} catch {
|
|
39474
39591
|
}
|
|
39475
39592
|
}
|
|
@@ -39835,7 +39952,12 @@ var WEAK_MATCHERS = /* @__PURE__ */ new Set([
|
|
|
39835
39952
|
"toBeUndefined",
|
|
39836
39953
|
"toBeTruthy",
|
|
39837
39954
|
"toBeFalsy",
|
|
39838
|
-
"toBeNull"
|
|
39955
|
+
"toBeNull",
|
|
39956
|
+
"toBeGreaterThan",
|
|
39957
|
+
"toBeLessThan",
|
|
39958
|
+
"toContain",
|
|
39959
|
+
"toHaveLength",
|
|
39960
|
+
"toBeInstanceOf"
|
|
39839
39961
|
]);
|
|
39840
39962
|
function isTautologicalAssertion(hit) {
|
|
39841
39963
|
if (hit.matcherArg && hit.expectArg && hit.expectArg === hit.matcherArg) return true;
|
|
@@ -40676,6 +40798,10 @@ function rgbToHue([r, g, b]) {
|
|
|
40676
40798
|
}
|
|
40677
40799
|
var mathColorClusterRule = createRule({
|
|
40678
40800
|
id: "visual/math-color-cluster",
|
|
40801
|
+
// v0.20.0 calibration: recall 0.000, fires never. The
|
|
40802
|
+
// "math-color cluster" heuristic is too specific to ever fire
|
|
40803
|
+
// on real code. Disable until rewritten.
|
|
40804
|
+
defaultOff: true,
|
|
40679
40805
|
category: "visual",
|
|
40680
40806
|
severity: "high",
|
|
40681
40807
|
aiSpecific: true,
|
|
@@ -40730,6 +40856,9 @@ var FONT_IMPORT_HINT_RE = /next\/font|@import\s+url|fonts\.googleapis|fonts\.gst
|
|
|
40730
40856
|
var NEXT_FONT_IMPORT_RE = /import\s+.*from\s+['"]next\/font\/(?:google|local)['"]/;
|
|
40731
40857
|
var mathDefaultFontRule = createRule({
|
|
40732
40858
|
id: "visual/math-default-font",
|
|
40859
|
+
// v0.20.0 calibration: recall 0.001, fires 1 per 1,000 files.
|
|
40860
|
+
// Too specific to be useful. Disable until rewritten.
|
|
40861
|
+
defaultOff: true,
|
|
40733
40862
|
category: "visual",
|
|
40734
40863
|
severity: "high",
|
|
40735
40864
|
aiSpecific: true,
|
|
@@ -40796,6 +40925,9 @@ var TEXT_SIZE_MAP = {
|
|
|
40796
40925
|
var TEXT_SIZE_RE = /\btext-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)\b/g;
|
|
40797
40926
|
var mathFontEntropyRule = createRule({
|
|
40798
40927
|
id: "visual/math-font-entropy",
|
|
40928
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
40929
|
+
// (verified). Disable until rewritten.
|
|
40930
|
+
defaultOff: true,
|
|
40799
40931
|
category: "visual",
|
|
40800
40932
|
severity: "high",
|
|
40801
40933
|
aiSpecific: true,
|
|
@@ -40918,6 +41050,10 @@ var ROUNDED_MAP = {
|
|
|
40918
41050
|
var ROUNDED_RE = /\brounded(?:-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?\b/g;
|
|
40919
41051
|
var mathRoundedEntropyRule = createRule({
|
|
40920
41052
|
id: "visual/math-rounded-entropy",
|
|
41053
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
41054
|
+
// (verified). The entropy heuristic is too specific to ever fire
|
|
41055
|
+
// on real code. Disable until rewritten.
|
|
41056
|
+
defaultOff: true,
|
|
40921
41057
|
category: "visual",
|
|
40922
41058
|
severity: "high",
|
|
40923
41059
|
aiSpecific: true,
|
|
@@ -40958,6 +41094,9 @@ var mathRoundedEntropyRule = createRule({
|
|
|
40958
41094
|
var SPACING_PREFIX_RE2 = /\b(p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap|gap-x|gap-y)-(\d+)\b/g;
|
|
40959
41095
|
var mathSpacingEntropyRule = createRule({
|
|
40960
41096
|
id: "visual/math-spacing-entropy",
|
|
41097
|
+
// v0.20.0 calibration: recall 0.002, fires 0 times on self-scan
|
|
41098
|
+
// (verified). Disable until rewritten.
|
|
41099
|
+
defaultOff: true,
|
|
40961
41100
|
category: "visual",
|
|
40962
41101
|
severity: "medium",
|
|
40963
41102
|
aiSpecific: true,
|
|
@@ -40995,7 +41134,7 @@ var mathSpacingEntropyRule = createRule({
|
|
|
40995
41134
|
|
|
40996
41135
|
// src/rules/visual/naturalness-anomaly.ts
|
|
40997
41136
|
var MIN_LENGTH = 50;
|
|
40998
|
-
var DISTINCT_RATIO_FLOOR = 0.
|
|
41137
|
+
var DISTINCT_RATIO_FLOOR = 0.2;
|
|
40999
41138
|
var naturalnessAnomalyRule = createRule({
|
|
41000
41139
|
id: "visual/naturalness-anomaly",
|
|
41001
41140
|
category: "visual",
|
|
@@ -41040,6 +41179,9 @@ var naturalnessAnomalyRule = createRule({
|
|
|
41040
41179
|
var SCALE_TOLERANCE = 1e-3;
|
|
41041
41180
|
var radiusScaleViolationRule = createRule({
|
|
41042
41181
|
id: "visual/radius-scale-violation",
|
|
41182
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
41183
|
+
// until rewritten.
|
|
41184
|
+
defaultOff: true,
|
|
41043
41185
|
category: "visual",
|
|
41044
41186
|
severity: "medium",
|
|
41045
41187
|
aiSpecific: false,
|
|
@@ -41224,6 +41366,10 @@ var ALT_HANDLER_RE = /^(?:onClick|onPointerDown|onKey(?:Down|Up|Press))$/;
|
|
|
41224
41366
|
var ALT_ROLE_RE = /^(?:button|application|tab|menuitem)$/;
|
|
41225
41367
|
var draggingMovementsRule = createRule({
|
|
41226
41368
|
id: "wcag/dragging-movements",
|
|
41369
|
+
// v0.20.0 calibration: v8.5 = DORMANT but defaultOff: false
|
|
41370
|
+
// was a bug — DORMANT rules should be off by default. The
|
|
41371
|
+
// rule was contributing 0 useful fires. Disable.
|
|
41372
|
+
defaultOff: true,
|
|
41227
41373
|
category: "wcag",
|
|
41228
41374
|
severity: "medium",
|
|
41229
41375
|
aiSpecific: false,
|
|
@@ -41370,6 +41516,11 @@ function scanForMissingAlt(source) {
|
|
|
41370
41516
|
}
|
|
41371
41517
|
var missingAltRule = createRule({
|
|
41372
41518
|
id: "wcag/missing-alt",
|
|
41519
|
+
// v0.20.0 calibration: lift 1.0, recall 0.000. Lift of 1.0
|
|
41520
|
+
// means the rule has zero discriminative power — it fires
|
|
41521
|
+
// equally on positive and negative examples. Dead rule.
|
|
41522
|
+
// Disable until rewritten.
|
|
41523
|
+
defaultOff: true,
|
|
41373
41524
|
category: "wcag",
|
|
41374
41525
|
severity: "medium",
|
|
41375
41526
|
aiSpecific: false,
|
|
@@ -41590,13 +41741,18 @@ var builtinRules = [
|
|
|
41590
41741
|
unusedLocalRule,
|
|
41591
41742
|
unusedParameterRule,
|
|
41592
41743
|
brokenLinkRule,
|
|
41593
|
-
expiredCodeExampleRule,
|
|
41594
41744
|
staleFunctionReferenceRule,
|
|
41595
41745
|
stalePackageReferenceRule,
|
|
41596
41746
|
dupIdenticalBlockRule,
|
|
41597
41747
|
goErrorWrapWithoutContextRule,
|
|
41598
41748
|
goNilSliceVsEmptyRule,
|
|
41599
41749
|
goStructTagInconsistencyRule,
|
|
41750
|
+
javaArraylistVsLinkedlistRule,
|
|
41751
|
+
javaEmptyCatchBlockRule,
|
|
41752
|
+
javaLegacyDateApiRule,
|
|
41753
|
+
javaRawTypeOveruseRule,
|
|
41754
|
+
javaStringConcatLoopRule,
|
|
41755
|
+
javaSystemOutPrintlnRule,
|
|
41600
41756
|
gapMonopolyRule,
|
|
41601
41757
|
mathElementUniformityRule,
|
|
41602
41758
|
mathGridUniformityRule,
|
|
@@ -42310,24 +42466,6 @@ var signal_strength_default = {
|
|
|
42310
42466
|
_v8Verdict: "OK",
|
|
42311
42467
|
_v8Lift: 48.52
|
|
42312
42468
|
},
|
|
42313
|
-
"docs/expired-code-example": {
|
|
42314
|
-
recall: 0,
|
|
42315
|
-
fpRate: 0,
|
|
42316
|
-
ratio: 0,
|
|
42317
|
-
precision: 0,
|
|
42318
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42319
|
-
verdict: "INVERTED",
|
|
42320
|
-
_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).",
|
|
42321
|
-
aiSpecific: false,
|
|
42322
|
-
_v7Verdict: "DORMANT",
|
|
42323
|
-
_v7Lift: 1,
|
|
42324
|
-
_v7Recall: 0,
|
|
42325
|
-
_v7FpRate: 0,
|
|
42326
|
-
_v7Precision: 0,
|
|
42327
|
-
_v8Verdict: "INVERTED",
|
|
42328
|
-
_v8Lift: 0,
|
|
42329
|
-
defaultOff: true
|
|
42330
|
-
},
|
|
42331
42469
|
"docs/stale-function-reference": {
|
|
42332
42470
|
recall: 26e-4,
|
|
42333
42471
|
fpRate: 5e-4,
|
|
@@ -42434,6 +42572,114 @@ var signal_strength_default = {
|
|
|
42434
42572
|
_v8Lift: 1,
|
|
42435
42573
|
defaultOff: true
|
|
42436
42574
|
},
|
|
42575
|
+
"java/arraylist-vs-linkedlist": {
|
|
42576
|
+
recall: 0,
|
|
42577
|
+
fpRate: 0,
|
|
42578
|
+
ratio: 1,
|
|
42579
|
+
precision: 0,
|
|
42580
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42581
|
+
verdict: "DORMANT",
|
|
42582
|
+
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42583
|
+
aiSpecific: true,
|
|
42584
|
+
_v7Verdict: "DORMANT",
|
|
42585
|
+
_v7Lift: 1,
|
|
42586
|
+
_v7Recall: 0,
|
|
42587
|
+
_v7FpRate: 0,
|
|
42588
|
+
_v7Precision: 0,
|
|
42589
|
+
_v8Verdict: "DORMANT",
|
|
42590
|
+
_v8Lift: 1,
|
|
42591
|
+
defaultOff: true
|
|
42592
|
+
},
|
|
42593
|
+
"java/empty-catch-block": {
|
|
42594
|
+
recall: 0,
|
|
42595
|
+
fpRate: 0,
|
|
42596
|
+
ratio: 1,
|
|
42597
|
+
precision: 0,
|
|
42598
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42599
|
+
verdict: "DORMANT",
|
|
42600
|
+
_calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42601
|
+
aiSpecific: true,
|
|
42602
|
+
_v7Verdict: "DORMANT",
|
|
42603
|
+
_v7Lift: 1,
|
|
42604
|
+
_v7Recall: 0,
|
|
42605
|
+
_v7FpRate: 0,
|
|
42606
|
+
_v7Precision: 0,
|
|
42607
|
+
_v8Verdict: "DORMANT",
|
|
42608
|
+
_v8Lift: 1,
|
|
42609
|
+
defaultOff: true
|
|
42610
|
+
},
|
|
42611
|
+
"java/legacy-date-api": {
|
|
42612
|
+
recall: 0,
|
|
42613
|
+
fpRate: 0,
|
|
42614
|
+
ratio: 1,
|
|
42615
|
+
precision: 0,
|
|
42616
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42617
|
+
verdict: "DORMANT",
|
|
42618
|
+
_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.",
|
|
42619
|
+
aiSpecific: true,
|
|
42620
|
+
_v7Verdict: "DORMANT",
|
|
42621
|
+
_v7Lift: 1,
|
|
42622
|
+
_v7Recall: 0,
|
|
42623
|
+
_v7FpRate: 0,
|
|
42624
|
+
_v7Precision: 0,
|
|
42625
|
+
_v8Verdict: "DORMANT",
|
|
42626
|
+
_v8Lift: 1,
|
|
42627
|
+
defaultOff: true
|
|
42628
|
+
},
|
|
42629
|
+
"java/raw-type-overuse": {
|
|
42630
|
+
recall: 0,
|
|
42631
|
+
fpRate: 0,
|
|
42632
|
+
ratio: 1,
|
|
42633
|
+
precision: 0,
|
|
42634
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42635
|
+
verdict: "DORMANT",
|
|
42636
|
+
_calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
|
|
42637
|
+
aiSpecific: true,
|
|
42638
|
+
_v7Verdict: "DORMANT",
|
|
42639
|
+
_v7Lift: 1,
|
|
42640
|
+
_v7Recall: 0,
|
|
42641
|
+
_v7FpRate: 0,
|
|
42642
|
+
_v7Precision: 0,
|
|
42643
|
+
_v8Verdict: "DORMANT",
|
|
42644
|
+
_v8Lift: 1,
|
|
42645
|
+
defaultOff: true
|
|
42646
|
+
},
|
|
42647
|
+
"java/string-concat-loop": {
|
|
42648
|
+
recall: 0,
|
|
42649
|
+
fpRate: 0,
|
|
42650
|
+
ratio: 1,
|
|
42651
|
+
precision: 0,
|
|
42652
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42653
|
+
verdict: "DORMANT",
|
|
42654
|
+
_calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
|
|
42655
|
+
aiSpecific: true,
|
|
42656
|
+
_v7Verdict: "DORMANT",
|
|
42657
|
+
_v7Lift: 1,
|
|
42658
|
+
_v7Recall: 0,
|
|
42659
|
+
_v7FpRate: 0,
|
|
42660
|
+
_v7Precision: 0,
|
|
42661
|
+
_v8Verdict: "DORMANT",
|
|
42662
|
+
_v8Lift: 1,
|
|
42663
|
+
defaultOff: true
|
|
42664
|
+
},
|
|
42665
|
+
"java/system-out-println": {
|
|
42666
|
+
recall: 0,
|
|
42667
|
+
fpRate: 0,
|
|
42668
|
+
ratio: 1,
|
|
42669
|
+
precision: 0,
|
|
42670
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42671
|
+
verdict: "DORMANT",
|
|
42672
|
+
_calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42673
|
+
aiSpecific: true,
|
|
42674
|
+
_v7Verdict: "DORMANT",
|
|
42675
|
+
_v7Lift: 1,
|
|
42676
|
+
_v7Recall: 0,
|
|
42677
|
+
_v7FpRate: 0,
|
|
42678
|
+
_v7Precision: 0,
|
|
42679
|
+
_v8Verdict: "DORMANT",
|
|
42680
|
+
_v8Lift: 1,
|
|
42681
|
+
defaultOff: true
|
|
42682
|
+
},
|
|
42437
42683
|
"layout/forced-layout": {
|
|
42438
42684
|
recall: 0,
|
|
42439
42685
|
fpRate: 0,
|
|
@@ -43690,7 +43936,7 @@ function loadSignalStrength() {
|
|
|
43690
43936
|
function buildParserCacheConfig(cwd) {
|
|
43691
43937
|
const envVal = process.env.SLOP_AUDIT_CACHE;
|
|
43692
43938
|
const enabled = envVal === "1" || envVal === "true";
|
|
43693
|
-
const root = process.env.SLOP_AUDIT_CACHE_ROOT ??
|
|
43939
|
+
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? join11(cwd, ".slopbrick", "cache", "ast");
|
|
43694
43940
|
return { enabled, root };
|
|
43695
43941
|
}
|
|
43696
43942
|
function applyRuleOverrides(issues, rules) {
|
|
@@ -43708,7 +43954,7 @@ function applyRuleOverrides(issues, rules) {
|
|
|
43708
43954
|
}
|
|
43709
43955
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
43710
43956
|
const cache = buildParserCacheConfig(cwd);
|
|
43711
|
-
const ext =
|
|
43957
|
+
const ext = extname4(filePath).toLowerCase();
|
|
43712
43958
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
43713
43959
|
".swift",
|
|
43714
43960
|
".kt",
|