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.
@@ -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 import_node_path11 = require("path");
444
- var import_node_path12 = require("path");
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/discover.ts
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 import_node_fs4 = require("fs");
35228
- var import_node_path4 = require("path");
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, import_node_fs4.existsSync)(dir) || out.length >= cap) return;
35676
+ if (!(0, import_node_fs.existsSync)(dir) || out.length >= cap) return;
35802
35677
  let entries;
35803
35678
  try {
35804
- entries = (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true });
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, import_node_path4.join)(dir, entry.name);
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, import_node_path4.extname)(entry.name))) out.push(full);
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, import_node_path4.join)(cwd, root), files, CAP);
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, import_node_fs4.readFileSync)(file, "utf-8");
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, import_node_path5.join)(cwd, "package.json");
36135
- if (!(0, import_node_fs5.existsSync)(pkgPath)) return out;
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, import_node_fs5.readFileSync)(pkgPath, "utf-8");
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, import_node_path6.dirname)((0, import_node_path6.resolve)(context.cwd, context.filePath));
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, import_node_path6.join)(docDir, filePart);
36178
- if ((0, import_node_fs6.existsSync)(resolved)) continue;
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 import_node_fs7 = require("fs");
36760
- var import_node_path7 = require("path");
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 import_node_fs8 = require("fs");
36764
- var import_node_path8 = require("path");
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 import_node_fs9 = require("fs");
36768
- var import_node_path9 = require("path");
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 import_node_fs10 = require("fs");
36818
- var import_node_path10 = require("path");
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 import_node_fs11 = require("fs");
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, import_node_fs11.readFileSync)(testFile, "utf-8"));
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, import_node_fs11.existsSync)(abs)) continue;
39755
- walk2(abs, found, import_node_fs11.readdirSync, import_node_fs11.statSync);
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, import_node_path12.join)(cwd, ".slopbrick", "cache", "ast");
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, import_node_path11.extname)(filePath).toLowerCase();
43927
+ const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
43740
43928
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
43741
43929
  ".swift",
43742
43930
  ".kt",