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.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]+)?)-(.+)$/;
|
|
@@ -35107,125 +35090,17 @@ var unusedParameterRule = createRule({
|
|
|
35107
35090
|
});
|
|
35108
35091
|
|
|
35109
35092
|
// 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
35093
|
var import_node_fs3 = require("fs");
|
|
35120
35094
|
var import_node_path3 = require("path");
|
|
35121
35095
|
|
|
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
|
|
35096
|
+
// src/engine/doc-freshness.ts
|
|
35154
35097
|
var import_node_fs2 = require("fs");
|
|
35155
35098
|
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
|
-
});
|
|
35099
|
+
var import_globby = require("globby");
|
|
35225
35100
|
|
|
35226
35101
|
// src/rules/docs/stale-function-reference.ts
|
|
35227
|
-
var
|
|
35228
|
-
var
|
|
35102
|
+
var import_node_fs = require("fs");
|
|
35103
|
+
var import_node_path = require("path");
|
|
35229
35104
|
var RESERVED = /* @__PURE__ */ new Set([
|
|
35230
35105
|
// JS reserved words
|
|
35231
35106
|
"true",
|
|
@@ -35798,29 +35673,29 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
|
|
|
35798
35673
|
var SOURCE_ROOTS = ["src", "lib", "app", "components"];
|
|
35799
35674
|
var CAP = 200;
|
|
35800
35675
|
function walk(dir, out, cap) {
|
|
35801
|
-
if (!(0,
|
|
35676
|
+
if (!(0, import_node_fs.existsSync)(dir) || out.length >= cap) return;
|
|
35802
35677
|
let entries;
|
|
35803
35678
|
try {
|
|
35804
|
-
entries = (0,
|
|
35679
|
+
entries = (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
|
|
35805
35680
|
} catch {
|
|
35806
35681
|
return;
|
|
35807
35682
|
}
|
|
35808
35683
|
for (const entry of entries) {
|
|
35809
35684
|
if (out.length >= cap) return;
|
|
35810
35685
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
35811
|
-
const full = (0,
|
|
35686
|
+
const full = (0, import_node_path.join)(dir, entry.name);
|
|
35812
35687
|
if (entry.isDirectory()) walk(full, out, cap);
|
|
35813
|
-
else if (entry.isFile() && SOURCE_EXTS.has((0,
|
|
35688
|
+
else if (entry.isFile() && SOURCE_EXTS.has((0, import_node_path.extname)(entry.name))) out.push(full);
|
|
35814
35689
|
}
|
|
35815
35690
|
}
|
|
35816
35691
|
function collectExports(cwd) {
|
|
35817
35692
|
const out = /* @__PURE__ */ new Set();
|
|
35818
35693
|
const files = [];
|
|
35819
|
-
for (const root of SOURCE_ROOTS) walk((0,
|
|
35694
|
+
for (const root of SOURCE_ROOTS) walk((0, import_node_path.join)(cwd, root), files, CAP);
|
|
35820
35695
|
for (const file of files) {
|
|
35821
35696
|
let source;
|
|
35822
35697
|
try {
|
|
35823
|
-
source = (0,
|
|
35698
|
+
source = (0, import_node_fs.readFileSync)(file, "utf-8");
|
|
35824
35699
|
} catch {
|
|
35825
35700
|
continue;
|
|
35826
35701
|
}
|
|
@@ -36083,38 +35958,6 @@ function extractInlineCodeSpans(source) {
|
|
|
36083
35958
|
}
|
|
36084
35959
|
return hits;
|
|
36085
35960
|
}
|
|
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
35961
|
function extractMarkdownLinks(source) {
|
|
36119
35962
|
const hits = [];
|
|
36120
35963
|
const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
@@ -36131,10 +35974,10 @@ function extractMarkdownLinks(source) {
|
|
|
36131
35974
|
}
|
|
36132
35975
|
function declaredPackages(cwd) {
|
|
36133
35976
|
const out = /* @__PURE__ */ new Set();
|
|
36134
|
-
const pkgPath = (0,
|
|
36135
|
-
if (!(0,
|
|
35977
|
+
const pkgPath = (0, import_node_path2.join)(cwd, "package.json");
|
|
35978
|
+
if (!(0, import_node_fs2.existsSync)(pkgPath)) return out;
|
|
36136
35979
|
try {
|
|
36137
|
-
const raw = (0,
|
|
35980
|
+
const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
|
|
36138
35981
|
const pkg = JSON.parse(raw);
|
|
36139
35982
|
for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
36140
35983
|
const v = pkg[k];
|
|
@@ -36164,7 +36007,7 @@ var brokenLinkRule = createRule({
|
|
|
36164
36007
|
const source = facts.v2?._source;
|
|
36165
36008
|
if (!source) return issues;
|
|
36166
36009
|
const links = extractMarkdownLinks(source);
|
|
36167
|
-
const docDir = (0,
|
|
36010
|
+
const docDir = (0, import_node_path3.dirname)((0, import_node_path3.resolve)(context.cwd, context.filePath));
|
|
36168
36011
|
for (const link of links) {
|
|
36169
36012
|
const target = link.target;
|
|
36170
36013
|
if (target.startsWith("http://") || target.startsWith("https://")) continue;
|
|
@@ -36174,8 +36017,8 @@ var brokenLinkRule = createRule({
|
|
|
36174
36017
|
if (target.startsWith("/")) continue;
|
|
36175
36018
|
const filePart = target.split("#")[0] ?? target;
|
|
36176
36019
|
if (filePart === "") continue;
|
|
36177
|
-
const resolved = (0,
|
|
36178
|
-
if ((0,
|
|
36020
|
+
const resolved = (0, import_node_path3.join)(docDir, filePart);
|
|
36021
|
+
if ((0, import_node_fs3.existsSync)(resolved)) continue;
|
|
36179
36022
|
issues.push({
|
|
36180
36023
|
ruleId: "docs/broken-link",
|
|
36181
36024
|
category: "docs",
|
|
@@ -36404,6 +36247,254 @@ var goStructTagInconsistencyRule = createRule({
|
|
|
36404
36247
|
}
|
|
36405
36248
|
});
|
|
36406
36249
|
|
|
36250
|
+
// src/rules/java/arraylist-vs-linkedlist.ts
|
|
36251
|
+
var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
|
|
36252
|
+
var javaArraylistVsLinkedlistRule = createRule({
|
|
36253
|
+
id: "java/arraylist-vs-linkedlist",
|
|
36254
|
+
category: "typo",
|
|
36255
|
+
severity: "low",
|
|
36256
|
+
aiSpecific: true,
|
|
36257
|
+
description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
|
|
36258
|
+
create(_context) {
|
|
36259
|
+
return {};
|
|
36260
|
+
},
|
|
36261
|
+
analyze(_context, facts) {
|
|
36262
|
+
const issues = [];
|
|
36263
|
+
const source = facts.v2?._source;
|
|
36264
|
+
if (!source) return issues;
|
|
36265
|
+
let m;
|
|
36266
|
+
NEW_LINKED_LIST_REGEX.lastIndex = 0;
|
|
36267
|
+
while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
|
|
36268
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36269
|
+
issues.push({
|
|
36270
|
+
ruleId: "java/arraylist-vs-linkedlist",
|
|
36271
|
+
category: "typo",
|
|
36272
|
+
severity: "low",
|
|
36273
|
+
aiSpecific: true,
|
|
36274
|
+
message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
|
|
36275
|
+
line,
|
|
36276
|
+
column: 1,
|
|
36277
|
+
advice: "Replace `new LinkedList<>()` with `new ArrayList<>()`. LinkedList is rarely the right choice (worse cache locality, 5x more memory per element, O(n) indexed access). Joshua Bloch (Effective Java, Item 28) recommends ArrayList unless you specifically need a Deque. AI agents default to LinkedList because of textbook examples. Reference: java/arraylist-vs-linkedlist v0.20."
|
|
36278
|
+
});
|
|
36279
|
+
}
|
|
36280
|
+
return issues;
|
|
36281
|
+
}
|
|
36282
|
+
});
|
|
36283
|
+
|
|
36284
|
+
// src/rules/java/empty-catch-block.ts
|
|
36285
|
+
var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
36286
|
+
var javaEmptyCatchBlockRule = createRule({
|
|
36287
|
+
id: "java/empty-catch-block",
|
|
36288
|
+
category: "logic",
|
|
36289
|
+
severity: "medium",
|
|
36290
|
+
aiSpecific: true,
|
|
36291
|
+
description: "Empty catch block \u2014 silently swallows exceptions",
|
|
36292
|
+
create(_context) {
|
|
36293
|
+
return {};
|
|
36294
|
+
},
|
|
36295
|
+
analyze(_context, facts) {
|
|
36296
|
+
const issues = [];
|
|
36297
|
+
const source = facts.v2?._source;
|
|
36298
|
+
if (!source) return issues;
|
|
36299
|
+
let m;
|
|
36300
|
+
SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
|
|
36301
|
+
while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
|
|
36302
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36303
|
+
issues.push({
|
|
36304
|
+
ruleId: "java/empty-catch-block",
|
|
36305
|
+
category: "logic",
|
|
36306
|
+
severity: "medium",
|
|
36307
|
+
aiSpecific: true,
|
|
36308
|
+
message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
|
|
36309
|
+
line,
|
|
36310
|
+
column: 1,
|
|
36311
|
+
advice: 'Log the exception (`log.error("...", e)`), re-throw it, or both. Empty catch blocks hide bugs. The pattern is common in AI-generated code that wants to look defensive. Reference: java/empty-catch-block v0.20.'
|
|
36312
|
+
});
|
|
36313
|
+
}
|
|
36314
|
+
return issues;
|
|
36315
|
+
}
|
|
36316
|
+
});
|
|
36317
|
+
|
|
36318
|
+
// src/rules/java/legacy-date-api.ts
|
|
36319
|
+
var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
|
|
36320
|
+
var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
|
|
36321
|
+
var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
|
|
36322
|
+
var javaLegacyDateApiRule = createRule({
|
|
36323
|
+
id: "java/legacy-date-api",
|
|
36324
|
+
category: "typo",
|
|
36325
|
+
severity: "low",
|
|
36326
|
+
aiSpecific: true,
|
|
36327
|
+
description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
|
|
36328
|
+
create(_context) {
|
|
36329
|
+
return {};
|
|
36330
|
+
},
|
|
36331
|
+
analyze(_context, facts) {
|
|
36332
|
+
const issues = [];
|
|
36333
|
+
const source = facts.v2?._source;
|
|
36334
|
+
if (!source) return issues;
|
|
36335
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
36336
|
+
let m;
|
|
36337
|
+
LEGACY_IMPORT_REGEX.lastIndex = 0;
|
|
36338
|
+
while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
|
|
36339
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36340
|
+
flagged.add(line);
|
|
36341
|
+
issues.push({
|
|
36342
|
+
ruleId: "java/legacy-date-api",
|
|
36343
|
+
category: "typo",
|
|
36344
|
+
severity: "low",
|
|
36345
|
+
aiSpecific: true,
|
|
36346
|
+
message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
|
|
36347
|
+
line,
|
|
36348
|
+
column: 1,
|
|
36349
|
+
advice: "Replace `java.util.Date` / `java.util.Calendar` with `java.time` (`LocalDate`, `LocalDateTime`, `Instant`, `ZonedDateTime`). java.time is immutable, thread-safe, and has a much better API. AI agents default to the legacy API because their training data predates Java 8 (2014). Reference: java/legacy-date-api v0.20."
|
|
36350
|
+
});
|
|
36351
|
+
}
|
|
36352
|
+
LEGACY_USAGE_REGEX.lastIndex = 0;
|
|
36353
|
+
while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
|
|
36354
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36355
|
+
if (flagged.has(line)) continue;
|
|
36356
|
+
issues.push({
|
|
36357
|
+
ruleId: "java/legacy-date-api",
|
|
36358
|
+
category: "typo",
|
|
36359
|
+
severity: "low",
|
|
36360
|
+
aiSpecific: true,
|
|
36361
|
+
message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
|
|
36362
|
+
line,
|
|
36363
|
+
column: 1,
|
|
36364
|
+
advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36365
|
+
});
|
|
36366
|
+
}
|
|
36367
|
+
CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
|
|
36368
|
+
while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
|
|
36369
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36370
|
+
if (flagged.has(line)) continue;
|
|
36371
|
+
issues.push({
|
|
36372
|
+
ruleId: "java/legacy-date-api",
|
|
36373
|
+
category: "typo",
|
|
36374
|
+
severity: "low",
|
|
36375
|
+
aiSpecific: true,
|
|
36376
|
+
message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
|
|
36377
|
+
line,
|
|
36378
|
+
column: 1,
|
|
36379
|
+
advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
|
|
36380
|
+
});
|
|
36381
|
+
}
|
|
36382
|
+
return issues;
|
|
36383
|
+
}
|
|
36384
|
+
});
|
|
36385
|
+
|
|
36386
|
+
// src/rules/java/raw-type-overuse.ts
|
|
36387
|
+
var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
|
|
36388
|
+
var javaRawTypeOveruseRule = createRule({
|
|
36389
|
+
id: "java/raw-type-overuse",
|
|
36390
|
+
category: "typo",
|
|
36391
|
+
severity: "low",
|
|
36392
|
+
aiSpecific: true,
|
|
36393
|
+
description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
|
|
36394
|
+
create(_context) {
|
|
36395
|
+
return {};
|
|
36396
|
+
},
|
|
36397
|
+
analyze(_context, facts) {
|
|
36398
|
+
const issues = [];
|
|
36399
|
+
const source = facts.v2?._source;
|
|
36400
|
+
if (!source) return issues;
|
|
36401
|
+
let m;
|
|
36402
|
+
RAW_TYPE_REGEX.lastIndex = 0;
|
|
36403
|
+
while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
|
|
36404
|
+
const typeName = m[1];
|
|
36405
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36406
|
+
issues.push({
|
|
36407
|
+
ruleId: "java/raw-type-overuse",
|
|
36408
|
+
category: "typo",
|
|
36409
|
+
severity: "low",
|
|
36410
|
+
aiSpecific: true,
|
|
36411
|
+
message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
|
|
36412
|
+
line,
|
|
36413
|
+
column: 1,
|
|
36414
|
+
advice: `Replace raw \`${typeName}\` with \`${typeName}<...>\`. Raw types disable generic type checking. Effective Java, Item 23: 'Don't use raw types in new code'. AI agents default to raw types when unsure of the correct generic parameters. Reference: java/raw-type-overuse v0.20.`
|
|
36415
|
+
});
|
|
36416
|
+
}
|
|
36417
|
+
return issues;
|
|
36418
|
+
}
|
|
36419
|
+
});
|
|
36420
|
+
|
|
36421
|
+
// src/rules/java/string-concat-loop.ts
|
|
36422
|
+
var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
|
|
36423
|
+
var javaStringConcatLoopRule = createRule({
|
|
36424
|
+
id: "java/string-concat-loop",
|
|
36425
|
+
category: "perf",
|
|
36426
|
+
severity: "low",
|
|
36427
|
+
aiSpecific: true,
|
|
36428
|
+
description: "String concatenation in a loop \u2014 use StringBuilder",
|
|
36429
|
+
create(_context) {
|
|
36430
|
+
return {};
|
|
36431
|
+
},
|
|
36432
|
+
analyze(_context, facts) {
|
|
36433
|
+
const issues = [];
|
|
36434
|
+
const source = facts.v2?._source;
|
|
36435
|
+
if (!source) return issues;
|
|
36436
|
+
if (!/\b(for|while|do)\b/.test(source)) return issues;
|
|
36437
|
+
let m;
|
|
36438
|
+
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
36439
|
+
while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
|
|
36440
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
36441
|
+
issues.push({
|
|
36442
|
+
ruleId: "java/string-concat-loop",
|
|
36443
|
+
category: "perf",
|
|
36444
|
+
severity: "low",
|
|
36445
|
+
aiSpecific: true,
|
|
36446
|
+
message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
|
|
36447
|
+
line,
|
|
36448
|
+
column: 1,
|
|
36449
|
+
advice: "Declare a `StringBuilder` outside the loop: `StringBuilder sb = new StringBuilder(); sb.append(...);` then `return sb.toString();` after the loop. String concatenation in a loop is O(n\xB2) \u2014 each iteration copies the prior string. AI agents concatenate strings in loops because of training-data examples. Reference: java/string-concat-loop v0.20."
|
|
36450
|
+
});
|
|
36451
|
+
}
|
|
36452
|
+
return issues;
|
|
36453
|
+
}
|
|
36454
|
+
});
|
|
36455
|
+
|
|
36456
|
+
// src/rules/java/system-out-println.ts
|
|
36457
|
+
var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
|
|
36458
|
+
var DEFAULT_THRESHOLD = 1;
|
|
36459
|
+
var javaSystemOutPrintlnRule = createRule({
|
|
36460
|
+
id: "java/system-out-println",
|
|
36461
|
+
category: "typo",
|
|
36462
|
+
severity: "low",
|
|
36463
|
+
aiSpecific: true,
|
|
36464
|
+
description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
|
|
36465
|
+
create(_context) {
|
|
36466
|
+
return { threshold: DEFAULT_THRESHOLD };
|
|
36467
|
+
},
|
|
36468
|
+
analyze(context, facts) {
|
|
36469
|
+
const issues = [];
|
|
36470
|
+
const source = facts.v2?._source;
|
|
36471
|
+
if (!source) return issues;
|
|
36472
|
+
const matches = [];
|
|
36473
|
+
let m;
|
|
36474
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
36475
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
36476
|
+
matches.push(m.index);
|
|
36477
|
+
}
|
|
36478
|
+
if (matches.length <= context.threshold) return issues;
|
|
36479
|
+
const cap = Math.min(matches.length, 10);
|
|
36480
|
+
for (let i = 0; i < cap; i++) {
|
|
36481
|
+
const idx = matches[i];
|
|
36482
|
+
const line = source.slice(0, idx).split("\n").length;
|
|
36483
|
+
issues.push({
|
|
36484
|
+
ruleId: "java/system-out-println",
|
|
36485
|
+
category: "typo",
|
|
36486
|
+
severity: "low",
|
|
36487
|
+
aiSpecific: true,
|
|
36488
|
+
message: `System.out.println at line ${line} \u2014 use a logger for production output`,
|
|
36489
|
+
line,
|
|
36490
|
+
column: 1,
|
|
36491
|
+
advice: "Replace with `private static final Logger log = LoggerFactory.getLogger(...);` then `log.info(...)`. AI agents default to println because their training data has countless textbook examples. Real Java code uses a logger. Reference: java/system-out-println v0.20."
|
|
36492
|
+
});
|
|
36493
|
+
}
|
|
36494
|
+
return issues;
|
|
36495
|
+
}
|
|
36496
|
+
});
|
|
36497
|
+
|
|
36407
36498
|
// src/rules/layout/gap-monopoly.ts
|
|
36408
36499
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36409
36500
|
var gapMonopolyRule = createRule({
|
|
@@ -36756,16 +36847,16 @@ var DEFAULT_CONFIG = {
|
|
|
36756
36847
|
};
|
|
36757
36848
|
|
|
36758
36849
|
// src/config/detect/monorepo.ts
|
|
36759
|
-
var
|
|
36760
|
-
var
|
|
36850
|
+
var import_node_fs4 = require("fs");
|
|
36851
|
+
var import_node_path4 = require("path");
|
|
36761
36852
|
|
|
36762
36853
|
// src/config/detect/styling.ts
|
|
36763
|
-
var
|
|
36764
|
-
var
|
|
36854
|
+
var import_node_fs5 = require("fs");
|
|
36855
|
+
var import_node_path5 = require("path");
|
|
36765
36856
|
|
|
36766
36857
|
// src/config/detect/stack.ts
|
|
36767
|
-
var
|
|
36768
|
-
var
|
|
36858
|
+
var import_node_fs6 = require("fs");
|
|
36859
|
+
var import_node_path6 = require("path");
|
|
36769
36860
|
|
|
36770
36861
|
// src/config/presets.ts
|
|
36771
36862
|
var REACT_ONLY_RULES = {
|
|
@@ -36814,8 +36905,8 @@ var FRAMEWORK_PRESETS = {
|
|
|
36814
36905
|
};
|
|
36815
36906
|
|
|
36816
36907
|
// src/config/load.ts
|
|
36817
|
-
var
|
|
36818
|
-
var
|
|
36908
|
+
var import_node_fs8 = require("fs");
|
|
36909
|
+
var import_node_path8 = require("path");
|
|
36819
36910
|
var import_node_module = require("module");
|
|
36820
36911
|
|
|
36821
36912
|
// src/engine/logger.ts
|
|
@@ -36837,6 +36928,10 @@ function setLoggerQuiet(quiet) {
|
|
|
36837
36928
|
logger = createLogger(quiet);
|
|
36838
36929
|
}
|
|
36839
36930
|
|
|
36931
|
+
// src/config/conventions.ts
|
|
36932
|
+
var import_node_fs7 = require("fs");
|
|
36933
|
+
var import_node_path7 = require("path");
|
|
36934
|
+
|
|
36840
36935
|
// src/config/init.ts
|
|
36841
36936
|
var STRICTNESS_PRESETS = {
|
|
36842
36937
|
strict: {
|
|
@@ -38202,7 +38297,6 @@ function scanForStringlyParams(paramText) {
|
|
|
38202
38297
|
}
|
|
38203
38298
|
|
|
38204
38299
|
// src/rules/rust/todo-macro.ts
|
|
38205
|
-
init_parser_rust();
|
|
38206
38300
|
var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
|
|
38207
38301
|
var rustTodoMacroRule = createRule({
|
|
38208
38302
|
id: "rust/todo-macro",
|
|
@@ -38338,7 +38432,6 @@ function collectReferencedNames(source) {
|
|
|
38338
38432
|
}
|
|
38339
38433
|
|
|
38340
38434
|
// src/rules/rust/unwrap-in-production.ts
|
|
38341
|
-
init_parser_rust();
|
|
38342
38435
|
var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
|
|
38343
38436
|
var rustUnwrapInProductionRule = createRule({
|
|
38344
38437
|
id: "rust/unwrap-in-production",
|
|
@@ -39475,7 +39568,7 @@ function looksRealistic(value) {
|
|
|
39475
39568
|
|
|
39476
39569
|
// src/rules/test/missing-edge-case.ts
|
|
39477
39570
|
var import_core4 = require("@swc/core");
|
|
39478
|
-
var
|
|
39571
|
+
var import_node_fs9 = require("fs");
|
|
39479
39572
|
var MAX_PER_FILE = 20;
|
|
39480
39573
|
var missingEdgeCaseRule = createRule({
|
|
39481
39574
|
id: "test/missing-edge-case",
|
|
@@ -39497,7 +39590,7 @@ var missingEdgeCaseRule = createRule({
|
|
|
39497
39590
|
const testFileSources = /* @__PURE__ */ new Map();
|
|
39498
39591
|
for (const testFile of discoverTestFiles(cwd)) {
|
|
39499
39592
|
try {
|
|
39500
|
-
testFileSources.set(testFile, (0,
|
|
39593
|
+
testFileSources.set(testFile, (0, import_node_fs9.readFileSync)(testFile, "utf-8"));
|
|
39501
39594
|
} catch {
|
|
39502
39595
|
}
|
|
39503
39596
|
}
|
|
@@ -39751,8 +39844,8 @@ function discoverTestFiles(cwd) {
|
|
|
39751
39844
|
const found = [];
|
|
39752
39845
|
for (const root of roots) {
|
|
39753
39846
|
const abs = `${cwd}/${root}`;
|
|
39754
|
-
if (!(0,
|
|
39755
|
-
walk2(abs, found,
|
|
39847
|
+
if (!(0, import_node_fs9.existsSync)(abs)) continue;
|
|
39848
|
+
walk2(abs, found, import_node_fs9.readdirSync, import_node_fs9.statSync);
|
|
39756
39849
|
if (found.length > 200) break;
|
|
39757
39850
|
}
|
|
39758
39851
|
return found;
|
|
@@ -41618,13 +41711,18 @@ var builtinRules = [
|
|
|
41618
41711
|
unusedLocalRule,
|
|
41619
41712
|
unusedParameterRule,
|
|
41620
41713
|
brokenLinkRule,
|
|
41621
|
-
expiredCodeExampleRule,
|
|
41622
41714
|
staleFunctionReferenceRule,
|
|
41623
41715
|
stalePackageReferenceRule,
|
|
41624
41716
|
dupIdenticalBlockRule,
|
|
41625
41717
|
goErrorWrapWithoutContextRule,
|
|
41626
41718
|
goNilSliceVsEmptyRule,
|
|
41627
41719
|
goStructTagInconsistencyRule,
|
|
41720
|
+
javaArraylistVsLinkedlistRule,
|
|
41721
|
+
javaEmptyCatchBlockRule,
|
|
41722
|
+
javaLegacyDateApiRule,
|
|
41723
|
+
javaRawTypeOveruseRule,
|
|
41724
|
+
javaStringConcatLoopRule,
|
|
41725
|
+
javaSystemOutPrintlnRule,
|
|
41628
41726
|
gapMonopolyRule,
|
|
41629
41727
|
mathElementUniformityRule,
|
|
41630
41728
|
mathGridUniformityRule,
|
|
@@ -42338,24 +42436,6 @@ var signal_strength_default = {
|
|
|
42338
42436
|
_v8Verdict: "OK",
|
|
42339
42437
|
_v8Lift: 48.52
|
|
42340
42438
|
},
|
|
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
42439
|
"docs/stale-function-reference": {
|
|
42360
42440
|
recall: 26e-4,
|
|
42361
42441
|
fpRate: 5e-4,
|
|
@@ -42462,6 +42542,114 @@ var signal_strength_default = {
|
|
|
42462
42542
|
_v8Lift: 1,
|
|
42463
42543
|
defaultOff: true
|
|
42464
42544
|
},
|
|
42545
|
+
"java/arraylist-vs-linkedlist": {
|
|
42546
|
+
recall: 0,
|
|
42547
|
+
fpRate: 0,
|
|
42548
|
+
ratio: 1,
|
|
42549
|
+
precision: 0,
|
|
42550
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42551
|
+
verdict: "DORMANT",
|
|
42552
|
+
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42553
|
+
aiSpecific: true,
|
|
42554
|
+
_v7Verdict: "DORMANT",
|
|
42555
|
+
_v7Lift: 1,
|
|
42556
|
+
_v7Recall: 0,
|
|
42557
|
+
_v7FpRate: 0,
|
|
42558
|
+
_v7Precision: 0,
|
|
42559
|
+
_v8Verdict: "DORMANT",
|
|
42560
|
+
_v8Lift: 1,
|
|
42561
|
+
defaultOff: true
|
|
42562
|
+
},
|
|
42563
|
+
"java/empty-catch-block": {
|
|
42564
|
+
recall: 0,
|
|
42565
|
+
fpRate: 0,
|
|
42566
|
+
ratio: 1,
|
|
42567
|
+
precision: 0,
|
|
42568
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42569
|
+
verdict: "DORMANT",
|
|
42570
|
+
_calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42571
|
+
aiSpecific: true,
|
|
42572
|
+
_v7Verdict: "DORMANT",
|
|
42573
|
+
_v7Lift: 1,
|
|
42574
|
+
_v7Recall: 0,
|
|
42575
|
+
_v7FpRate: 0,
|
|
42576
|
+
_v7Precision: 0,
|
|
42577
|
+
_v8Verdict: "DORMANT",
|
|
42578
|
+
_v8Lift: 1,
|
|
42579
|
+
defaultOff: true
|
|
42580
|
+
},
|
|
42581
|
+
"java/legacy-date-api": {
|
|
42582
|
+
recall: 0,
|
|
42583
|
+
fpRate: 0,
|
|
42584
|
+
ratio: 1,
|
|
42585
|
+
precision: 0,
|
|
42586
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42587
|
+
verdict: "DORMANT",
|
|
42588
|
+
_calibrationNote: "v0.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42589
|
+
aiSpecific: true,
|
|
42590
|
+
_v7Verdict: "DORMANT",
|
|
42591
|
+
_v7Lift: 1,
|
|
42592
|
+
_v7Recall: 0,
|
|
42593
|
+
_v7FpRate: 0,
|
|
42594
|
+
_v7Precision: 0,
|
|
42595
|
+
_v8Verdict: "DORMANT",
|
|
42596
|
+
_v8Lift: 1,
|
|
42597
|
+
defaultOff: true
|
|
42598
|
+
},
|
|
42599
|
+
"java/raw-type-overuse": {
|
|
42600
|
+
recall: 0,
|
|
42601
|
+
fpRate: 0,
|
|
42602
|
+
ratio: 1,
|
|
42603
|
+
precision: 0,
|
|
42604
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42605
|
+
verdict: "DORMANT",
|
|
42606
|
+
_calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
|
|
42607
|
+
aiSpecific: true,
|
|
42608
|
+
_v7Verdict: "DORMANT",
|
|
42609
|
+
_v7Lift: 1,
|
|
42610
|
+
_v7Recall: 0,
|
|
42611
|
+
_v7FpRate: 0,
|
|
42612
|
+
_v7Precision: 0,
|
|
42613
|
+
_v8Verdict: "DORMANT",
|
|
42614
|
+
_v8Lift: 1,
|
|
42615
|
+
defaultOff: true
|
|
42616
|
+
},
|
|
42617
|
+
"java/string-concat-loop": {
|
|
42618
|
+
recall: 0,
|
|
42619
|
+
fpRate: 0,
|
|
42620
|
+
ratio: 1,
|
|
42621
|
+
precision: 0,
|
|
42622
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42623
|
+
verdict: "DORMANT",
|
|
42624
|
+
_calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
|
|
42625
|
+
aiSpecific: true,
|
|
42626
|
+
_v7Verdict: "DORMANT",
|
|
42627
|
+
_v7Lift: 1,
|
|
42628
|
+
_v7Recall: 0,
|
|
42629
|
+
_v7FpRate: 0,
|
|
42630
|
+
_v7Precision: 0,
|
|
42631
|
+
_v8Verdict: "DORMANT",
|
|
42632
|
+
_v8Lift: 1,
|
|
42633
|
+
defaultOff: true
|
|
42634
|
+
},
|
|
42635
|
+
"java/system-out-println": {
|
|
42636
|
+
recall: 0,
|
|
42637
|
+
fpRate: 0,
|
|
42638
|
+
ratio: 1,
|
|
42639
|
+
precision: 0,
|
|
42640
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42641
|
+
verdict: "DORMANT",
|
|
42642
|
+
_calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
|
|
42643
|
+
aiSpecific: true,
|
|
42644
|
+
_v7Verdict: "DORMANT",
|
|
42645
|
+
_v7Lift: 1,
|
|
42646
|
+
_v7Recall: 0,
|
|
42647
|
+
_v7FpRate: 0,
|
|
42648
|
+
_v7Precision: 0,
|
|
42649
|
+
_v8Verdict: "DORMANT",
|
|
42650
|
+
_v8Lift: 1,
|
|
42651
|
+
defaultOff: true
|
|
42652
|
+
},
|
|
42465
42653
|
"layout/forced-layout": {
|
|
42466
42654
|
recall: 0,
|
|
42467
42655
|
fpRate: 0,
|
|
@@ -43718,7 +43906,7 @@ function loadSignalStrength() {
|
|
|
43718
43906
|
function buildParserCacheConfig(cwd) {
|
|
43719
43907
|
const envVal = process.env.SLOP_AUDIT_CACHE;
|
|
43720
43908
|
const enabled = envVal === "1" || envVal === "true";
|
|
43721
|
-
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0,
|
|
43909
|
+
const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0, import_node_path10.join)(cwd, ".slopbrick", "cache", "ast");
|
|
43722
43910
|
return { enabled, root };
|
|
43723
43911
|
}
|
|
43724
43912
|
function applyRuleOverrides(issues, rules) {
|
|
@@ -43736,7 +43924,7 @@ function applyRuleOverrides(issues, rules) {
|
|
|
43736
43924
|
}
|
|
43737
43925
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
43738
43926
|
const cache = buildParserCacheConfig(cwd);
|
|
43739
|
-
const ext = (0,
|
|
43927
|
+
const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
|
|
43740
43928
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
43741
43929
|
".swift",
|
|
43742
43930
|
".kt",
|