slopbrick 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/worker.cjs +787 -599
- package/dist/engine/worker.js +786 -599
- package/dist/index.cjs +3182 -2846
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3180 -2844
- 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]+)?)-(.+)$/;
|
|
@@ -35079,125 +35061,17 @@ var unusedParameterRule = createRule({
|
|
|
35079
35061
|
});
|
|
35080
35062
|
|
|
35081
35063
|
// src/rules/docs/broken-link.ts
|
|
35082
|
-
import { existsSync as
|
|
35083
|
-
import { dirname as
|
|
35064
|
+
import { existsSync as existsSync3 } from "fs";
|
|
35065
|
+
import { dirname as dirname3, join as join5, resolve } from "path";
|
|
35084
35066
|
|
|
35085
35067
|
// 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
|
|
35068
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
35069
|
+
import { join as join4, dirname as dirname2, relative } from "path";
|
|
35095
35070
|
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
35071
|
|
|
35198
35072
|
// src/rules/docs/stale-function-reference.ts
|
|
35199
|
-
import { readFileSync
|
|
35200
|
-
import { join as
|
|
35073
|
+
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
35074
|
+
import { join as join3, extname } from "path";
|
|
35201
35075
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
35202
35076
|
// JS reserved words
|
|
35203
35077
|
"true",
|
|
@@ -35770,7 +35644,7 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
|
|
|
35770
35644
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
35771
35645
|
var CAP = 200;
|
|
35772
35646
|
function walk(dir, out, cap) {
|
|
35773
|
-
if (!
|
|
35647
|
+
if (!existsSync(dir) || out.length >= cap) return;
|
|
35774
35648
|
let entries;
|
|
35775
35649
|
try {
|
|
35776
35650
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -35780,19 +35654,19 @@ function walk(dir, out, cap) {
|
|
|
35780
35654
|
for (const entry of entries) {
|
|
35781
35655
|
if (out.length >= cap) return;
|
|
35782
35656
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
35783
|
-
const full =
|
|
35657
|
+
const full = join3(dir, entry.name);
|
|
35784
35658
|
if (entry.isDirectory()) walk(full, out, cap);
|
|
35785
|
-
else if (entry.isFile() && SOURCE_EXTS.has(
|
|
35659
|
+
else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name))) out.push(full);
|
|
35786
35660
|
}
|
|
35787
35661
|
}
|
|
35788
35662
|
function collectExports(cwd) {
|
|
35789
35663
|
const out = /* @__PURE__ */ new Set();
|
|
35790
35664
|
const files = [];
|
|
35791
|
-
for (const root of SOURCE_ROOTS) walk(
|
|
35665
|
+
for (const root of SOURCE_ROOTS) walk(join3(cwd, root), files, CAP);
|
|
35792
35666
|
for (const file of files) {
|
|
35793
35667
|
let source;
|
|
35794
35668
|
try {
|
|
35795
|
-
source =
|
|
35669
|
+
source = readFileSync(file, "utf-8");
|
|
35796
35670
|
} catch {
|
|
35797
35671
|
continue;
|
|
35798
35672
|
}
|
|
@@ -36055,38 +35929,6 @@ function extractInlineCodeSpans(source) {
|
|
|
36055
35929
|
}
|
|
36056
35930
|
return hits;
|
|
36057
35931
|
}
|
|
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
35932
|
function extractMarkdownLinks(source) {
|
|
36091
35933
|
const hits = [];
|
|
36092
35934
|
const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
@@ -36103,10 +35945,10 @@ function extractMarkdownLinks(source) {
|
|
|
36103
35945
|
}
|
|
36104
35946
|
function declaredPackages(cwd) {
|
|
36105
35947
|
const out = /* @__PURE__ */ new Set();
|
|
36106
|
-
const pkgPath =
|
|
36107
|
-
if (!
|
|
35948
|
+
const pkgPath = join4(cwd, "package.json");
|
|
35949
|
+
if (!existsSync2(pkgPath)) return out;
|
|
36108
35950
|
try {
|
|
36109
|
-
const raw =
|
|
35951
|
+
const raw = readFileSync2(pkgPath, "utf-8");
|
|
36110
35952
|
const pkg = JSON.parse(raw);
|
|
36111
35953
|
for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
36112
35954
|
const v = pkg[k];
|
|
@@ -36136,7 +35978,7 @@ var brokenLinkRule = createRule({
|
|
|
36136
35978
|
const source = facts.v2?._source;
|
|
36137
35979
|
if (!source) return issues;
|
|
36138
35980
|
const links = extractMarkdownLinks(source);
|
|
36139
|
-
const docDir =
|
|
35981
|
+
const docDir = dirname3(resolve(context.cwd, context.filePath));
|
|
36140
35982
|
for (const link of links) {
|
|
36141
35983
|
const target = link.target;
|
|
36142
35984
|
if (target.startsWith("http://") || target.startsWith("https://")) continue;
|
|
@@ -36146,8 +35988,8 @@ var brokenLinkRule = createRule({
|
|
|
36146
35988
|
if (target.startsWith("/")) continue;
|
|
36147
35989
|
const filePart = target.split("#")[0] ?? target;
|
|
36148
35990
|
if (filePart === "") continue;
|
|
36149
|
-
const resolved =
|
|
36150
|
-
if (
|
|
35991
|
+
const resolved = join5(docDir, filePart);
|
|
35992
|
+
if (existsSync3(resolved)) continue;
|
|
36151
35993
|
issues.push({
|
|
36152
35994
|
ruleId: "docs/broken-link",
|
|
36153
35995
|
category: "docs",
|
|
@@ -36376,6 +36218,254 @@ var goStructTagInconsistencyRule = createRule({
|
|
|
36376
36218
|
}
|
|
36377
36219
|
});
|
|
36378
36220
|
|
|
36221
|
+
// src/rules/java/arraylist-vs-linkedlist.ts
|
|
36222
|
+
var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
|
|
36223
|
+
var javaArraylistVsLinkedlistRule = createRule({
|
|
36224
|
+
id: "java/arraylist-vs-linkedlist",
|
|
36225
|
+
category: "typo",
|
|
36226
|
+
severity: "low",
|
|
36227
|
+
aiSpecific: true,
|
|
36228
|
+
description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
|
|
36229
|
+
create(_context) {
|
|
36230
|
+
return {};
|
|
36231
|
+
},
|
|
36232
|
+
analyze(_context, facts) {
|
|
36233
|
+
const issues = [];
|
|
36234
|
+
const source = facts.v2?._source;
|
|
36235
|
+
if (!source) return issues;
|
|
36236
|
+
let m;
|
|
36237
|
+
NEW_LINKED_LIST_REGEX.lastIndex = 0;
|
|
36238
|
+
while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
|
|
36239
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36240
|
+
issues.push({
|
|
36241
|
+
ruleId: "java/arraylist-vs-linkedlist",
|
|
36242
|
+
category: "typo",
|
|
36243
|
+
severity: "low",
|
|
36244
|
+
aiSpecific: true,
|
|
36245
|
+
message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
|
|
36246
|
+
line,
|
|
36247
|
+
column: 1,
|
|
36248
|
+
advice: "Replace `new LinkedList<>()` with `new ArrayList<>()`. LinkedList is rarely the right choice (worse cache locality, 5x more memory per element, O(n) indexed access). Joshua Bloch (Effective Java, Item 28) recommends ArrayList unless you specifically need a Deque. AI agents default to LinkedList because of textbook examples. Reference: java/arraylist-vs-linkedlist v0.20."
|
|
36249
|
+
});
|
|
36250
|
+
}
|
|
36251
|
+
return issues;
|
|
36252
|
+
}
|
|
36253
|
+
});
|
|
36254
|
+
|
|
36255
|
+
// src/rules/java/empty-catch-block.ts
|
|
36256
|
+
var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
36257
|
+
var javaEmptyCatchBlockRule = createRule({
|
|
36258
|
+
id: "java/empty-catch-block",
|
|
36259
|
+
category: "logic",
|
|
36260
|
+
severity: "medium",
|
|
36261
|
+
aiSpecific: true,
|
|
36262
|
+
description: "Empty catch block \u2014 silently swallows exceptions",
|
|
36263
|
+
create(_context) {
|
|
36264
|
+
return {};
|
|
36265
|
+
},
|
|
36266
|
+
analyze(_context, facts) {
|
|
36267
|
+
const issues = [];
|
|
36268
|
+
const source = facts.v2?._source;
|
|
36269
|
+
if (!source) return issues;
|
|
36270
|
+
let m;
|
|
36271
|
+
SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
|
|
36272
|
+
while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
|
|
36273
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36274
|
+
issues.push({
|
|
36275
|
+
ruleId: "java/empty-catch-block",
|
|
36276
|
+
category: "logic",
|
|
36277
|
+
severity: "medium",
|
|
36278
|
+
aiSpecific: true,
|
|
36279
|
+
message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
|
|
36280
|
+
line,
|
|
36281
|
+
column: 1,
|
|
36282
|
+
advice: 'Log the exception (`log.error("...", e)`), re-throw it, or both. Empty catch blocks hide bugs. The pattern is common in AI-generated code that wants to look defensive. Reference: java/empty-catch-block v0.20.'
|
|
36283
|
+
});
|
|
36284
|
+
}
|
|
36285
|
+
return issues;
|
|
36286
|
+
}
|
|
36287
|
+
});
|
|
36288
|
+
|
|
36289
|
+
// src/rules/java/legacy-date-api.ts
|
|
36290
|
+
var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
|
|
36291
|
+
var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
|
|
36292
|
+
var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
|
|
36293
|
+
var javaLegacyDateApiRule = createRule({
|
|
36294
|
+
id: "java/legacy-date-api",
|
|
36295
|
+
category: "typo",
|
|
36296
|
+
severity: "low",
|
|
36297
|
+
aiSpecific: true,
|
|
36298
|
+
description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
|
|
36299
|
+
create(_context) {
|
|
36300
|
+
return {};
|
|
36301
|
+
},
|
|
36302
|
+
analyze(_context, facts) {
|
|
36303
|
+
const issues = [];
|
|
36304
|
+
const source = facts.v2?._source;
|
|
36305
|
+
if (!source) return issues;
|
|
36306
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
36307
|
+
let m;
|
|
36308
|
+
LEGACY_IMPORT_REGEX.lastIndex = 0;
|
|
36309
|
+
while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
|
|
36310
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36311
|
+
flagged.add(line);
|
|
36312
|
+
issues.push({
|
|
36313
|
+
ruleId: "java/legacy-date-api",
|
|
36314
|
+
category: "typo",
|
|
36315
|
+
severity: "low",
|
|
36316
|
+
aiSpecific: true,
|
|
36317
|
+
message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
|
|
36318
|
+
line,
|
|
36319
|
+
column: 1,
|
|
36320
|
+
advice: "Replace `java.util.Date` / `java.util.Calendar` with `java.time` (`LocalDate`, `LocalDateTime`, `Instant`, `ZonedDateTime`). java.time is immutable, thread-safe, and has a much better API. AI agents default to the legacy API because their training data predates Java 8 (2014). Reference: java/legacy-date-api v0.20."
|
|
36321
|
+
});
|
|
36322
|
+
}
|
|
36323
|
+
LEGACY_USAGE_REGEX.lastIndex = 0;
|
|
36324
|
+
while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
|
|
36325
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36326
|
+
if (flagged.has(line)) continue;
|
|
36327
|
+
issues.push({
|
|
36328
|
+
ruleId: "java/legacy-date-api",
|
|
36329
|
+
category: "typo",
|
|
36330
|
+
severity: "low",
|
|
36331
|
+
aiSpecific: true,
|
|
36332
|
+
message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
|
|
36333
|
+
line,
|
|
36334
|
+
column: 1,
|
|
36335
|
+
advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36336
|
+
});
|
|
36337
|
+
}
|
|
36338
|
+
CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
|
|
36339
|
+
while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
|
|
36340
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36341
|
+
if (flagged.has(line)) continue;
|
|
36342
|
+
issues.push({
|
|
36343
|
+
ruleId: "java/legacy-date-api",
|
|
36344
|
+
category: "typo",
|
|
36345
|
+
severity: "low",
|
|
36346
|
+
aiSpecific: true,
|
|
36347
|
+
message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
|
|
36348
|
+
line,
|
|
36349
|
+
column: 1,
|
|
36350
|
+
advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36351
|
+
});
|
|
36352
|
+
}
|
|
36353
|
+
return issues;
|
|
36354
|
+
}
|
|
36355
|
+
});
|
|
36356
|
+
|
|
36357
|
+
// src/rules/java/raw-type-overuse.ts
|
|
36358
|
+
var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
|
|
36359
|
+
var javaRawTypeOveruseRule = createRule({
|
|
36360
|
+
id: "java/raw-type-overuse",
|
|
36361
|
+
category: "typo",
|
|
36362
|
+
severity: "low",
|
|
36363
|
+
aiSpecific: true,
|
|
36364
|
+
description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
|
|
36365
|
+
create(_context) {
|
|
36366
|
+
return {};
|
|
36367
|
+
},
|
|
36368
|
+
analyze(_context, facts) {
|
|
36369
|
+
const issues = [];
|
|
36370
|
+
const source = facts.v2?._source;
|
|
36371
|
+
if (!source) return issues;
|
|
36372
|
+
let m;
|
|
36373
|
+
RAW_TYPE_REGEX.lastIndex = 0;
|
|
36374
|
+
while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
|
|
36375
|
+
const typeName = m[1];
|
|
36376
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36377
|
+
issues.push({
|
|
36378
|
+
ruleId: "java/raw-type-overuse",
|
|
36379
|
+
category: "typo",
|
|
36380
|
+
severity: "low",
|
|
36381
|
+
aiSpecific: true,
|
|
36382
|
+
message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
|
|
36383
|
+
line,
|
|
36384
|
+
column: 1,
|
|
36385
|
+
advice: `Replace raw \`${typeName}\` with \`${typeName}<...>\`. Raw types disable generic type checking. Effective Java, Item 23: 'Don't use raw types in new code'. AI agents default to raw types when unsure of the correct generic parameters. Reference: java/raw-type-overuse v0.20.`
|
|
36386
|
+
});
|
|
36387
|
+
}
|
|
36388
|
+
return issues;
|
|
36389
|
+
}
|
|
36390
|
+
});
|
|
36391
|
+
|
|
36392
|
+
// src/rules/java/string-concat-loop.ts
|
|
36393
|
+
var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
|
|
36394
|
+
var javaStringConcatLoopRule = createRule({
|
|
36395
|
+
id: "java/string-concat-loop",
|
|
36396
|
+
category: "perf",
|
|
36397
|
+
severity: "low",
|
|
36398
|
+
aiSpecific: true,
|
|
36399
|
+
description: "String concatenation in a loop \u2014 use StringBuilder",
|
|
36400
|
+
create(_context) {
|
|
36401
|
+
return {};
|
|
36402
|
+
},
|
|
36403
|
+
analyze(_context, facts) {
|
|
36404
|
+
const issues = [];
|
|
36405
|
+
const source = facts.v2?._source;
|
|
36406
|
+
if (!source) return issues;
|
|
36407
|
+
if (!/\b(for|while|do)\b/.test(source)) return issues;
|
|
36408
|
+
let m;
|
|
36409
|
+
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
36410
|
+
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
36411
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36412
|
+
issues.push({
|
|
36413
|
+
ruleId: "java/string-concat-loop",
|
|
36414
|
+
category: "perf",
|
|
36415
|
+
severity: "low",
|
|
36416
|
+
aiSpecific: true,
|
|
36417
|
+
message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
|
|
36418
|
+
line,
|
|
36419
|
+
column: 1,
|
|
36420
|
+
advice: "Declare a `StringBuilder` outside the loop: `StringBuilder sb = new StringBuilder(); sb.append(...);` then `return sb.toString();` after the loop. String concatenation in a loop is O(n\xB2) \u2014 each iteration copies the prior string. AI agents concatenate strings in loops because of training-data examples. Reference: java/string-concat-loop v0.20."
|
|
36421
|
+
});
|
|
36422
|
+
}
|
|
36423
|
+
return issues;
|
|
36424
|
+
}
|
|
36425
|
+
});
|
|
36426
|
+
|
|
36427
|
+
// src/rules/java/system-out-println.ts
|
|
36428
|
+
var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
|
|
36429
|
+
var DEFAULT_THRESHOLD = 1;
|
|
36430
|
+
var javaSystemOutPrintlnRule = createRule({
|
|
36431
|
+
id: "java/system-out-println",
|
|
36432
|
+
category: "typo",
|
|
36433
|
+
severity: "low",
|
|
36434
|
+
aiSpecific: true,
|
|
36435
|
+
description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
|
|
36436
|
+
create(_context) {
|
|
36437
|
+
return { threshold: DEFAULT_THRESHOLD };
|
|
36438
|
+
},
|
|
36439
|
+
analyze(context, facts) {
|
|
36440
|
+
const issues = [];
|
|
36441
|
+
const source = facts.v2?._source;
|
|
36442
|
+
if (!source) return issues;
|
|
36443
|
+
const matches = [];
|
|
36444
|
+
let m;
|
|
36445
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
36446
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
36447
|
+
matches.push(m.index);
|
|
36448
|
+
}
|
|
36449
|
+
if (matches.length <= context.threshold) return issues;
|
|
36450
|
+
const cap = Math.min(matches.length, 10);
|
|
36451
|
+
for (let i = 0; i < cap; i++) {
|
|
36452
|
+
const idx = matches[i];
|
|
36453
|
+
const line = source.slice(0, idx).split("\n").length;
|
|
36454
|
+
issues.push({
|
|
36455
|
+
ruleId: "java/system-out-println",
|
|
36456
|
+
category: "typo",
|
|
36457
|
+
severity: "low",
|
|
36458
|
+
aiSpecific: true,
|
|
36459
|
+
message: `System.out.println at line ${line} \u2014 use a logger for production output`,
|
|
36460
|
+
line,
|
|
36461
|
+
column: 1,
|
|
36462
|
+
advice: "Replace with `private static final Logger log = LoggerFactory.getLogger(...);` then `log.info(...)`. AI agents default to println because their training data has countless textbook examples. Real Java code uses a logger. Reference: java/system-out-println v0.20."
|
|
36463
|
+
});
|
|
36464
|
+
}
|
|
36465
|
+
return issues;
|
|
36466
|
+
}
|
|
36467
|
+
});
|
|
36468
|
+
|
|
36379
36469
|
// src/rules/layout/gap-monopoly.ts
|
|
36380
36470
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36381
36471
|
var gapMonopolyRule = createRule({
|
|
@@ -36728,16 +36818,16 @@ var DEFAULT_CONFIG = {
|
|
|
36728
36818
|
};
|
|
36729
36819
|
|
|
36730
36820
|
// src/config/detect/monorepo.ts
|
|
36731
|
-
import { existsSync as
|
|
36732
|
-
import { dirname as
|
|
36821
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
36822
|
+
import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
|
|
36733
36823
|
|
|
36734
36824
|
// src/config/detect/styling.ts
|
|
36735
|
-
import { existsSync as
|
|
36736
|
-
import { join as
|
|
36825
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
36826
|
+
import { join as join7, resolve as resolve3 } from "path";
|
|
36737
36827
|
|
|
36738
36828
|
// src/config/detect/stack.ts
|
|
36739
|
-
import { existsSync as
|
|
36740
|
-
import { extname as
|
|
36829
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
36830
|
+
import { extname as extname2, join as join8, resolve as resolve4 } from "path";
|
|
36741
36831
|
|
|
36742
36832
|
// src/config/presets.ts
|
|
36743
36833
|
var REACT_ONLY_RULES = {
|
|
@@ -36786,8 +36876,8 @@ var FRAMEWORK_PRESETS = {
|
|
|
36786
36876
|
};
|
|
36787
36877
|
|
|
36788
36878
|
// src/config/load.ts
|
|
36789
|
-
import { existsSync as existsSync8, readFileSync as
|
|
36790
|
-
import { dirname as
|
|
36879
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
36880
|
+
import { dirname as dirname5, extname as extname3, join as join10, resolve as resolve5 } from "path";
|
|
36791
36881
|
import { createRequire } from "module";
|
|
36792
36882
|
|
|
36793
36883
|
// src/engine/logger.ts
|
|
@@ -36809,6 +36899,10 @@ function setLoggerQuiet(quiet) {
|
|
|
36809
36899
|
logger = createLogger(quiet);
|
|
36810
36900
|
}
|
|
36811
36901
|
|
|
36902
|
+
// src/config/conventions.ts
|
|
36903
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
36904
|
+
import { join as join9 } from "path";
|
|
36905
|
+
|
|
36812
36906
|
// src/config/init.ts
|
|
36813
36907
|
var STRICTNESS_PRESETS = {
|
|
36814
36908
|
strict: {
|
|
@@ -38174,7 +38268,6 @@ function scanForStringlyParams(paramText) {
|
|
|
38174
38268
|
}
|
|
38175
38269
|
|
|
38176
38270
|
// src/rules/rust/todo-macro.ts
|
|
38177
|
-
init_parser_rust();
|
|
38178
38271
|
var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
|
|
38179
38272
|
var rustTodoMacroRule = createRule({
|
|
38180
38273
|
id: "rust/todo-macro",
|
|
@@ -38310,7 +38403,6 @@ function collectReferencedNames(source) {
|
|
|
38310
38403
|
}
|
|
38311
38404
|
|
|
38312
38405
|
// src/rules/rust/unwrap-in-production.ts
|
|
38313
|
-
init_parser_rust();
|
|
38314
38406
|
var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
|
|
38315
38407
|
var rustUnwrapInProductionRule = createRule({
|
|
38316
38408
|
id: "rust/unwrap-in-production",
|
|
@@ -39447,7 +39539,7 @@ function looksRealistic(value) {
|
|
|
39447
39539
|
|
|
39448
39540
|
// src/rules/test/missing-edge-case.ts
|
|
39449
39541
|
import { parseSync as parseSync2 } from "@swc/core";
|
|
39450
|
-
import { existsSync as existsSync9, readFileSync as
|
|
39542
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
39451
39543
|
var MAX_PER_FILE = 20;
|
|
39452
39544
|
var missingEdgeCaseRule = createRule({
|
|
39453
39545
|
id: "test/missing-edge-case",
|
|
@@ -39469,7 +39561,7 @@ var missingEdgeCaseRule = createRule({
|
|
|
39469
39561
|
const testFileSources = /* @__PURE__ */ new Map();
|
|
39470
39562
|
for (const testFile of discoverTestFiles(cwd)) {
|
|
39471
39563
|
try {
|
|
39472
|
-
testFileSources.set(testFile,
|
|
39564
|
+
testFileSources.set(testFile, readFileSync8(testFile, "utf-8"));
|
|
39473
39565
|
} catch {
|
|
39474
39566
|
}
|
|
39475
39567
|
}
|
|
@@ -41590,13 +41682,18 @@ var builtinRules = [
|
|
|
41590
41682
|
unusedLocalRule,
|
|
41591
41683
|
unusedParameterRule,
|
|
41592
41684
|
brokenLinkRule,
|
|
41593
|
-
expiredCodeExampleRule,
|
|
41594
41685
|
staleFunctionReferenceRule,
|
|
41595
41686
|
stalePackageReferenceRule,
|
|
41596
41687
|
dupIdenticalBlockRule,
|
|
41597
41688
|
goErrorWrapWithoutContextRule,
|
|
41598
41689
|
goNilSliceVsEmptyRule,
|
|
41599
41690
|
goStructTagInconsistencyRule,
|
|
41691
|
+
javaArraylistVsLinkedlistRule,
|
|
41692
|
+
javaEmptyCatchBlockRule,
|
|
41693
|
+
javaLegacyDateApiRule,
|
|
41694
|
+
javaRawTypeOveruseRule,
|
|
41695
|
+
javaStringConcatLoopRule,
|
|
41696
|
+
javaSystemOutPrintlnRule,
|
|
41600
41697
|
gapMonopolyRule,
|
|
41601
41698
|
mathElementUniformityRule,
|
|
41602
41699
|
mathGridUniformityRule,
|
|
@@ -42310,24 +42407,6 @@ var signal_strength_default = {
|
|
|
42310
42407
|
_v8Verdict: "OK",
|
|
42311
42408
|
_v8Lift: 48.52
|
|
42312
42409
|
},
|
|
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
42410
|
"docs/stale-function-reference": {
|
|
42332
42411
|
recall: 26e-4,
|
|
42333
42412
|
fpRate: 5e-4,
|
|
@@ -42434,6 +42513,114 @@ var signal_strength_default = {
|
|
|
42434
42513
|
_v8Lift: 1,
|
|
42435
42514
|
defaultOff: true
|
|
42436
42515
|
},
|
|
42516
|
+
"java/arraylist-vs-linkedlist": {
|
|
42517
|
+
recall: 0,
|
|
42518
|
+
fpRate: 0,
|
|
42519
|
+
ratio: 1,
|
|
42520
|
+
precision: 0,
|
|
42521
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42522
|
+
verdict: "DORMANT",
|
|
42523
|
+
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42524
|
+
aiSpecific: true,
|
|
42525
|
+
_v7Verdict: "DORMANT",
|
|
42526
|
+
_v7Lift: 1,
|
|
42527
|
+
_v7Recall: 0,
|
|
42528
|
+
_v7FpRate: 0,
|
|
42529
|
+
_v7Precision: 0,
|
|
42530
|
+
_v8Verdict: "DORMANT",
|
|
42531
|
+
_v8Lift: 1,
|
|
42532
|
+
defaultOff: true
|
|
42533
|
+
},
|
|
42534
|
+
"java/empty-catch-block": {
|
|
42535
|
+
recall: 0,
|
|
42536
|
+
fpRate: 0,
|
|
42537
|
+
ratio: 1,
|
|
42538
|
+
precision: 0,
|
|
42539
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42540
|
+
verdict: "DORMANT",
|
|
42541
|
+
_calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42542
|
+
aiSpecific: true,
|
|
42543
|
+
_v7Verdict: "DORMANT",
|
|
42544
|
+
_v7Lift: 1,
|
|
42545
|
+
_v7Recall: 0,
|
|
42546
|
+
_v7FpRate: 0,
|
|
42547
|
+
_v7Precision: 0,
|
|
42548
|
+
_v8Verdict: "DORMANT",
|
|
42549
|
+
_v8Lift: 1,
|
|
42550
|
+
defaultOff: true
|
|
42551
|
+
},
|
|
42552
|
+
"java/legacy-date-api": {
|
|
42553
|
+
recall: 0,
|
|
42554
|
+
fpRate: 0,
|
|
42555
|
+
ratio: 1,
|
|
42556
|
+
precision: 0,
|
|
42557
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42558
|
+
verdict: "DORMANT",
|
|
42559
|
+
_calibrationNote: "v0.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42560
|
+
aiSpecific: true,
|
|
42561
|
+
_v7Verdict: "DORMANT",
|
|
42562
|
+
_v7Lift: 1,
|
|
42563
|
+
_v7Recall: 0,
|
|
42564
|
+
_v7FpRate: 0,
|
|
42565
|
+
_v7Precision: 0,
|
|
42566
|
+
_v8Verdict: "DORMANT",
|
|
42567
|
+
_v8Lift: 1,
|
|
42568
|
+
defaultOff: true
|
|
42569
|
+
},
|
|
42570
|
+
"java/raw-type-overuse": {
|
|
42571
|
+
recall: 0,
|
|
42572
|
+
fpRate: 0,
|
|
42573
|
+
ratio: 1,
|
|
42574
|
+
precision: 0,
|
|
42575
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42576
|
+
verdict: "DORMANT",
|
|
42577
|
+
_calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
|
|
42578
|
+
aiSpecific: true,
|
|
42579
|
+
_v7Verdict: "DORMANT",
|
|
42580
|
+
_v7Lift: 1,
|
|
42581
|
+
_v7Recall: 0,
|
|
42582
|
+
_v7FpRate: 0,
|
|
42583
|
+
_v7Precision: 0,
|
|
42584
|
+
_v8Verdict: "DORMANT",
|
|
42585
|
+
_v8Lift: 1,
|
|
42586
|
+
defaultOff: true
|
|
42587
|
+
},
|
|
42588
|
+
"java/string-concat-loop": {
|
|
42589
|
+
recall: 0,
|
|
42590
|
+
fpRate: 0,
|
|
42591
|
+
ratio: 1,
|
|
42592
|
+
precision: 0,
|
|
42593
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42594
|
+
verdict: "DORMANT",
|
|
42595
|
+
_calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
|
|
42596
|
+
aiSpecific: true,
|
|
42597
|
+
_v7Verdict: "DORMANT",
|
|
42598
|
+
_v7Lift: 1,
|
|
42599
|
+
_v7Recall: 0,
|
|
42600
|
+
_v7FpRate: 0,
|
|
42601
|
+
_v7Precision: 0,
|
|
42602
|
+
_v8Verdict: "DORMANT",
|
|
42603
|
+
_v8Lift: 1,
|
|
42604
|
+
defaultOff: true
|
|
42605
|
+
},
|
|
42606
|
+
"java/system-out-println": {
|
|
42607
|
+
recall: 0,
|
|
42608
|
+
fpRate: 0,
|
|
42609
|
+
ratio: 1,
|
|
42610
|
+
precision: 0,
|
|
42611
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42612
|
+
verdict: "DORMANT",
|
|
42613
|
+
_calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42614
|
+
aiSpecific: true,
|
|
42615
|
+
_v7Verdict: "DORMANT",
|
|
42616
|
+
_v7Lift: 1,
|
|
42617
|
+
_v7Recall: 0,
|
|
42618
|
+
_v7FpRate: 0,
|
|
42619
|
+
_v7Precision: 0,
|
|
42620
|
+
_v8Verdict: "DORMANT",
|
|
42621
|
+
_v8Lift: 1,
|
|
42622
|
+
defaultOff: true
|
|
42623
|
+
},
|
|
42437
42624
|
"layout/forced-layout": {
|
|
42438
42625
|
recall: 0,
|
|
42439
42626
|
fpRate: 0,
|
|
@@ -43690,7 +43877,7 @@ function loadSignalStrength() {
|
|
|
43690
43877
|
function buildParserCacheConfig(cwd) {
|
|
43691
43878
|
const envVal = process.env.SLOP_AUDIT_CACHE;
|
|
43692
43879
|
const enabled = envVal === "1" || envVal === "true";
|
|
43693
|
-
const root = process.env.SLOP_AUDIT_CACHE_ROOT ??
|
|
43880
|
+
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? join11(cwd, ".slopbrick", "cache", "ast");
|
|
43694
43881
|
return { enabled, root };
|
|
43695
43882
|
}
|
|
43696
43883
|
function applyRuleOverrides(issues, rules) {
|
|
@@ -43708,7 +43895,7 @@ function applyRuleOverrides(issues, rules) {
|
|
|
43708
43895
|
}
|
|
43709
43896
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
43710
43897
|
const cache = buildParserCacheConfig(cwd);
|
|
43711
|
-
const ext =
|
|
43898
|
+
const ext = extname4(filePath).toLowerCase();
|
|
43712
43899
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
43713
43900
|
".swift",
|
|
43714
43901
|
".kt",
|