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.
@@ -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 extname6 } from "path";
416
- import { join as join12 } from "path";
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 existsSync4 } from "fs";
35083
- import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
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 readFileSync5, existsSync as existsSync3 } from "fs";
35087
- import { join as join6, dirname as dirname3, relative as relative2 } from "path";
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 as readFileSync4, readdirSync, existsSync as existsSync2 } from "fs";
35200
- import { join as join5, extname as extname3 } from "path";
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 (!existsSync2(dir) || out.length >= cap) return;
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 = join5(dir, entry.name);
35657
+ const full = join3(dir, entry.name);
35784
35658
  if (entry.isDirectory()) walk(full, out, cap);
35785
- else if (entry.isFile() && SOURCE_EXTS.has(extname3(entry.name))) out.push(full);
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(join5(cwd, root), files, CAP);
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 = readFileSync4(file, "utf-8");
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 = join6(cwd, "package.json");
36107
- if (!existsSync3(pkgPath)) return out;
35948
+ const pkgPath = join4(cwd, "package.json");
35949
+ if (!existsSync2(pkgPath)) return out;
36108
35950
  try {
36109
- const raw = readFileSync5(pkgPath, "utf-8");
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 = dirname4(resolve2(context.cwd, context.filePath));
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 = join7(docDir, filePart);
36150
- if (existsSync4(resolved)) continue;
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 existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync } from "fs";
36732
- import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
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 existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
36736
- import { join as join9, resolve as resolve4 } from "path";
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 existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
36740
- import { extname as extname4, join as join10, resolve as resolve5 } from "path";
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 readFileSync9 } from "fs";
36790
- import { dirname as dirname6, extname as extname5, join as join11, resolve as resolve6 } from "path";
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 readFileSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
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, readFileSync10(testFile, "utf-8"));
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 ?? join12(cwd, ".slopbrick", "cache", "ast");
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 = extname6(filePath).toLowerCase();
43898
+ const ext = extname4(filePath).toLowerCase();
43712
43899
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
43713
43900
  ".swift",
43714
43901
  ".kt",