@kevinrabun/judges 3.1.1 → 3.3.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.
@@ -0,0 +1,955 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Tree-sitter AST — Unified real syntax-tree analysis for all languages
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // Uses web-tree-sitter (WASM-based, zero native deps) to parse source code
5
+ // into a full syntax tree, then walks the tree to extract function metrics,
6
+ // dead code, deep nesting, type-safety issues, and imports.
7
+ //
8
+ // Supports: TypeScript, JavaScript, Python, Go, Rust, Java, C#, C++
9
+ //
10
+ // Graceful degradation: if tree-sitter WASM grammars aren't available at
11
+ // runtime, the caller can fall back to the structural parser.
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ import { createRequire } from "node:module";
14
+ import { fileURLToPath } from "node:url";
15
+ import { dirname, join } from "node:path";
16
+ const require = createRequire(import.meta.url);
17
+ // ─── Lazy Initialization ────────────────────────────────────────────────────
18
+ let initPromise = null;
19
+ let parserModule = null;
20
+ // Grammar file name mapping
21
+ const GRAMMAR_FILES = {
22
+ typescript: "tree-sitter-typescript.wasm",
23
+ javascript: "tree-sitter-typescript.wasm", // TS grammar is a superset of JS
24
+ python: "tree-sitter-python.wasm",
25
+ go: "tree-sitter-go.wasm",
26
+ rust: "tree-sitter-rust.wasm",
27
+ java: "tree-sitter-java.wasm",
28
+ csharp: "tree-sitter-c_sharp.wasm",
29
+ cpp: "tree-sitter-cpp.wasm",
30
+ };
31
+ // Cached language instances
32
+ const languageCache = new Map();
33
+ // Resolve grammar directory relative to this module
34
+ const __dirname = dirname(fileURLToPath(import.meta.url));
35
+ // In development: src/ast/ → ../../grammars/
36
+ // In dist: dist/ast/ → ../../grammars/
37
+ const GRAMMAR_DIR = join(__dirname, "..", "..", "grammars");
38
+ async function ensureInit() {
39
+ if (initPromise)
40
+ return initPromise;
41
+ initPromise = (async () => {
42
+ try {
43
+ const mod = require("web-tree-sitter");
44
+ await mod.Parser.init();
45
+ parserModule = mod;
46
+ return true;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ })();
52
+ return initPromise;
53
+ }
54
+ async function getLanguage(lang) {
55
+ if (languageCache.has(lang))
56
+ return languageCache.get(lang);
57
+ const file = GRAMMAR_FILES[lang];
58
+ if (!file)
59
+ return null;
60
+ try {
61
+ const grammar = await parserModule.Language.load(join(GRAMMAR_DIR, file));
62
+ languageCache.set(lang, grammar);
63
+ return grammar;
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ // ─── Public API ──────────────────────────────────────────────────────────────
70
+ /**
71
+ * Check whether tree-sitter analysis is available for a given language.
72
+ * Must be called (and awaited) before analyzeWithTreeSitter.
73
+ */
74
+ export async function isTreeSitterAvailable(lang) {
75
+ const ready = await ensureInit();
76
+ if (!ready)
77
+ return false;
78
+ const grammar = await getLanguage(lang);
79
+ return grammar !== null;
80
+ }
81
+ /**
82
+ * Synchronous readiness check — returns true only if tree-sitter's WASM
83
+ * runtime AND the grammar for `lang` have already been loaded into memory.
84
+ * This is safe to call from synchronous code paths; if the async init
85
+ * hasn't finished yet, it simply returns false and the caller falls back
86
+ * to the structural parser.
87
+ */
88
+ export function isTreeSitterReadySync(lang) {
89
+ return parserModule !== null && languageCache.has(lang);
90
+ }
91
+ /**
92
+ * Synchronous tree-sitter analysis. Can ONLY be called when
93
+ * isTreeSitterReadySync(lang) returns true (i.e. parser module and grammar
94
+ * are already loaded). parser.parse() is synchronous in web-tree-sitter
95
+ * once the WASM runtime and grammar are in memory.
96
+ *
97
+ * Returns the same CodeStructure interface as analyzeWithTreeSitter.
98
+ * Throws if preconditions are not met.
99
+ */
100
+ export function analyzeWithTreeSitterSync(code, language) {
101
+ if (!parserModule)
102
+ throw new Error("Tree-sitter not initialized");
103
+ const grammar = languageCache.get(language);
104
+ if (!grammar)
105
+ throw new Error(`Tree-sitter grammar for ${language} not loaded`);
106
+ return parseAndAnalyze(code, language, grammar);
107
+ }
108
+ /**
109
+ * Analyse source code using tree-sitter's real syntax tree.
110
+ * Returns the same CodeStructure interface as the TypeScript and
111
+ * structural parsers — but with much higher precision for non-JS/TS languages.
112
+ *
113
+ * IMPORTANT: Call isTreeSitterAvailable(lang) first. If it returns false,
114
+ * fall back to analyzeStructurally().
115
+ */
116
+ export async function analyzeWithTreeSitter(code, language) {
117
+ if (!parserModule)
118
+ throw new Error("Tree-sitter not initialized");
119
+ const grammar = await getLanguage(language);
120
+ if (!grammar)
121
+ throw new Error(`Tree-sitter grammar for ${language} not available`);
122
+ return parseAndAnalyze(code, language, grammar);
123
+ }
124
+ /**
125
+ * Shared parsing + analysis logic used by both sync and async entry points.
126
+ */
127
+ function parseAndAnalyze(code, language, grammar) {
128
+ const parser = new parserModule.Parser();
129
+ parser.setLanguage(grammar);
130
+ const tree = parser.parse(code);
131
+ const root = tree.rootNode;
132
+ const lines = code.split("\n");
133
+ // Extract all analysis data from the tree
134
+ const functions = extractFunctions(root, language);
135
+ const deadCodeLines = detectDeadCode(root, language);
136
+ const deepNestLines = detectDeepNesting(root, language);
137
+ const typeAnyLines = detectWeakTypes(root, language);
138
+ const imports = extractImports(root, language);
139
+ const classes = extractClasses(root, language);
140
+ // Compute file-level metrics
141
+ const fileCyclomaticComplexity = functions.reduce((sum, f) => sum + f.cyclomaticComplexity, 0) || 1;
142
+ const maxNestingDepth = functions.reduce((max, f) => Math.max(max, f.maxNestingDepth), 0);
143
+ return {
144
+ language,
145
+ totalLines: lines.length,
146
+ functions,
147
+ fileCyclomaticComplexity,
148
+ maxNestingDepth,
149
+ deadCodeLines,
150
+ deepNestLines,
151
+ typeAnyLines,
152
+ imports,
153
+ classes: classes.length > 0 ? classes : undefined,
154
+ };
155
+ }
156
+ // ─── Function Extraction ────────────────────────────────────────────────────
157
+ /** Node types that represent function/method definitions per language */
158
+ const FUNCTION_NODE_TYPES = {
159
+ typescript: [
160
+ "function_declaration",
161
+ "method_definition",
162
+ "arrow_function",
163
+ "function_expression",
164
+ "generator_function_declaration",
165
+ "generator_function",
166
+ ],
167
+ javascript: [
168
+ "function_declaration",
169
+ "method_definition",
170
+ "arrow_function",
171
+ "function_expression",
172
+ "generator_function_declaration",
173
+ "generator_function",
174
+ ],
175
+ python: ["function_definition"],
176
+ go: ["function_declaration", "method_declaration"],
177
+ rust: ["function_item"],
178
+ java: ["method_declaration", "constructor_declaration"],
179
+ csharp: ["method_declaration", "constructor_declaration", "local_function_statement"],
180
+ cpp: ["function_definition"],
181
+ };
182
+ function extractFunctions(root, language) {
183
+ const funcTypes = FUNCTION_NODE_TYPES[language] || [];
184
+ const functions = [];
185
+ const classRanges = extractClassRanges(root, language);
186
+ walkTree(root, (node) => {
187
+ if (funcTypes.includes(node.type)) {
188
+ const info = analyzeFunctionNode(node, language, classRanges);
189
+ if (info)
190
+ functions.push(info);
191
+ }
192
+ });
193
+ return functions;
194
+ }
195
+ function extractClassRanges(root, language) {
196
+ const classTypes = CLASS_NODE_TYPES[language] || [];
197
+ const ranges = [];
198
+ walkTree(root, (node) => {
199
+ if (classTypes.includes(node.type)) {
200
+ const nameNode = node.childForFieldName("name");
201
+ if (nameNode) {
202
+ ranges.push({
203
+ name: nameNode.text,
204
+ startLine: node.startPosition.row + 1,
205
+ endLine: node.endPosition.row + 1,
206
+ });
207
+ }
208
+ }
209
+ });
210
+ return ranges;
211
+ }
212
+ function analyzeFunctionNode(node, language, classRanges) {
213
+ // Get function name
214
+ const nameNode = node.childForFieldName("name");
215
+ let name = nameNode?.text || "<anonymous>";
216
+ // C++: name is inside the declarator chain
217
+ // function_definition → declarator (function_declarator) → declarator (identifier / qualified_identifier)
218
+ if (language === "cpp" && name === "<anonymous>") {
219
+ const decl = node.childForFieldName("declarator");
220
+ if (decl) {
221
+ const nameDecl = decl.childForFieldName("declarator");
222
+ if (nameDecl) {
223
+ name = nameDecl.text;
224
+ }
225
+ }
226
+ }
227
+ // TypeScript/JavaScript: arrow functions and function expressions get their
228
+ // name from the parent variable_declarator or property assignment
229
+ if ((language === "typescript" || language === "javascript") && name === "<anonymous>" && node.parent) {
230
+ if (node.parent.type === "variable_declarator") {
231
+ const nameChild = node.parent.childForFieldName("name");
232
+ if (nameChild)
233
+ name = nameChild.text;
234
+ }
235
+ else if (node.parent.type === "pair" || node.parent.type === "property_assignment") {
236
+ const key = node.parent.childForFieldName("key");
237
+ if (key)
238
+ name = key.text;
239
+ }
240
+ }
241
+ // For Go method_declaration, extract receiver type
242
+ if (language === "go" && node.type === "method_declaration") {
243
+ const receiver = node.childForFieldName("receiver");
244
+ if (receiver) {
245
+ // Extract the type from the receiver parameter list
246
+ const typeNode = findFirstByType(receiver, "type_identifier");
247
+ if (typeNode) {
248
+ name = `${typeNode.text}.${name}`;
249
+ }
250
+ }
251
+ }
252
+ // Check if function is inside a class
253
+ const startLine = node.startPosition.row + 1;
254
+ const endLine = node.endPosition.row + 1;
255
+ const containingClass = classRanges.find((c) => startLine >= c.startLine && endLine <= c.endLine);
256
+ if (containingClass && language !== "go") {
257
+ name = `${containingClass.name}.${name}`;
258
+ }
259
+ // Count parameters
260
+ const paramCount = countParameters(node, language);
261
+ // Compute cyclomatic complexity
262
+ const complexity = computeCyclomaticComplexity(node, language);
263
+ // Compute max nesting depth
264
+ const maxNesting = computeMaxNesting(node, language, 0);
265
+ // Check for decorators (Python, Java, C#)
266
+ const decorators = extractDecorators(node, language);
267
+ // Check for async
268
+ const isAsync = checkIsAsync(node, language);
269
+ const info = {
270
+ name,
271
+ startLine,
272
+ endLine,
273
+ lineCount: endLine - startLine + 1,
274
+ parameterCount: paramCount,
275
+ cyclomaticComplexity: complexity,
276
+ maxNestingDepth: maxNesting,
277
+ };
278
+ if (decorators.length > 0)
279
+ info.decorators = decorators;
280
+ if (containingClass)
281
+ info.className = containingClass.name;
282
+ if (isAsync)
283
+ info.isAsync = true;
284
+ return info;
285
+ }
286
+ // ─── Parameter Counting ─────────────────────────────────────────────────────
287
+ function countParameters(funcNode, language) {
288
+ let paramsNode = null;
289
+ switch (language) {
290
+ case "typescript":
291
+ case "javascript":
292
+ paramsNode = funcNode.childForFieldName("parameters");
293
+ if (!paramsNode)
294
+ return 0;
295
+ return paramsNode.namedChildren.filter((c) => c.type === "required_parameter" || c.type === "optional_parameter" || c.type === "rest_parameter").length;
296
+ case "python":
297
+ paramsNode = funcNode.childForFieldName("parameters");
298
+ if (!paramsNode)
299
+ return 0;
300
+ // Count identifier children, excluding 'self' and 'cls'
301
+ return paramsNode.namedChildren.filter((c) => {
302
+ if (c.type === "identifier" && (c.text === "self" || c.text === "cls"))
303
+ return false;
304
+ // Also handle typed_parameter, typed_default_parameter, etc.
305
+ if (c.type === "identifier" ||
306
+ c.type === "default_parameter" ||
307
+ c.type === "typed_parameter" ||
308
+ c.type === "typed_default_parameter" ||
309
+ c.type === "list_splat_pattern" ||
310
+ c.type === "dictionary_splat_pattern") {
311
+ // For typed_parameter, check if it's self/cls
312
+ if (c.type === "typed_parameter") {
313
+ const nameChild = c.namedChildren[0];
314
+ if (nameChild && (nameChild.text === "self" || nameChild.text === "cls"))
315
+ return false;
316
+ }
317
+ return true;
318
+ }
319
+ return false;
320
+ }).length;
321
+ case "go":
322
+ paramsNode = funcNode.childForFieldName("parameters");
323
+ if (!paramsNode)
324
+ return 0;
325
+ return paramsNode.namedChildren.filter((c) => c.type === "parameter_declaration").length;
326
+ case "rust":
327
+ paramsNode = funcNode.childForFieldName("parameters");
328
+ if (!paramsNode)
329
+ return 0;
330
+ return paramsNode.namedChildren.filter((c) => c.type === "parameter" || c.type === "self_parameter").length;
331
+ case "java":
332
+ paramsNode = funcNode.childForFieldName("parameters");
333
+ if (!paramsNode)
334
+ return 0;
335
+ return paramsNode.namedChildren.filter((c) => c.type === "formal_parameter" || c.type === "spread_parameter")
336
+ .length;
337
+ case "csharp":
338
+ paramsNode = funcNode.childForFieldName("parameters");
339
+ if (!paramsNode)
340
+ return 0;
341
+ return paramsNode.namedChildren.filter((c) => c.type === "parameter").length;
342
+ case "cpp": {
343
+ // C++: parameters sit inside function_definition → declarator (function_declarator) → parameters
344
+ const declarator = funcNode.childForFieldName("declarator");
345
+ if (!declarator)
346
+ return 0;
347
+ paramsNode = declarator.childForFieldName("parameters");
348
+ if (!paramsNode)
349
+ return 0;
350
+ return paramsNode.namedChildren.filter((c) => c.type === "parameter_declaration" || c.type === "variadic_parameter_declaration").length;
351
+ }
352
+ default:
353
+ return 0;
354
+ }
355
+ }
356
+ // ─── Cyclomatic Complexity ──────────────────────────────────────────────────
357
+ // CC = 1 + number of decision points
358
+ const DECISION_NODE_TYPES = {
359
+ typescript: new Set([
360
+ "if_statement",
361
+ "for_statement",
362
+ "for_in_statement",
363
+ "while_statement",
364
+ "do_statement",
365
+ "switch_case",
366
+ "catch_clause",
367
+ "ternary_expression",
368
+ ]),
369
+ javascript: new Set([
370
+ "if_statement",
371
+ "for_statement",
372
+ "for_in_statement",
373
+ "while_statement",
374
+ "do_statement",
375
+ "switch_case",
376
+ "catch_clause",
377
+ "ternary_expression",
378
+ ]),
379
+ python: new Set([
380
+ "if_statement",
381
+ "elif_clause",
382
+ "for_statement",
383
+ "while_statement",
384
+ "except_clause",
385
+ "conditional_expression",
386
+ "for_in_clause",
387
+ // Boolean operators — each 'and'/'or' is a decision point
388
+ "boolean_operator",
389
+ ]),
390
+ go: new Set(["if_statement", "for_statement", "expression_case", "default_case", "type_case", "communication_case"]),
391
+ rust: new Set(["if_expression", "for_expression", "while_expression", "loop_expression", "match_arm"]),
392
+ java: new Set([
393
+ "if_statement",
394
+ "for_statement",
395
+ "enhanced_for_statement",
396
+ "while_statement",
397
+ "do_statement",
398
+ "catch_clause",
399
+ "switch_block_statement_group",
400
+ "ternary_expression",
401
+ ]),
402
+ csharp: new Set([
403
+ "if_statement",
404
+ "for_statement",
405
+ "for_each_statement",
406
+ "while_statement",
407
+ "do_statement",
408
+ "catch_clause",
409
+ "switch_section",
410
+ "conditional_expression",
411
+ ]),
412
+ cpp: new Set([
413
+ "if_statement",
414
+ "for_statement",
415
+ "for_range_loop",
416
+ "while_statement",
417
+ "do_statement",
418
+ "case_statement",
419
+ "catch_clause",
420
+ "conditional_expression",
421
+ ]),
422
+ };
423
+ // Binary operators that add to complexity (&&, ||)
424
+ const LOGICAL_OPS = new Set(["&&", "||", "and", "or"]);
425
+ function computeCyclomaticComplexity(funcNode, language) {
426
+ let complexity = 1; // base path
427
+ const decisionTypes = DECISION_NODE_TYPES[language] || new Set();
428
+ walkTree(funcNode, (node) => {
429
+ if (decisionTypes.has(node.type)) {
430
+ complexity++;
431
+ }
432
+ // Check binary expressions for logical operators (&&, ||)
433
+ if (node.type === "binary_expression") {
434
+ const op = node.children.find((c) => c.type === "&&" || c.type === "||" || c.text === "&&" || c.text === "||");
435
+ if (op)
436
+ complexity++;
437
+ }
438
+ });
439
+ return complexity;
440
+ }
441
+ // ─── Nesting Depth ──────────────────────────────────────────────────────────
442
+ const NESTING_NODE_TYPES = {
443
+ typescript: new Set([
444
+ "if_statement",
445
+ "for_statement",
446
+ "for_in_statement",
447
+ "while_statement",
448
+ "do_statement",
449
+ "switch_statement",
450
+ "try_statement",
451
+ "arrow_function",
452
+ "function_expression",
453
+ ]),
454
+ javascript: new Set([
455
+ "if_statement",
456
+ "for_statement",
457
+ "for_in_statement",
458
+ "while_statement",
459
+ "do_statement",
460
+ "switch_statement",
461
+ "try_statement",
462
+ "arrow_function",
463
+ "function_expression",
464
+ ]),
465
+ python: new Set([
466
+ "if_statement",
467
+ "for_statement",
468
+ "while_statement",
469
+ "with_statement",
470
+ "try_statement",
471
+ "except_clause",
472
+ "for_in_clause",
473
+ "function_definition",
474
+ "class_definition",
475
+ ]),
476
+ go: new Set([
477
+ "if_statement",
478
+ "for_statement",
479
+ "select_statement",
480
+ "type_switch_statement",
481
+ "expression_switch_statement",
482
+ "func_literal",
483
+ ]),
484
+ rust: new Set([
485
+ "if_expression",
486
+ "for_expression",
487
+ "while_expression",
488
+ "loop_expression",
489
+ "match_expression",
490
+ "closure_expression",
491
+ ]),
492
+ java: new Set([
493
+ "if_statement",
494
+ "for_statement",
495
+ "enhanced_for_statement",
496
+ "while_statement",
497
+ "do_statement",
498
+ "try_statement",
499
+ "switch_expression",
500
+ "lambda_expression",
501
+ ]),
502
+ csharp: new Set([
503
+ "if_statement",
504
+ "for_statement",
505
+ "for_each_statement",
506
+ "while_statement",
507
+ "do_statement",
508
+ "try_statement",
509
+ "switch_statement",
510
+ "lambda_expression",
511
+ ]),
512
+ cpp: new Set([
513
+ "if_statement",
514
+ "for_statement",
515
+ "for_range_loop",
516
+ "while_statement",
517
+ "do_statement",
518
+ "try_statement",
519
+ "switch_statement",
520
+ "lambda_expression",
521
+ ]),
522
+ };
523
+ function computeMaxNesting(node, language, currentDepth) {
524
+ const nestingTypes = NESTING_NODE_TYPES[language] || new Set();
525
+ let maxDepth = currentDepth;
526
+ for (const child of node.namedChildren) {
527
+ let childDepth = currentDepth;
528
+ if (nestingTypes.has(child.type)) {
529
+ childDepth = currentDepth + 1;
530
+ if (childDepth > maxDepth)
531
+ maxDepth = childDepth;
532
+ }
533
+ const subMax = computeMaxNesting(child, language, childDepth);
534
+ if (subMax > maxDepth)
535
+ maxDepth = subMax;
536
+ }
537
+ return maxDepth;
538
+ }
539
+ // ─── Dead Code Detection ────────────────────────────────────────────────────
540
+ /** Node types that represent terminal statements (control flow never continues past them) */
541
+ const TERMINAL_TYPES = {
542
+ typescript: new Set(["return_statement", "throw_statement", "break_statement", "continue_statement"]),
543
+ javascript: new Set(["return_statement", "throw_statement", "break_statement", "continue_statement"]),
544
+ python: new Set(["return_statement", "raise_statement", "break_statement", "continue_statement"]),
545
+ go: new Set(["return_statement", "break_statement", "continue_statement"]),
546
+ rust: new Set(["return_expression", "break_expression", "continue_expression"]),
547
+ java: new Set(["return_statement", "throw_statement", "break_statement", "continue_statement"]),
548
+ csharp: new Set(["return_statement", "throw_statement", "break_statement", "continue_statement"]),
549
+ cpp: new Set(["return_statement", "throw_statement", "break_statement", "continue_statement"]),
550
+ };
551
+ /** Node types that represent blocks containing sequential statements */
552
+ const BLOCK_TYPES = {
553
+ typescript: new Set(["statement_block"]),
554
+ javascript: new Set(["statement_block"]),
555
+ python: new Set(["block"]),
556
+ go: new Set(["block"]),
557
+ rust: new Set(["block"]),
558
+ java: new Set(["block"]),
559
+ csharp: new Set(["block"]),
560
+ cpp: new Set(["compound_statement"]),
561
+ };
562
+ function detectDeadCode(root, language) {
563
+ const deadLines = [];
564
+ const terminalTypes = TERMINAL_TYPES[language] || new Set();
565
+ const blockTypes = BLOCK_TYPES[language] || new Set();
566
+ walkTree(root, (node) => {
567
+ if (!blockTypes.has(node.type))
568
+ return;
569
+ const children = node.namedChildren;
570
+ let foundTerminal = false;
571
+ for (const child of children) {
572
+ if (foundTerminal) {
573
+ // Everything after a terminal statement is dead code
574
+ for (let line = child.startPosition.row + 1; line <= child.endPosition.row + 1; line++) {
575
+ deadLines.push(line);
576
+ }
577
+ }
578
+ // Check if this child IS a terminal or CONTAINS a bare terminal
579
+ // (only direct children, not nested in sub-blocks)
580
+ if (terminalTypes.has(child.type)) {
581
+ foundTerminal = true;
582
+ }
583
+ // For expression_statement wrapping a return (Rust)
584
+ if (child.type === "expression_statement") {
585
+ const expr = child.namedChildren[0];
586
+ if (expr && terminalTypes.has(expr.type)) {
587
+ foundTerminal = true;
588
+ }
589
+ }
590
+ }
591
+ });
592
+ return [...new Set(deadLines)].sort((a, b) => a - b);
593
+ }
594
+ // ─── Deep Nesting Detection ─────────────────────────────────────────────────
595
+ function detectDeepNesting(root, language) {
596
+ const deepLines = [];
597
+ const nestingTypes = NESTING_NODE_TYPES[language] || new Set();
598
+ const threshold = 4; // Depth > 4 is "deep"
599
+ function walk(node, depth) {
600
+ for (const child of node.namedChildren) {
601
+ let childDepth = depth;
602
+ if (nestingTypes.has(child.type)) {
603
+ childDepth = depth + 1;
604
+ }
605
+ if (childDepth > threshold) {
606
+ // Mark all lines in this deeply-nested node
607
+ for (let line = child.startPosition.row + 1; line <= child.endPosition.row + 1; line++) {
608
+ deepLines.push(line);
609
+ }
610
+ }
611
+ walk(child, childDepth);
612
+ }
613
+ }
614
+ walk(root, 0);
615
+ return [...new Set(deepLines)].sort((a, b) => a - b);
616
+ }
617
+ // ─── Weak Type Detection ────────────────────────────────────────────────────
618
+ const WEAK_TYPE_PATTERNS = {
619
+ typescript: (node) => {
620
+ // 'any' keyword in type annotations
621
+ if (node.type === "predefined_type" && node.text === "any")
622
+ return true;
623
+ if (node.type === "type_identifier" && node.text === "any")
624
+ return true;
625
+ return false;
626
+ },
627
+ javascript: () => false, // JS has no static type annotations
628
+ python: (node) => {
629
+ // typing.Any or just Any in type annotations
630
+ if (node.type === "type" || node.type === "annotation") {
631
+ return node.text.includes("Any");
632
+ }
633
+ return false;
634
+ },
635
+ go: (node) => {
636
+ // interface{} or any keyword
637
+ if (node.type === "interface_type") {
638
+ // Empty interface
639
+ return node.namedChildren.length === 0;
640
+ }
641
+ if (node.type === "type_identifier" && node.text === "any")
642
+ return true;
643
+ return false;
644
+ },
645
+ rust: (node) => {
646
+ // unsafe blocks and unsafe function declarations
647
+ if (node.type === "unsafe_block")
648
+ return true;
649
+ // unsafe fn ... — the function_item's text starts with "unsafe"
650
+ if (node.type === "function_item" && node.text.trimStart().startsWith("unsafe "))
651
+ return true;
652
+ if (node.type === "type_cast_expression") {
653
+ return node.text.includes("*const") || node.text.includes("*mut");
654
+ }
655
+ return false;
656
+ },
657
+ java: (node) => {
658
+ // Object type, Class<?>
659
+ if (node.type === "type_identifier" && node.text === "Object")
660
+ return true;
661
+ if (node.type === "generic_type" && node.text.includes("Class<?>"))
662
+ return true;
663
+ return false;
664
+ },
665
+ csharp: (node) => {
666
+ // dynamic, object
667
+ if (node.type === "predefined_type" && (node.text === "dynamic" || node.text === "object")) {
668
+ return true;
669
+ }
670
+ if (node.type === "identifier" && node.text === "dynamic")
671
+ return true;
672
+ return false;
673
+ },
674
+ cpp: (node) => {
675
+ // void* pointers (unsafe), auto keyword (type-erased)
676
+ if (node.type === "pointer_declarator") {
677
+ const parent = node.parent;
678
+ if (parent && parent.text.includes("void"))
679
+ return true;
680
+ }
681
+ if (node.type === "auto" || (node.type === "primitive_type" && node.text === "auto"))
682
+ return true;
683
+ return false;
684
+ },
685
+ };
686
+ function detectWeakTypes(root, language) {
687
+ const weakLines = [];
688
+ const checker = WEAK_TYPE_PATTERNS[language];
689
+ if (!checker)
690
+ return weakLines;
691
+ walkTree(root, (node) => {
692
+ if (checker(node)) {
693
+ weakLines.push(node.startPosition.row + 1);
694
+ }
695
+ });
696
+ return [...new Set(weakLines)].sort((a, b) => a - b);
697
+ }
698
+ // ─── Import Extraction ──────────────────────────────────────────────────────
699
+ const IMPORT_NODE_TYPES = {
700
+ typescript: ["import_statement"],
701
+ javascript: ["import_statement"],
702
+ python: ["import_statement", "import_from_statement"],
703
+ go: ["import_declaration"],
704
+ rust: ["use_declaration"],
705
+ java: ["import_declaration"],
706
+ csharp: ["using_directive"],
707
+ cpp: ["preproc_include"],
708
+ };
709
+ function extractImports(root, language) {
710
+ const imports = [];
711
+ const importTypes = IMPORT_NODE_TYPES[language] || [];
712
+ walkTree(root, (node) => {
713
+ if (!importTypes.includes(node.type))
714
+ return;
715
+ switch (language) {
716
+ case "typescript":
717
+ case "javascript":
718
+ // import { foo } from "module"; import "module"; import * as x from "module"
719
+ {
720
+ const source = node.childForFieldName("source");
721
+ if (source) {
722
+ imports.push(source.text.replace(/['"]/g, ""));
723
+ }
724
+ }
725
+ break;
726
+ case "python":
727
+ if (node.type === "import_statement") {
728
+ // import os, import os.path
729
+ for (const child of node.namedChildren) {
730
+ if (child.type === "dotted_name" || child.type === "aliased_import") {
731
+ const name = child.type === "aliased_import" ? child.childForFieldName("name")?.text || child.text : child.text;
732
+ if (name)
733
+ imports.push(name);
734
+ }
735
+ }
736
+ }
737
+ else if (node.type === "import_from_statement") {
738
+ // from flask import Flask
739
+ const moduleNode = node.childForFieldName("module_name");
740
+ if (moduleNode)
741
+ imports.push(moduleNode.text);
742
+ }
743
+ break;
744
+ case "go":
745
+ // import "fmt" or import ( "fmt" "net/http" )
746
+ walkTree(node, (child) => {
747
+ if (child.type === "import_spec" || child.type === "interpreted_string_literal") {
748
+ const text = child.text.replace(/"/g, "");
749
+ if (text && text !== "(" && text !== ")")
750
+ imports.push(text);
751
+ }
752
+ });
753
+ break;
754
+ case "rust":
755
+ // use std::io; use crate::module_name;
756
+ {
757
+ const pathNode = node.namedChildren.find((c) => c.type === "scoped_identifier" ||
758
+ c.type === "identifier" ||
759
+ c.type === "use_wildcard" ||
760
+ c.type === "use_list" ||
761
+ c.type === "scoped_use_list");
762
+ if (pathNode) {
763
+ // Extract the root crate/module name
764
+ const fullPath = pathNode.text;
765
+ const rootModule = fullPath.split("::")[0];
766
+ if (rootModule)
767
+ imports.push(rootModule);
768
+ }
769
+ }
770
+ break;
771
+ case "java":
772
+ // import com.example.Foo;
773
+ {
774
+ const nameNode = node.namedChildren.find((c) => c.type === "scoped_identifier" || c.type === "identifier");
775
+ if (nameNode)
776
+ imports.push(nameNode.text);
777
+ }
778
+ break;
779
+ case "csharp":
780
+ // using System.IO;
781
+ {
782
+ const nameNode = node.namedChildren.find((c) => c.type === "qualified_name" || c.type === "identifier");
783
+ if (nameNode)
784
+ imports.push(nameNode.text);
785
+ }
786
+ break;
787
+ case "cpp":
788
+ // #include <header> or #include "header"
789
+ {
790
+ const pathNode = node.namedChildren.find((c) => c.type === "string_literal" || c.type === "system_lib_string");
791
+ if (pathNode) {
792
+ imports.push(pathNode.text.replace(/[<>"]/g, ""));
793
+ }
794
+ }
795
+ break;
796
+ }
797
+ });
798
+ // TypeScript/JavaScript: also detect require("module") calls
799
+ if (language === "typescript" || language === "javascript") {
800
+ walkTree(root, (node) => {
801
+ if (node.type !== "call_expression")
802
+ return;
803
+ const fn = node.childForFieldName("function");
804
+ if (!fn || fn.text !== "require")
805
+ return;
806
+ const args = node.childForFieldName("arguments");
807
+ if (!args)
808
+ return;
809
+ const firstArg = args.namedChildren[0];
810
+ if (firstArg && (firstArg.type === "string" || firstArg.type === "template_string")) {
811
+ imports.push(firstArg.text.replace(/['"]/g, ""));
812
+ }
813
+ });
814
+ }
815
+ return imports;
816
+ }
817
+ // ─── Class Extraction ───────────────────────────────────────────────────────
818
+ const CLASS_NODE_TYPES = {
819
+ typescript: ["class_declaration"],
820
+ javascript: ["class_declaration"],
821
+ python: ["class_definition"],
822
+ go: ["type_declaration"],
823
+ rust: ["struct_item", "enum_item"],
824
+ java: ["class_declaration", "interface_declaration", "enum_declaration"],
825
+ csharp: ["class_declaration", "struct_declaration", "interface_declaration", "enum_declaration"],
826
+ cpp: ["class_specifier", "struct_specifier"],
827
+ };
828
+ function extractClasses(root, language) {
829
+ const classes = [];
830
+ const classTypes = CLASS_NODE_TYPES[language] || [];
831
+ walkTree(root, (node) => {
832
+ if (!classTypes.includes(node.type))
833
+ return;
834
+ if (language === "go" && node.type === "type_declaration") {
835
+ // Only count struct types: type Foo struct { ... }
836
+ const spec = node.namedChildren.find((c) => c.type === "type_spec");
837
+ if (spec) {
838
+ const typeBody = spec.childForFieldName("type");
839
+ if (typeBody && typeBody.type === "struct_type") {
840
+ const nameNode = spec.childForFieldName("name");
841
+ if (nameNode)
842
+ classes.push(nameNode.text);
843
+ }
844
+ }
845
+ return;
846
+ }
847
+ const nameNode = node.childForFieldName("name");
848
+ if (nameNode)
849
+ classes.push(nameNode.text);
850
+ });
851
+ return classes;
852
+ }
853
+ // ─── Decorator / Annotation Extraction ──────────────────────────────────────
854
+ function extractDecorators(funcNode, language) {
855
+ const decorators = [];
856
+ switch (language) {
857
+ case "typescript": {
858
+ // TypeScript decorators are similar to Python
859
+ const parent = funcNode.parent;
860
+ if (parent) {
861
+ for (const child of parent.namedChildren) {
862
+ if (child.type === "decorator" && child.endPosition.row < funcNode.startPosition.row) {
863
+ decorators.push(child.text.replace(/^@/, "").split("(")[0]);
864
+ }
865
+ }
866
+ }
867
+ break;
868
+ }
869
+ case "python": {
870
+ // Decorators are siblings before the function_definition, but in the
871
+ // tree-sitter grammar they're children of a decorated_definition parent.
872
+ const parent = funcNode.parent;
873
+ if (parent && parent.type === "decorated_definition") {
874
+ for (const child of parent.namedChildren) {
875
+ if (child.type === "decorator") {
876
+ // Extract decorator name (without the @)
877
+ const text = child.text.replace(/^@/, "").split("(")[0];
878
+ decorators.push(text);
879
+ }
880
+ }
881
+ }
882
+ break;
883
+ }
884
+ case "java": {
885
+ // Annotations are modifiers before the method
886
+ const modifiers = funcNode.childForFieldName("modifiers") || funcNode.childForFieldName("modifier");
887
+ if (modifiers) {
888
+ for (const child of modifiers.namedChildren) {
889
+ if (child.type === "marker_annotation" || child.type === "annotation") {
890
+ decorators.push(child.text.replace(/^@/, "").split("(")[0]);
891
+ }
892
+ }
893
+ }
894
+ break;
895
+ }
896
+ case "csharp": {
897
+ // Attribute lists before the method
898
+ const parent = funcNode.parent;
899
+ if (parent) {
900
+ for (const child of parent.namedChildren) {
901
+ if (child.type === "attribute_list" && child.endPosition.row < funcNode.startPosition.row) {
902
+ decorators.push(child.text.replace(/[\[\]]/g, "").split("(")[0]);
903
+ }
904
+ }
905
+ }
906
+ break;
907
+ }
908
+ }
909
+ return decorators;
910
+ }
911
+ // ─── Async Detection ────────────────────────────────────────────────────────
912
+ function checkIsAsync(funcNode, language) {
913
+ switch (language) {
914
+ case "typescript":
915
+ case "javascript":
916
+ // async keyword is a direct child of the function node
917
+ return funcNode.children.some((c) => c.type === "async");
918
+ case "python":
919
+ // In Python tree-sitter, async functions have type "function_definition"
920
+ // but the parent is a "decorated_definition" or the text starts with "async"
921
+ return funcNode.text.trimStart().startsWith("async ");
922
+ case "rust":
923
+ // async fn
924
+ return funcNode.text.trimStart().startsWith("async ");
925
+ case "java":
926
+ case "csharp": {
927
+ // Check modifiers for 'async' keyword
928
+ const modifiers = funcNode.childForFieldName("modifiers");
929
+ if (modifiers) {
930
+ return modifiers.children.some((c) => c.text === "async");
931
+ }
932
+ return false;
933
+ }
934
+ default:
935
+ return false;
936
+ }
937
+ }
938
+ // ─── Tree Walking Helper ────────────────────────────────────────────────────
939
+ function walkTree(node, callback) {
940
+ callback(node);
941
+ for (const child of node.children) {
942
+ walkTree(child, callback);
943
+ }
944
+ }
945
+ function findFirstByType(node, type) {
946
+ if (node.type === type)
947
+ return node;
948
+ for (const child of node.children) {
949
+ const found = findFirstByType(child, type);
950
+ if (found)
951
+ return found;
952
+ }
953
+ return null;
954
+ }
955
+ //# sourceMappingURL=tree-sitter-ast.js.map