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.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");
|
|
@@ -7262,8 +6857,396 @@ function dispatchNode(node, parent, path, vctx) {
|
|
|
7262
6857
|
return false;
|
|
7263
6858
|
}
|
|
7264
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
|
+
|
|
7265
7249
|
// src/engine/visitors/v2-build.ts
|
|
7266
|
-
init_rust();
|
|
7267
7250
|
var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
|
|
7268
7251
|
var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
|
|
7269
7252
|
var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
|
|
@@ -32899,8 +32882,8 @@ function getCorpusBaselines() {
|
|
|
32899
32882
|
|
|
32900
32883
|
// src/rules/ai/comment-ratio.ts
|
|
32901
32884
|
var MIN_FILE_LINES = 20;
|
|
32902
|
-
var FALLBACK_LOW = 0.
|
|
32903
|
-
var FALLBACK_HIGH = 0.
|
|
32885
|
+
var FALLBACK_LOW = 0.02;
|
|
32886
|
+
var FALLBACK_HIGH = 0.55;
|
|
32904
32887
|
var LINE_COMMENT_RE = /^\s*(?:\/\/|#|--|;|%)/;
|
|
32905
32888
|
var BLOCK_COMMENT_OPEN_RE = /\/\*/;
|
|
32906
32889
|
var aiCommentRatioRule = createRule({
|
|
@@ -33029,9 +33012,9 @@ function std(xs) {
|
|
|
33029
33012
|
|
|
33030
33013
|
// src/rules/ai/compression-profile.ts
|
|
33031
33014
|
var MIN_BYTES = 2e3;
|
|
33032
|
-
var GZIP_RATIO_AI_THRESHOLD = 0.
|
|
33033
|
-
var MEAN_LINE_NCD_HUMAN_MAX = 0.
|
|
33034
|
-
var NCD_CV_AI_MAX = 0.
|
|
33015
|
+
var GZIP_RATIO_AI_THRESHOLD = 0.6;
|
|
33016
|
+
var MEAN_LINE_NCD_HUMAN_MAX = 0.25;
|
|
33017
|
+
var NCD_CV_AI_MAX = 0.4;
|
|
33035
33018
|
var aiCompressionProfileRule = createRule({
|
|
33036
33019
|
id: "ai/compression-profile",
|
|
33037
33020
|
category: "ai",
|
|
@@ -33186,6 +33169,13 @@ var aiDefaultReactStackRule = createRule({
|
|
|
33186
33169
|
category: "ai",
|
|
33187
33170
|
severity: "low",
|
|
33188
33171
|
aiSpecific: true,
|
|
33172
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.001
|
|
33173
|
+
// (fires 1 time per 1,000 files). The "≥3 of default stack"
|
|
33174
|
+
// threshold catches almost nothing on real codebases — the
|
|
33175
|
+
// default stack is now ubiquitous, so the signal is near-zero.
|
|
33176
|
+
// Disable until the threshold is lowered or the rule is split
|
|
33177
|
+
// into per-library detectors.
|
|
33178
|
+
defaultOff: true,
|
|
33189
33179
|
description: "File uses \u22653 of the default React stack (Next.js + TanStack Query + Zustand + shadcn/ui) \u2014 Sascha 2025 + Nam et al. MSR 2026",
|
|
33190
33180
|
create(context) {
|
|
33191
33181
|
return context;
|
|
@@ -33438,6 +33428,10 @@ var aiLibraryReinventionRule = createRule({
|
|
|
33438
33428
|
category: "ai",
|
|
33439
33429
|
severity: "medium",
|
|
33440
33430
|
aiSpecific: true,
|
|
33431
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
33432
|
+
// (fires never on the corpus). The "≥2 reinvented patterns"
|
|
33433
|
+
// threshold is too strict. Disable until rewritten.
|
|
33434
|
+
defaultOff: true,
|
|
33441
33435
|
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",
|
|
33442
33436
|
create(context) {
|
|
33443
33437
|
return context;
|
|
@@ -33624,6 +33618,12 @@ var aiMarkdownLeakageRule = createRule({
|
|
|
33624
33618
|
category: "ai",
|
|
33625
33619
|
severity: "high",
|
|
33626
33620
|
aiSpecific: true,
|
|
33621
|
+
// v0.20.0 calibration: v8.5 verdict = INVERTED (lift 65,504× but
|
|
33622
|
+
// recall 0.000 — fires never on the corpus, and when it does fire
|
|
33623
|
+
// it's wrong). The rule was on by default, contributing false
|
|
33624
|
+
// positives to the self-scan. Disable until the rule is rewritten
|
|
33625
|
+
// or the v9 corpus shows it works.
|
|
33626
|
+
defaultOff: true,
|
|
33627
33627
|
description: "Stray Markdown fence or bare language name at top of file \u2014 AI tools leak the fenced-block format into code",
|
|
33628
33628
|
create(context) {
|
|
33629
33629
|
return context;
|
|
@@ -34049,6 +34049,11 @@ var aiTextLikeRatioRule = createRule({
|
|
|
34049
34049
|
category: "ai",
|
|
34050
34050
|
severity: "low",
|
|
34051
34051
|
aiSpecific: true,
|
|
34052
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
34053
|
+
// (fires never on the corpus). The rule is too specific to ever
|
|
34054
|
+
// fire on real code — the prose-detection heuristic is too
|
|
34055
|
+
// conservative. Disable until rewritten.
|
|
34056
|
+
defaultOff: true,
|
|
34052
34057
|
description: "High ratio of prose-like lines embedded in code \u2014 AI tools often leave natural-language explanations in source files (Yotkova 2026)",
|
|
34053
34058
|
create(context) {
|
|
34054
34059
|
return context;
|
|
@@ -35107,125 +35112,17 @@ var unusedParameterRule = createRule({
|
|
|
35107
35112
|
});
|
|
35108
35113
|
|
|
35109
35114
|
// src/rules/docs/broken-link.ts
|
|
35110
|
-
var import_node_fs6 = require("fs");
|
|
35111
|
-
var import_node_path6 = require("path");
|
|
35112
|
-
|
|
35113
|
-
// src/engine/doc-freshness.ts
|
|
35114
|
-
var import_node_fs5 = require("fs");
|
|
35115
|
-
var import_node_path5 = require("path");
|
|
35116
|
-
var import_globby2 = require("globby");
|
|
35117
|
-
|
|
35118
|
-
// src/mcp/patterns.ts
|
|
35119
35115
|
var import_node_fs3 = require("fs");
|
|
35120
35116
|
var import_node_path3 = require("path");
|
|
35121
35117
|
|
|
35122
|
-
// src/engine/
|
|
35123
|
-
var import_globby = require("globby");
|
|
35124
|
-
var import_minimatch = require("minimatch");
|
|
35125
|
-
var import_node_path = require("path");
|
|
35126
|
-
var import_node_fs = require("fs");
|
|
35127
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
|
|
35128
|
-
var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35129
|
-
".py",
|
|
35130
|
-
".go",
|
|
35131
|
-
// v0.14.0
|
|
35132
|
-
".swift",
|
|
35133
|
-
".kt",
|
|
35134
|
-
".kts",
|
|
35135
|
-
".dart",
|
|
35136
|
-
".rs",
|
|
35137
|
-
".cpp",
|
|
35138
|
-
".cc",
|
|
35139
|
-
".cxx",
|
|
35140
|
-
".c",
|
|
35141
|
-
".h",
|
|
35142
|
-
".hpp",
|
|
35143
|
-
".hxx",
|
|
35144
|
-
".java",
|
|
35145
|
-
".rb",
|
|
35146
|
-
".php"
|
|
35147
|
-
]);
|
|
35148
|
-
var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
35149
|
-
...SOURCE_EXTENSIONS,
|
|
35150
|
-
...BACKEND_EXTENSIONS
|
|
35151
|
-
]);
|
|
35152
|
-
|
|
35153
|
-
// src/config/conventions.ts
|
|
35118
|
+
// src/engine/doc-freshness.ts
|
|
35154
35119
|
var import_node_fs2 = require("fs");
|
|
35155
35120
|
var import_node_path2 = require("path");
|
|
35156
|
-
|
|
35157
|
-
// src/mcp/patterns.ts
|
|
35158
|
-
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;
|
|
35159
|
-
var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35160
|
-
var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
35161
|
-
function extractImports(source) {
|
|
35162
|
-
const seen = /* @__PURE__ */ new Set();
|
|
35163
|
-
const out = [];
|
|
35164
|
-
const push = (spec) => {
|
|
35165
|
-
if (spec.startsWith(".") || spec.startsWith("/")) return;
|
|
35166
|
-
if (seen.has(spec)) return;
|
|
35167
|
-
seen.add(spec);
|
|
35168
|
-
out.push(spec);
|
|
35169
|
-
};
|
|
35170
|
-
for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
|
|
35171
|
-
re.lastIndex = 0;
|
|
35172
|
-
let m;
|
|
35173
|
-
while ((m = re.exec(source)) !== null) {
|
|
35174
|
-
push(m[2]);
|
|
35175
|
-
}
|
|
35176
|
-
}
|
|
35177
|
-
return out;
|
|
35178
|
-
}
|
|
35179
|
-
|
|
35180
|
-
// src/rules/docs/expired-code-example.ts
|
|
35181
|
-
var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
|
|
35182
|
-
function stripSubpath(spec) {
|
|
35183
|
-
if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
|
|
35184
|
-
return spec.split("/")[0] ?? spec;
|
|
35185
|
-
}
|
|
35186
|
-
var expiredCodeExampleRule = createRule({
|
|
35187
|
-
id: "docs/expired-code-example",
|
|
35188
|
-
category: "docs",
|
|
35189
|
-
severity: "medium",
|
|
35190
|
-
aiSpecific: false,
|
|
35191
|
-
description: "A fenced code example imports a package that is not declared in package.json.",
|
|
35192
|
-
create(context) {
|
|
35193
|
-
return { ...context, packages: declaredPackages(context.cwd) };
|
|
35194
|
-
},
|
|
35195
|
-
analyze(context, facts) {
|
|
35196
|
-
const issues = [];
|
|
35197
|
-
const source = facts.v2?._source;
|
|
35198
|
-
if (!source) return issues;
|
|
35199
|
-
const packages = declaredPackages(context.cwd);
|
|
35200
|
-
const packageName = context.packageName;
|
|
35201
|
-
if (packageName) packages.add(packageName);
|
|
35202
|
-
const blocks = extractFencedCodeBlocks(source);
|
|
35203
|
-
for (const block of blocks) {
|
|
35204
|
-
if (!CODE_LANGS.has(block.lang)) continue;
|
|
35205
|
-
if (block.body.split("\n").length < 2) continue;
|
|
35206
|
-
const imports = extractImports(block.body);
|
|
35207
|
-
for (const imp of imports) {
|
|
35208
|
-
const pkgName = stripSubpath(imp);
|
|
35209
|
-
if (packages.has(pkgName)) continue;
|
|
35210
|
-
issues.push({
|
|
35211
|
-
ruleId: "docs/expired-code-example",
|
|
35212
|
-
category: "docs",
|
|
35213
|
-
severity: "medium",
|
|
35214
|
-
aiSpecific: false,
|
|
35215
|
-
message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
|
|
35216
|
-
line: block.line,
|
|
35217
|
-
column: block.column,
|
|
35218
|
-
advice: `Add \`${pkgName}\` to package.json or update the example.`
|
|
35219
|
-
});
|
|
35220
|
-
}
|
|
35221
|
-
}
|
|
35222
|
-
return issues;
|
|
35223
|
-
}
|
|
35224
|
-
});
|
|
35121
|
+
var import_globby = require("globby");
|
|
35225
35122
|
|
|
35226
35123
|
// src/rules/docs/stale-function-reference.ts
|
|
35227
|
-
var
|
|
35228
|
-
var
|
|
35124
|
+
var import_node_fs = require("fs");
|
|
35125
|
+
var import_node_path = require("path");
|
|
35229
35126
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
35230
35127
|
// JS reserved words
|
|
35231
35128
|
"true",
|
|
@@ -35798,29 +35695,29 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
|
|
|
35798
35695
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
35799
35696
|
var CAP = 200;
|
|
35800
35697
|
function walk(dir, out, cap) {
|
|
35801
|
-
if (!(0,
|
|
35698
|
+
if (!(0, import_node_fs.existsSync)(dir) || out.length >= cap) return;
|
|
35802
35699
|
let entries;
|
|
35803
35700
|
try {
|
|
35804
|
-
entries = (0,
|
|
35701
|
+
entries = (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
|
|
35805
35702
|
} catch {
|
|
35806
35703
|
return;
|
|
35807
35704
|
}
|
|
35808
35705
|
for (const entry of entries) {
|
|
35809
35706
|
if (out.length >= cap) return;
|
|
35810
35707
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
35811
|
-
const full = (0,
|
|
35708
|
+
const full = (0, import_node_path.join)(dir, entry.name);
|
|
35812
35709
|
if (entry.isDirectory()) walk(full, out, cap);
|
|
35813
|
-
else if (entry.isFile() && SOURCE_EXTS.has((0,
|
|
35710
|
+
else if (entry.isFile() && SOURCE_EXTS.has((0, import_node_path.extname)(entry.name))) out.push(full);
|
|
35814
35711
|
}
|
|
35815
35712
|
}
|
|
35816
35713
|
function collectExports(cwd) {
|
|
35817
35714
|
const out = /* @__PURE__ */ new Set();
|
|
35818
35715
|
const files = [];
|
|
35819
|
-
for (const root of SOURCE_ROOTS) walk((0,
|
|
35716
|
+
for (const root of SOURCE_ROOTS) walk((0, import_node_path.join)(cwd, root), files, CAP);
|
|
35820
35717
|
for (const file of files) {
|
|
35821
35718
|
let source;
|
|
35822
35719
|
try {
|
|
35823
|
-
source = (0,
|
|
35720
|
+
source = (0, import_node_fs.readFileSync)(file, "utf-8");
|
|
35824
35721
|
} catch {
|
|
35825
35722
|
continue;
|
|
35826
35723
|
}
|
|
@@ -35833,7 +35730,7 @@ function collectExports(cwd) {
|
|
|
35833
35730
|
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
|
|
35834
35731
|
// v0.18.6: also collect field names from `export interface` and
|
|
35835
35732
|
// `export type` declarations. Without this, fields like
|
|
35836
|
-
// `crossFileDrift`, `
|
|
35733
|
+
// `crossFileDrift`, `aiSlopScore`, `engineeringHygiene` are
|
|
35837
35734
|
// flagged as stale even though they're valid type fields.
|
|
35838
35735
|
/^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
|
|
35839
35736
|
]) {
|
|
@@ -36083,38 +35980,6 @@ function extractInlineCodeSpans(source) {
|
|
|
36083
35980
|
}
|
|
36084
35981
|
return hits;
|
|
36085
35982
|
}
|
|
36086
|
-
function extractFencedCodeBlocks(source) {
|
|
36087
|
-
const blocks = [];
|
|
36088
|
-
const lines = source.split("\n");
|
|
36089
|
-
let i = 0;
|
|
36090
|
-
while (i < lines.length) {
|
|
36091
|
-
const line = lines[i] ?? "";
|
|
36092
|
-
const fenceMatch = /^```(\w*)\s*$/.exec(line);
|
|
36093
|
-
if (!fenceMatch) {
|
|
36094
|
-
i++;
|
|
36095
|
-
continue;
|
|
36096
|
-
}
|
|
36097
|
-
const lang = fenceMatch[1] ?? "";
|
|
36098
|
-
const startLine = i + 1;
|
|
36099
|
-
const bodyLines = [];
|
|
36100
|
-
i++;
|
|
36101
|
-
while (i < lines.length) {
|
|
36102
|
-
if (/^```\s*$/.test(lines[i] ?? "")) {
|
|
36103
|
-
i++;
|
|
36104
|
-
break;
|
|
36105
|
-
}
|
|
36106
|
-
bodyLines.push(lines[i] ?? "");
|
|
36107
|
-
i++;
|
|
36108
|
-
}
|
|
36109
|
-
blocks.push({
|
|
36110
|
-
lang,
|
|
36111
|
-
body: bodyLines.join("\n"),
|
|
36112
|
-
line: startLine,
|
|
36113
|
-
column: 1
|
|
36114
|
-
});
|
|
36115
|
-
}
|
|
36116
|
-
return blocks;
|
|
36117
|
-
}
|
|
36118
35983
|
function extractMarkdownLinks(source) {
|
|
36119
35984
|
const hits = [];
|
|
36120
35985
|
const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
@@ -36131,10 +35996,10 @@ function extractMarkdownLinks(source) {
|
|
|
36131
35996
|
}
|
|
36132
35997
|
function declaredPackages(cwd) {
|
|
36133
35998
|
const out = /* @__PURE__ */ new Set();
|
|
36134
|
-
const pkgPath = (0,
|
|
36135
|
-
if (!(0,
|
|
35999
|
+
const pkgPath = (0, import_node_path2.join)(cwd, "package.json");
|
|
36000
|
+
if (!(0, import_node_fs2.existsSync)(pkgPath)) return out;
|
|
36136
36001
|
try {
|
|
36137
|
-
const raw = (0,
|
|
36002
|
+
const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
|
|
36138
36003
|
const pkg = JSON.parse(raw);
|
|
36139
36004
|
for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
36140
36005
|
const v = pkg[k];
|
|
@@ -36164,7 +36029,7 @@ var brokenLinkRule = createRule({
|
|
|
36164
36029
|
const source = facts.v2?._source;
|
|
36165
36030
|
if (!source) return issues;
|
|
36166
36031
|
const links = extractMarkdownLinks(source);
|
|
36167
|
-
const docDir = (0,
|
|
36032
|
+
const docDir = (0, import_node_path3.dirname)((0, import_node_path3.resolve)(context.cwd, context.filePath));
|
|
36168
36033
|
for (const link of links) {
|
|
36169
36034
|
const target = link.target;
|
|
36170
36035
|
if (target.startsWith("http://") || target.startsWith("https://")) continue;
|
|
@@ -36174,8 +36039,8 @@ var brokenLinkRule = createRule({
|
|
|
36174
36039
|
if (target.startsWith("/")) continue;
|
|
36175
36040
|
const filePart = target.split("#")[0] ?? target;
|
|
36176
36041
|
if (filePart === "") continue;
|
|
36177
|
-
const resolved = (0,
|
|
36178
|
-
if ((0,
|
|
36042
|
+
const resolved = (0, import_node_path3.join)(docDir, filePart);
|
|
36043
|
+
if ((0, import_node_fs3.existsSync)(resolved)) continue;
|
|
36179
36044
|
issues.push({
|
|
36180
36045
|
ruleId: "docs/broken-link",
|
|
36181
36046
|
category: "docs",
|
|
@@ -36404,10 +36269,261 @@ var goStructTagInconsistencyRule = createRule({
|
|
|
36404
36269
|
}
|
|
36405
36270
|
});
|
|
36406
36271
|
|
|
36272
|
+
// src/rules/java/arraylist-vs-linkedlist.ts
|
|
36273
|
+
var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
|
|
36274
|
+
var javaArraylistVsLinkedlistRule = createRule({
|
|
36275
|
+
id: "java/arraylist-vs-linkedlist",
|
|
36276
|
+
category: "typo",
|
|
36277
|
+
severity: "low",
|
|
36278
|
+
aiSpecific: true,
|
|
36279
|
+
description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
|
|
36280
|
+
create(_context) {
|
|
36281
|
+
return {};
|
|
36282
|
+
},
|
|
36283
|
+
analyze(_context, facts) {
|
|
36284
|
+
const issues = [];
|
|
36285
|
+
const source = facts.v2?._source;
|
|
36286
|
+
if (!source) return issues;
|
|
36287
|
+
let m;
|
|
36288
|
+
NEW_LINKED_LIST_REGEX.lastIndex = 0;
|
|
36289
|
+
while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
|
|
36290
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36291
|
+
issues.push({
|
|
36292
|
+
ruleId: "java/arraylist-vs-linkedlist",
|
|
36293
|
+
category: "typo",
|
|
36294
|
+
severity: "low",
|
|
36295
|
+
aiSpecific: true,
|
|
36296
|
+
message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
|
|
36297
|
+
line,
|
|
36298
|
+
column: 1,
|
|
36299
|
+
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."
|
|
36300
|
+
});
|
|
36301
|
+
}
|
|
36302
|
+
return issues;
|
|
36303
|
+
}
|
|
36304
|
+
});
|
|
36305
|
+
|
|
36306
|
+
// src/rules/java/empty-catch-block.ts
|
|
36307
|
+
var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
36308
|
+
var javaEmptyCatchBlockRule = createRule({
|
|
36309
|
+
id: "java/empty-catch-block",
|
|
36310
|
+
category: "logic",
|
|
36311
|
+
severity: "medium",
|
|
36312
|
+
aiSpecific: true,
|
|
36313
|
+
description: "Empty catch block \u2014 silently swallows exceptions",
|
|
36314
|
+
create(_context) {
|
|
36315
|
+
return {};
|
|
36316
|
+
},
|
|
36317
|
+
analyze(_context, facts) {
|
|
36318
|
+
const issues = [];
|
|
36319
|
+
const source = facts.v2?._source;
|
|
36320
|
+
if (!source) return issues;
|
|
36321
|
+
let m;
|
|
36322
|
+
SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
|
|
36323
|
+
while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
|
|
36324
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36325
|
+
issues.push({
|
|
36326
|
+
ruleId: "java/empty-catch-block",
|
|
36327
|
+
category: "logic",
|
|
36328
|
+
severity: "medium",
|
|
36329
|
+
aiSpecific: true,
|
|
36330
|
+
message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
|
|
36331
|
+
line,
|
|
36332
|
+
column: 1,
|
|
36333
|
+
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.'
|
|
36334
|
+
});
|
|
36335
|
+
}
|
|
36336
|
+
return issues;
|
|
36337
|
+
}
|
|
36338
|
+
});
|
|
36339
|
+
|
|
36340
|
+
// src/rules/java/legacy-date-api.ts
|
|
36341
|
+
var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
|
|
36342
|
+
var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
|
|
36343
|
+
var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
|
|
36344
|
+
var javaLegacyDateApiRule = createRule({
|
|
36345
|
+
id: "java/legacy-date-api",
|
|
36346
|
+
category: "typo",
|
|
36347
|
+
severity: "low",
|
|
36348
|
+
aiSpecific: true,
|
|
36349
|
+
description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
|
|
36350
|
+
create(_context) {
|
|
36351
|
+
return {};
|
|
36352
|
+
},
|
|
36353
|
+
analyze(_context, facts) {
|
|
36354
|
+
const issues = [];
|
|
36355
|
+
const source = facts.v2?._source;
|
|
36356
|
+
if (!source) return issues;
|
|
36357
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
36358
|
+
let m;
|
|
36359
|
+
LEGACY_IMPORT_REGEX.lastIndex = 0;
|
|
36360
|
+
while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
|
|
36361
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36362
|
+
flagged.add(line);
|
|
36363
|
+
issues.push({
|
|
36364
|
+
ruleId: "java/legacy-date-api",
|
|
36365
|
+
category: "typo",
|
|
36366
|
+
severity: "low",
|
|
36367
|
+
aiSpecific: true,
|
|
36368
|
+
message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
|
|
36369
|
+
line,
|
|
36370
|
+
column: 1,
|
|
36371
|
+
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."
|
|
36372
|
+
});
|
|
36373
|
+
}
|
|
36374
|
+
LEGACY_USAGE_REGEX.lastIndex = 0;
|
|
36375
|
+
while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
|
|
36376
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36377
|
+
if (flagged.has(line)) continue;
|
|
36378
|
+
issues.push({
|
|
36379
|
+
ruleId: "java/legacy-date-api",
|
|
36380
|
+
category: "typo",
|
|
36381
|
+
severity: "low",
|
|
36382
|
+
aiSpecific: true,
|
|
36383
|
+
message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
|
|
36384
|
+
line,
|
|
36385
|
+
column: 1,
|
|
36386
|
+
advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36387
|
+
});
|
|
36388
|
+
}
|
|
36389
|
+
CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
|
|
36390
|
+
while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
|
|
36391
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36392
|
+
if (flagged.has(line)) continue;
|
|
36393
|
+
issues.push({
|
|
36394
|
+
ruleId: "java/legacy-date-api",
|
|
36395
|
+
category: "typo",
|
|
36396
|
+
severity: "low",
|
|
36397
|
+
aiSpecific: true,
|
|
36398
|
+
message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
|
|
36399
|
+
line,
|
|
36400
|
+
column: 1,
|
|
36401
|
+
advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36402
|
+
});
|
|
36403
|
+
}
|
|
36404
|
+
return issues;
|
|
36405
|
+
}
|
|
36406
|
+
});
|
|
36407
|
+
|
|
36408
|
+
// src/rules/java/raw-type-overuse.ts
|
|
36409
|
+
var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
|
|
36410
|
+
var javaRawTypeOveruseRule = createRule({
|
|
36411
|
+
id: "java/raw-type-overuse",
|
|
36412
|
+
category: "typo",
|
|
36413
|
+
severity: "low",
|
|
36414
|
+
aiSpecific: true,
|
|
36415
|
+
description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
|
|
36416
|
+
create(_context) {
|
|
36417
|
+
return {};
|
|
36418
|
+
},
|
|
36419
|
+
analyze(_context, facts) {
|
|
36420
|
+
const issues = [];
|
|
36421
|
+
const source = facts.v2?._source;
|
|
36422
|
+
if (!source) return issues;
|
|
36423
|
+
let m;
|
|
36424
|
+
RAW_TYPE_REGEX.lastIndex = 0;
|
|
36425
|
+
while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
|
|
36426
|
+
const typeName = m[1];
|
|
36427
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36428
|
+
issues.push({
|
|
36429
|
+
ruleId: "java/raw-type-overuse",
|
|
36430
|
+
category: "typo",
|
|
36431
|
+
severity: "low",
|
|
36432
|
+
aiSpecific: true,
|
|
36433
|
+
message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
|
|
36434
|
+
line,
|
|
36435
|
+
column: 1,
|
|
36436
|
+
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.`
|
|
36437
|
+
});
|
|
36438
|
+
}
|
|
36439
|
+
return issues;
|
|
36440
|
+
}
|
|
36441
|
+
});
|
|
36442
|
+
|
|
36443
|
+
// src/rules/java/string-concat-loop.ts
|
|
36444
|
+
var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
|
|
36445
|
+
var javaStringConcatLoopRule = createRule({
|
|
36446
|
+
id: "java/string-concat-loop",
|
|
36447
|
+
category: "perf",
|
|
36448
|
+
severity: "low",
|
|
36449
|
+
aiSpecific: true,
|
|
36450
|
+
description: "String concatenation in a loop \u2014 use StringBuilder",
|
|
36451
|
+
create(_context) {
|
|
36452
|
+
return {};
|
|
36453
|
+
},
|
|
36454
|
+
analyze(_context, facts) {
|
|
36455
|
+
const issues = [];
|
|
36456
|
+
const source = facts.v2?._source;
|
|
36457
|
+
if (!source) return issues;
|
|
36458
|
+
if (!/\b(for|while|do)\b/.test(source)) return issues;
|
|
36459
|
+
let m;
|
|
36460
|
+
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
36461
|
+
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
36462
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36463
|
+
issues.push({
|
|
36464
|
+
ruleId: "java/string-concat-loop",
|
|
36465
|
+
category: "perf",
|
|
36466
|
+
severity: "low",
|
|
36467
|
+
aiSpecific: true,
|
|
36468
|
+
message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
|
|
36469
|
+
line,
|
|
36470
|
+
column: 1,
|
|
36471
|
+
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."
|
|
36472
|
+
});
|
|
36473
|
+
}
|
|
36474
|
+
return issues;
|
|
36475
|
+
}
|
|
36476
|
+
});
|
|
36477
|
+
|
|
36478
|
+
// src/rules/java/system-out-println.ts
|
|
36479
|
+
var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
|
|
36480
|
+
var DEFAULT_THRESHOLD = 1;
|
|
36481
|
+
var javaSystemOutPrintlnRule = createRule({
|
|
36482
|
+
id: "java/system-out-println",
|
|
36483
|
+
category: "typo",
|
|
36484
|
+
severity: "low",
|
|
36485
|
+
aiSpecific: true,
|
|
36486
|
+
description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
|
|
36487
|
+
create(_context) {
|
|
36488
|
+
return { threshold: DEFAULT_THRESHOLD };
|
|
36489
|
+
},
|
|
36490
|
+
analyze(context, facts) {
|
|
36491
|
+
const issues = [];
|
|
36492
|
+
const source = facts.v2?._source;
|
|
36493
|
+
if (!source) return issues;
|
|
36494
|
+
const matches = [];
|
|
36495
|
+
let m;
|
|
36496
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
36497
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
36498
|
+
matches.push(m.index);
|
|
36499
|
+
}
|
|
36500
|
+
if (matches.length <= context.threshold) return issues;
|
|
36501
|
+
const cap = Math.min(matches.length, 10);
|
|
36502
|
+
for (let i = 0; i < cap; i++) {
|
|
36503
|
+
const idx = matches[i];
|
|
36504
|
+
const line = source.slice(0, idx).split("\n").length;
|
|
36505
|
+
issues.push({
|
|
36506
|
+
ruleId: "java/system-out-println",
|
|
36507
|
+
category: "typo",
|
|
36508
|
+
severity: "low",
|
|
36509
|
+
aiSpecific: true,
|
|
36510
|
+
message: `System.out.println at line ${line} \u2014 use a logger for production output`,
|
|
36511
|
+
line,
|
|
36512
|
+
column: 1,
|
|
36513
|
+
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."
|
|
36514
|
+
});
|
|
36515
|
+
}
|
|
36516
|
+
return issues;
|
|
36517
|
+
}
|
|
36518
|
+
});
|
|
36519
|
+
|
|
36407
36520
|
// src/rules/layout/gap-monopoly.ts
|
|
36408
36521
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36409
36522
|
var gapMonopolyRule = createRule({
|
|
36410
36523
|
id: "layout/gap-monopoly",
|
|
36524
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
36525
|
+
// until rewritten.
|
|
36526
|
+
defaultOff: true,
|
|
36411
36527
|
category: "layout",
|
|
36412
36528
|
severity: "medium",
|
|
36413
36529
|
aiSpecific: false,
|
|
@@ -36504,6 +36620,9 @@ var mathElementUniformityRule = createRule({
|
|
|
36504
36620
|
var GRID_COLS_RE = /\bgrid-cols-(\d+)\b/g;
|
|
36505
36621
|
var mathGridUniformityRule = createRule({
|
|
36506
36622
|
id: "layout/math-grid-uniformity",
|
|
36623
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
36624
|
+
// until rewritten.
|
|
36625
|
+
defaultOff: true,
|
|
36507
36626
|
category: "layout",
|
|
36508
36627
|
severity: "high",
|
|
36509
36628
|
aiSpecific: true,
|
|
@@ -36756,16 +36875,16 @@ var DEFAULT_CONFIG = {
|
|
|
36756
36875
|
};
|
|
36757
36876
|
|
|
36758
36877
|
// src/config/detect/monorepo.ts
|
|
36759
|
-
var
|
|
36760
|
-
var
|
|
36878
|
+
var import_node_fs4 = require("fs");
|
|
36879
|
+
var import_node_path4 = require("path");
|
|
36761
36880
|
|
|
36762
36881
|
// src/config/detect/styling.ts
|
|
36763
|
-
var
|
|
36764
|
-
var
|
|
36882
|
+
var import_node_fs5 = require("fs");
|
|
36883
|
+
var import_node_path5 = require("path");
|
|
36765
36884
|
|
|
36766
36885
|
// src/config/detect/stack.ts
|
|
36767
|
-
var
|
|
36768
|
-
var
|
|
36886
|
+
var import_node_fs6 = require("fs");
|
|
36887
|
+
var import_node_path6 = require("path");
|
|
36769
36888
|
|
|
36770
36889
|
// src/config/presets.ts
|
|
36771
36890
|
var REACT_ONLY_RULES = {
|
|
@@ -36814,8 +36933,8 @@ var FRAMEWORK_PRESETS = {
|
|
|
36814
36933
|
};
|
|
36815
36934
|
|
|
36816
36935
|
// src/config/load.ts
|
|
36817
|
-
var
|
|
36818
|
-
var
|
|
36936
|
+
var import_node_fs8 = require("fs");
|
|
36937
|
+
var import_node_path8 = require("path");
|
|
36819
36938
|
var import_node_module = require("module");
|
|
36820
36939
|
|
|
36821
36940
|
// src/engine/logger.ts
|
|
@@ -36837,6 +36956,10 @@ function setLoggerQuiet(quiet) {
|
|
|
36837
36956
|
logger = createLogger(quiet);
|
|
36838
36957
|
}
|
|
36839
36958
|
|
|
36959
|
+
// src/config/conventions.ts
|
|
36960
|
+
var import_node_fs7 = require("fs");
|
|
36961
|
+
var import_node_path7 = require("path");
|
|
36962
|
+
|
|
36840
36963
|
// src/config/init.ts
|
|
36841
36964
|
var STRICTNESS_PRESETS = {
|
|
36842
36965
|
strict: {
|
|
@@ -36951,6 +37074,9 @@ function checkInlineValue(value, scale) {
|
|
|
36951
37074
|
}
|
|
36952
37075
|
var spacingGridRule = createRule({
|
|
36953
37076
|
id: "layout/spacing-grid",
|
|
37077
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
37078
|
+
// until rewritten.
|
|
37079
|
+
defaultOff: true,
|
|
36954
37080
|
category: "layout",
|
|
36955
37081
|
severity: "medium",
|
|
36956
37082
|
aiSpecific: false,
|
|
@@ -37060,15 +37186,9 @@ var boundaryViolationRule = createRule({
|
|
|
37060
37186
|
if (context.supportsRsc === false) return [];
|
|
37061
37187
|
if (!facts.v2) return [];
|
|
37062
37188
|
const issues = [];
|
|
37063
|
-
const hookLines = /* @__PURE__ */ new Map();
|
|
37064
|
-
for (const h of facts.v2.logic.hooks) {
|
|
37065
|
-
const arr = hookLines.get(h.name) ?? [];
|
|
37066
|
-
arr.push({ line: h.line, column: h.column });
|
|
37067
|
-
hookLines.set(h.name, arr);
|
|
37068
|
-
}
|
|
37069
37189
|
for (const component of facts.v2.components) {
|
|
37070
37190
|
if (!component.isServerComponent) continue;
|
|
37071
|
-
for (const hook of
|
|
37191
|
+
for (const hook of component.hookCalls) {
|
|
37072
37192
|
if (!context.clientHooks.has(hook.name)) continue;
|
|
37073
37193
|
issues.push({
|
|
37074
37194
|
ruleId: "logic/boundary-violation",
|
|
@@ -37076,8 +37196,8 @@ var boundaryViolationRule = createRule({
|
|
|
37076
37196
|
severity: "high",
|
|
37077
37197
|
aiSpecific: true,
|
|
37078
37198
|
message: `'${hook.name}' only works in interactive client components. This component runs on the server.`,
|
|
37079
|
-
line:
|
|
37080
|
-
column:
|
|
37199
|
+
line: hook.line,
|
|
37200
|
+
column: hook.column,
|
|
37081
37201
|
advice: "Add the 'use client' directive at the top of this file, or move the stateful part to a separate client component.",
|
|
37082
37202
|
fix: {
|
|
37083
37203
|
kind: "insert",
|
|
@@ -38202,7 +38322,6 @@ function scanForStringlyParams(paramText) {
|
|
|
38202
38322
|
}
|
|
38203
38323
|
|
|
38204
38324
|
// src/rules/rust/todo-macro.ts
|
|
38205
|
-
init_parser_rust();
|
|
38206
38325
|
var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
|
|
38207
38326
|
var rustTodoMacroRule = createRule({
|
|
38208
38327
|
id: "rust/todo-macro",
|
|
@@ -38338,7 +38457,6 @@ function collectReferencedNames(source) {
|
|
|
38338
38457
|
}
|
|
38339
38458
|
|
|
38340
38459
|
// src/rules/rust/unwrap-in-production.ts
|
|
38341
|
-
init_parser_rust();
|
|
38342
38460
|
var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
|
|
38343
38461
|
var rustUnwrapInProductionRule = createRule({
|
|
38344
38462
|
id: "rust/unwrap-in-production",
|
|
@@ -39475,7 +39593,7 @@ function looksRealistic(value) {
|
|
|
39475
39593
|
|
|
39476
39594
|
// src/rules/test/missing-edge-case.ts
|
|
39477
39595
|
var import_core4 = require("@swc/core");
|
|
39478
|
-
var
|
|
39596
|
+
var import_node_fs9 = require("fs");
|
|
39479
39597
|
var MAX_PER_FILE = 20;
|
|
39480
39598
|
var missingEdgeCaseRule = createRule({
|
|
39481
39599
|
id: "test/missing-edge-case",
|
|
@@ -39497,7 +39615,7 @@ var missingEdgeCaseRule = createRule({
|
|
|
39497
39615
|
const testFileSources = /* @__PURE__ */ new Map();
|
|
39498
39616
|
for (const testFile of discoverTestFiles(cwd)) {
|
|
39499
39617
|
try {
|
|
39500
|
-
testFileSources.set(testFile, (0,
|
|
39618
|
+
testFileSources.set(testFile, (0, import_node_fs9.readFileSync)(testFile, "utf-8"));
|
|
39501
39619
|
} catch {
|
|
39502
39620
|
}
|
|
39503
39621
|
}
|
|
@@ -39751,8 +39869,8 @@ function discoverTestFiles(cwd) {
|
|
|
39751
39869
|
const found = [];
|
|
39752
39870
|
for (const root of roots) {
|
|
39753
39871
|
const abs = `${cwd}/${root}`;
|
|
39754
|
-
if (!(0,
|
|
39755
|
-
walk2(abs, found,
|
|
39872
|
+
if (!(0, import_node_fs9.existsSync)(abs)) continue;
|
|
39873
|
+
walk2(abs, found, import_node_fs9.readdirSync, import_node_fs9.statSync);
|
|
39756
39874
|
if (found.length > 200) break;
|
|
39757
39875
|
}
|
|
39758
39876
|
return found;
|
|
@@ -39863,7 +39981,12 @@ var WEAK_MATCHERS = /* @__PURE__ */ new Set([
|
|
|
39863
39981
|
"toBeUndefined",
|
|
39864
39982
|
"toBeTruthy",
|
|
39865
39983
|
"toBeFalsy",
|
|
39866
|
-
"toBeNull"
|
|
39984
|
+
"toBeNull",
|
|
39985
|
+
"toBeGreaterThan",
|
|
39986
|
+
"toBeLessThan",
|
|
39987
|
+
"toContain",
|
|
39988
|
+
"toHaveLength",
|
|
39989
|
+
"toBeInstanceOf"
|
|
39867
39990
|
]);
|
|
39868
39991
|
function isTautologicalAssertion(hit) {
|
|
39869
39992
|
if (hit.matcherArg && hit.expectArg && hit.expectArg === hit.matcherArg) return true;
|
|
@@ -40704,6 +40827,10 @@ function rgbToHue([r, g, b]) {
|
|
|
40704
40827
|
}
|
|
40705
40828
|
var mathColorClusterRule = createRule({
|
|
40706
40829
|
id: "visual/math-color-cluster",
|
|
40830
|
+
// v0.20.0 calibration: recall 0.000, fires never. The
|
|
40831
|
+
// "math-color cluster" heuristic is too specific to ever fire
|
|
40832
|
+
// on real code. Disable until rewritten.
|
|
40833
|
+
defaultOff: true,
|
|
40707
40834
|
category: "visual",
|
|
40708
40835
|
severity: "high",
|
|
40709
40836
|
aiSpecific: true,
|
|
@@ -40758,6 +40885,9 @@ var FONT_IMPORT_HINT_RE = /next\/font|@import\s+url|fonts\.googleapis|fonts\.gst
|
|
|
40758
40885
|
var NEXT_FONT_IMPORT_RE = /import\s+.*from\s+['"]next\/font\/(?:google|local)['"]/;
|
|
40759
40886
|
var mathDefaultFontRule = createRule({
|
|
40760
40887
|
id: "visual/math-default-font",
|
|
40888
|
+
// v0.20.0 calibration: recall 0.001, fires 1 per 1,000 files.
|
|
40889
|
+
// Too specific to be useful. Disable until rewritten.
|
|
40890
|
+
defaultOff: true,
|
|
40761
40891
|
category: "visual",
|
|
40762
40892
|
severity: "high",
|
|
40763
40893
|
aiSpecific: true,
|
|
@@ -40824,6 +40954,9 @@ var TEXT_SIZE_MAP = {
|
|
|
40824
40954
|
var TEXT_SIZE_RE = /\btext-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)\b/g;
|
|
40825
40955
|
var mathFontEntropyRule = createRule({
|
|
40826
40956
|
id: "visual/math-font-entropy",
|
|
40957
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
40958
|
+
// (verified). Disable until rewritten.
|
|
40959
|
+
defaultOff: true,
|
|
40827
40960
|
category: "visual",
|
|
40828
40961
|
severity: "high",
|
|
40829
40962
|
aiSpecific: true,
|
|
@@ -40946,6 +41079,10 @@ var ROUNDED_MAP = {
|
|
|
40946
41079
|
var ROUNDED_RE = /\brounded(?:-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?\b/g;
|
|
40947
41080
|
var mathRoundedEntropyRule = createRule({
|
|
40948
41081
|
id: "visual/math-rounded-entropy",
|
|
41082
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
41083
|
+
// (verified). The entropy heuristic is too specific to ever fire
|
|
41084
|
+
// on real code. Disable until rewritten.
|
|
41085
|
+
defaultOff: true,
|
|
40949
41086
|
category: "visual",
|
|
40950
41087
|
severity: "high",
|
|
40951
41088
|
aiSpecific: true,
|
|
@@ -40986,6 +41123,9 @@ var mathRoundedEntropyRule = createRule({
|
|
|
40986
41123
|
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;
|
|
40987
41124
|
var mathSpacingEntropyRule = createRule({
|
|
40988
41125
|
id: "visual/math-spacing-entropy",
|
|
41126
|
+
// v0.20.0 calibration: recall 0.002, fires 0 times on self-scan
|
|
41127
|
+
// (verified). Disable until rewritten.
|
|
41128
|
+
defaultOff: true,
|
|
40989
41129
|
category: "visual",
|
|
40990
41130
|
severity: "medium",
|
|
40991
41131
|
aiSpecific: true,
|
|
@@ -41023,7 +41163,7 @@ var mathSpacingEntropyRule = createRule({
|
|
|
41023
41163
|
|
|
41024
41164
|
// src/rules/visual/naturalness-anomaly.ts
|
|
41025
41165
|
var MIN_LENGTH = 50;
|
|
41026
|
-
var DISTINCT_RATIO_FLOOR = 0.
|
|
41166
|
+
var DISTINCT_RATIO_FLOOR = 0.2;
|
|
41027
41167
|
var naturalnessAnomalyRule = createRule({
|
|
41028
41168
|
id: "visual/naturalness-anomaly",
|
|
41029
41169
|
category: "visual",
|
|
@@ -41068,6 +41208,9 @@ var naturalnessAnomalyRule = createRule({
|
|
|
41068
41208
|
var SCALE_TOLERANCE = 1e-3;
|
|
41069
41209
|
var radiusScaleViolationRule = createRule({
|
|
41070
41210
|
id: "visual/radius-scale-violation",
|
|
41211
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
41212
|
+
// until rewritten.
|
|
41213
|
+
defaultOff: true,
|
|
41071
41214
|
category: "visual",
|
|
41072
41215
|
severity: "medium",
|
|
41073
41216
|
aiSpecific: false,
|
|
@@ -41252,6 +41395,10 @@ var ALT_HANDLER_RE = /^(?:onClick|onPointerDown|onKey(?:Down|Up|Press))$/;
|
|
|
41252
41395
|
var ALT_ROLE_RE = /^(?:button|application|tab|menuitem)$/;
|
|
41253
41396
|
var draggingMovementsRule = createRule({
|
|
41254
41397
|
id: "wcag/dragging-movements",
|
|
41398
|
+
// v0.20.0 calibration: v8.5 = DORMANT but defaultOff: false
|
|
41399
|
+
// was a bug — DORMANT rules should be off by default. The
|
|
41400
|
+
// rule was contributing 0 useful fires. Disable.
|
|
41401
|
+
defaultOff: true,
|
|
41255
41402
|
category: "wcag",
|
|
41256
41403
|
severity: "medium",
|
|
41257
41404
|
aiSpecific: false,
|
|
@@ -41398,6 +41545,11 @@ function scanForMissingAlt(source) {
|
|
|
41398
41545
|
}
|
|
41399
41546
|
var missingAltRule = createRule({
|
|
41400
41547
|
id: "wcag/missing-alt",
|
|
41548
|
+
// v0.20.0 calibration: lift 1.0, recall 0.000. Lift of 1.0
|
|
41549
|
+
// means the rule has zero discriminative power — it fires
|
|
41550
|
+
// equally on positive and negative examples. Dead rule.
|
|
41551
|
+
// Disable until rewritten.
|
|
41552
|
+
defaultOff: true,
|
|
41401
41553
|
category: "wcag",
|
|
41402
41554
|
severity: "medium",
|
|
41403
41555
|
aiSpecific: false,
|
|
@@ -41618,13 +41770,18 @@ var builtinRules = [
|
|
|
41618
41770
|
unusedLocalRule,
|
|
41619
41771
|
unusedParameterRule,
|
|
41620
41772
|
brokenLinkRule,
|
|
41621
|
-
expiredCodeExampleRule,
|
|
41622
41773
|
staleFunctionReferenceRule,
|
|
41623
41774
|
stalePackageReferenceRule,
|
|
41624
41775
|
dupIdenticalBlockRule,
|
|
41625
41776
|
goErrorWrapWithoutContextRule,
|
|
41626
41777
|
goNilSliceVsEmptyRule,
|
|
41627
41778
|
goStructTagInconsistencyRule,
|
|
41779
|
+
javaArraylistVsLinkedlistRule,
|
|
41780
|
+
javaEmptyCatchBlockRule,
|
|
41781
|
+
javaLegacyDateApiRule,
|
|
41782
|
+
javaRawTypeOveruseRule,
|
|
41783
|
+
javaStringConcatLoopRule,
|
|
41784
|
+
javaSystemOutPrintlnRule,
|
|
41628
41785
|
gapMonopolyRule,
|
|
41629
41786
|
mathElementUniformityRule,
|
|
41630
41787
|
mathGridUniformityRule,
|
|
@@ -42338,24 +42495,6 @@ var signal_strength_default = {
|
|
|
42338
42495
|
_v8Verdict: "OK",
|
|
42339
42496
|
_v8Lift: 48.52
|
|
42340
42497
|
},
|
|
42341
|
-
"docs/expired-code-example": {
|
|
42342
|
-
recall: 0,
|
|
42343
|
-
fpRate: 0,
|
|
42344
|
-
ratio: 0,
|
|
42345
|
-
precision: 0,
|
|
42346
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42347
|
-
verdict: "INVERTED",
|
|
42348
|
-
_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).",
|
|
42349
|
-
aiSpecific: false,
|
|
42350
|
-
_v7Verdict: "DORMANT",
|
|
42351
|
-
_v7Lift: 1,
|
|
42352
|
-
_v7Recall: 0,
|
|
42353
|
-
_v7FpRate: 0,
|
|
42354
|
-
_v7Precision: 0,
|
|
42355
|
-
_v8Verdict: "INVERTED",
|
|
42356
|
-
_v8Lift: 0,
|
|
42357
|
-
defaultOff: true
|
|
42358
|
-
},
|
|
42359
42498
|
"docs/stale-function-reference": {
|
|
42360
42499
|
recall: 26e-4,
|
|
42361
42500
|
fpRate: 5e-4,
|
|
@@ -42462,6 +42601,114 @@ var signal_strength_default = {
|
|
|
42462
42601
|
_v8Lift: 1,
|
|
42463
42602
|
defaultOff: true
|
|
42464
42603
|
},
|
|
42604
|
+
"java/arraylist-vs-linkedlist": {
|
|
42605
|
+
recall: 0,
|
|
42606
|
+
fpRate: 0,
|
|
42607
|
+
ratio: 1,
|
|
42608
|
+
precision: 0,
|
|
42609
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42610
|
+
verdict: "DORMANT",
|
|
42611
|
+
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42612
|
+
aiSpecific: true,
|
|
42613
|
+
_v7Verdict: "DORMANT",
|
|
42614
|
+
_v7Lift: 1,
|
|
42615
|
+
_v7Recall: 0,
|
|
42616
|
+
_v7FpRate: 0,
|
|
42617
|
+
_v7Precision: 0,
|
|
42618
|
+
_v8Verdict: "DORMANT",
|
|
42619
|
+
_v8Lift: 1,
|
|
42620
|
+
defaultOff: true
|
|
42621
|
+
},
|
|
42622
|
+
"java/empty-catch-block": {
|
|
42623
|
+
recall: 0,
|
|
42624
|
+
fpRate: 0,
|
|
42625
|
+
ratio: 1,
|
|
42626
|
+
precision: 0,
|
|
42627
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42628
|
+
verdict: "DORMANT",
|
|
42629
|
+
_calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42630
|
+
aiSpecific: true,
|
|
42631
|
+
_v7Verdict: "DORMANT",
|
|
42632
|
+
_v7Lift: 1,
|
|
42633
|
+
_v7Recall: 0,
|
|
42634
|
+
_v7FpRate: 0,
|
|
42635
|
+
_v7Precision: 0,
|
|
42636
|
+
_v8Verdict: "DORMANT",
|
|
42637
|
+
_v8Lift: 1,
|
|
42638
|
+
defaultOff: true
|
|
42639
|
+
},
|
|
42640
|
+
"java/legacy-date-api": {
|
|
42641
|
+
recall: 0,
|
|
42642
|
+
fpRate: 0,
|
|
42643
|
+
ratio: 1,
|
|
42644
|
+
precision: 0,
|
|
42645
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42646
|
+
verdict: "DORMANT",
|
|
42647
|
+
_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.",
|
|
42648
|
+
aiSpecific: true,
|
|
42649
|
+
_v7Verdict: "DORMANT",
|
|
42650
|
+
_v7Lift: 1,
|
|
42651
|
+
_v7Recall: 0,
|
|
42652
|
+
_v7FpRate: 0,
|
|
42653
|
+
_v7Precision: 0,
|
|
42654
|
+
_v8Verdict: "DORMANT",
|
|
42655
|
+
_v8Lift: 1,
|
|
42656
|
+
defaultOff: true
|
|
42657
|
+
},
|
|
42658
|
+
"java/raw-type-overuse": {
|
|
42659
|
+
recall: 0,
|
|
42660
|
+
fpRate: 0,
|
|
42661
|
+
ratio: 1,
|
|
42662
|
+
precision: 0,
|
|
42663
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42664
|
+
verdict: "DORMANT",
|
|
42665
|
+
_calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
|
|
42666
|
+
aiSpecific: true,
|
|
42667
|
+
_v7Verdict: "DORMANT",
|
|
42668
|
+
_v7Lift: 1,
|
|
42669
|
+
_v7Recall: 0,
|
|
42670
|
+
_v7FpRate: 0,
|
|
42671
|
+
_v7Precision: 0,
|
|
42672
|
+
_v8Verdict: "DORMANT",
|
|
42673
|
+
_v8Lift: 1,
|
|
42674
|
+
defaultOff: true
|
|
42675
|
+
},
|
|
42676
|
+
"java/string-concat-loop": {
|
|
42677
|
+
recall: 0,
|
|
42678
|
+
fpRate: 0,
|
|
42679
|
+
ratio: 1,
|
|
42680
|
+
precision: 0,
|
|
42681
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42682
|
+
verdict: "DORMANT",
|
|
42683
|
+
_calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
|
|
42684
|
+
aiSpecific: true,
|
|
42685
|
+
_v7Verdict: "DORMANT",
|
|
42686
|
+
_v7Lift: 1,
|
|
42687
|
+
_v7Recall: 0,
|
|
42688
|
+
_v7FpRate: 0,
|
|
42689
|
+
_v7Precision: 0,
|
|
42690
|
+
_v8Verdict: "DORMANT",
|
|
42691
|
+
_v8Lift: 1,
|
|
42692
|
+
defaultOff: true
|
|
42693
|
+
},
|
|
42694
|
+
"java/system-out-println": {
|
|
42695
|
+
recall: 0,
|
|
42696
|
+
fpRate: 0,
|
|
42697
|
+
ratio: 1,
|
|
42698
|
+
precision: 0,
|
|
42699
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42700
|
+
verdict: "DORMANT",
|
|
42701
|
+
_calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42702
|
+
aiSpecific: true,
|
|
42703
|
+
_v7Verdict: "DORMANT",
|
|
42704
|
+
_v7Lift: 1,
|
|
42705
|
+
_v7Recall: 0,
|
|
42706
|
+
_v7FpRate: 0,
|
|
42707
|
+
_v7Precision: 0,
|
|
42708
|
+
_v8Verdict: "DORMANT",
|
|
42709
|
+
_v8Lift: 1,
|
|
42710
|
+
defaultOff: true
|
|
42711
|
+
},
|
|
42465
42712
|
"layout/forced-layout": {
|
|
42466
42713
|
recall: 0,
|
|
42467
42714
|
fpRate: 0,
|
|
@@ -43718,7 +43965,7 @@ function loadSignalStrength() {
|
|
|
43718
43965
|
function buildParserCacheConfig(cwd) {
|
|
43719
43966
|
const envVal = process.env.SLOP_AUDIT_CACHE;
|
|
43720
43967
|
const enabled = envVal === "1" || envVal === "true";
|
|
43721
|
-
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0,
|
|
43968
|
+
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0, import_node_path10.join)(cwd, ".slopbrick", "cache", "ast");
|
|
43722
43969
|
return { enabled, root };
|
|
43723
43970
|
}
|
|
43724
43971
|
function applyRuleOverrides(issues, rules) {
|
|
@@ -43736,7 +43983,7 @@ function applyRuleOverrides(issues, rules) {
|
|
|
43736
43983
|
}
|
|
43737
43984
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
43738
43985
|
const cache = buildParserCacheConfig(cwd);
|
|
43739
|
-
const ext = (0,
|
|
43986
|
+
const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
|
|
43740
43987
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
43741
43988
|
".swift",
|
|
43742
43989
|
".kt",
|