gitnexus 1.4.8 → 1.4.9

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.
Files changed (211) hide show
  1. package/README.md +7 -0
  2. package/dist/cli/index-repo.d.ts +15 -0
  3. package/dist/cli/index-repo.js +115 -0
  4. package/dist/cli/index.js +11 -2
  5. package/dist/cli/setup.js +12 -9
  6. package/dist/cli/wiki.d.ts +4 -0
  7. package/dist/cli/wiki.js +174 -53
  8. package/dist/config/supported-languages.d.ts +7 -5
  9. package/dist/config/supported-languages.js +6 -4
  10. package/dist/core/graph/graph.js +9 -1
  11. package/dist/core/graph/types.d.ts +10 -2
  12. package/dist/core/ingestion/call-processor.d.ts +18 -1
  13. package/dist/core/ingestion/call-processor.js +297 -38
  14. package/dist/core/ingestion/call-routing.d.ts +3 -18
  15. package/dist/core/ingestion/call-routing.js +0 -19
  16. package/dist/core/ingestion/cobol/cobol-copy-expander.d.ts +57 -0
  17. package/dist/core/ingestion/cobol/cobol-copy-expander.js +385 -0
  18. package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +210 -0
  19. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1509 -0
  20. package/dist/core/ingestion/cobol/jcl-parser.d.ts +68 -0
  21. package/dist/core/ingestion/cobol/jcl-parser.js +217 -0
  22. package/dist/core/ingestion/cobol/jcl-processor.d.ts +33 -0
  23. package/dist/core/ingestion/cobol/jcl-processor.js +229 -0
  24. package/dist/core/ingestion/cobol-processor.d.ts +54 -0
  25. package/dist/core/ingestion/cobol-processor.js +1186 -0
  26. package/dist/core/ingestion/entry-point-scoring.d.ts +17 -0
  27. package/dist/core/ingestion/entry-point-scoring.js +18 -4
  28. package/dist/core/ingestion/export-detection.d.ts +47 -8
  29. package/dist/core/ingestion/export-detection.js +29 -50
  30. package/dist/core/ingestion/field-extractor.d.ts +29 -0
  31. package/dist/core/ingestion/field-extractor.js +25 -0
  32. package/dist/core/ingestion/field-extractors/configs/c-cpp.d.ts +3 -0
  33. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +108 -0
  34. package/dist/core/ingestion/field-extractors/configs/csharp.d.ts +8 -0
  35. package/dist/core/ingestion/field-extractors/configs/csharp.js +73 -0
  36. package/dist/core/ingestion/field-extractors/configs/dart.d.ts +8 -0
  37. package/dist/core/ingestion/field-extractors/configs/dart.js +76 -0
  38. package/dist/core/ingestion/field-extractors/configs/go.d.ts +11 -0
  39. package/dist/core/ingestion/field-extractors/configs/go.js +64 -0
  40. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +44 -0
  41. package/dist/core/ingestion/field-extractors/configs/helpers.js +134 -0
  42. package/dist/core/ingestion/field-extractors/configs/jvm.d.ts +3 -0
  43. package/dist/core/ingestion/field-extractors/configs/jvm.js +118 -0
  44. package/dist/core/ingestion/field-extractors/configs/php.d.ts +8 -0
  45. package/dist/core/ingestion/field-extractors/configs/php.js +67 -0
  46. package/dist/core/ingestion/field-extractors/configs/python.d.ts +12 -0
  47. package/dist/core/ingestion/field-extractors/configs/python.js +91 -0
  48. package/dist/core/ingestion/field-extractors/configs/ruby.d.ts +16 -0
  49. package/dist/core/ingestion/field-extractors/configs/ruby.js +75 -0
  50. package/dist/core/ingestion/field-extractors/configs/rust.d.ts +9 -0
  51. package/dist/core/ingestion/field-extractors/configs/rust.js +55 -0
  52. package/dist/core/ingestion/field-extractors/configs/swift.d.ts +8 -0
  53. package/dist/core/ingestion/field-extractors/configs/swift.js +63 -0
  54. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.d.ts +3 -0
  55. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +60 -0
  56. package/dist/core/ingestion/field-extractors/generic.d.ts +46 -0
  57. package/dist/core/ingestion/field-extractors/generic.js +111 -0
  58. package/dist/core/ingestion/field-extractors/typescript.d.ts +77 -0
  59. package/dist/core/ingestion/field-extractors/typescript.js +291 -0
  60. package/dist/core/ingestion/field-types.d.ts +59 -0
  61. package/dist/core/ingestion/field-types.js +2 -0
  62. package/dist/core/ingestion/framework-detection.d.ts +87 -0
  63. package/dist/core/ingestion/framework-detection.js +65 -2
  64. package/dist/core/ingestion/heritage-processor.js +15 -17
  65. package/dist/core/ingestion/import-processor.d.ts +9 -10
  66. package/dist/core/ingestion/import-processor.js +59 -14
  67. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.d.ts +6 -9
  68. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.js +20 -2
  69. package/dist/core/ingestion/import-resolvers/dart.d.ts +7 -0
  70. package/dist/core/ingestion/import-resolvers/dart.js +44 -0
  71. package/dist/core/ingestion/{resolvers → import-resolvers}/go.d.ts +4 -5
  72. package/dist/core/ingestion/{resolvers → import-resolvers}/go.js +17 -0
  73. package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.d.ts +9 -1
  74. package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.js +56 -0
  75. package/dist/core/ingestion/{resolvers → import-resolvers}/php.d.ts +6 -10
  76. package/dist/core/ingestion/{resolvers → import-resolvers}/php.js +7 -2
  77. package/dist/core/ingestion/{resolvers → import-resolvers}/python.d.ts +9 -3
  78. package/dist/core/ingestion/{resolvers → import-resolvers}/python.js +35 -3
  79. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.d.ts +5 -2
  80. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.js +7 -2
  81. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.d.ts +5 -2
  82. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.js +41 -2
  83. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.d.ts +15 -7
  84. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.js +22 -3
  85. package/dist/core/ingestion/import-resolvers/swift.d.ts +7 -0
  86. package/dist/core/ingestion/import-resolvers/swift.js +23 -0
  87. package/dist/core/ingestion/import-resolvers/types.d.ts +44 -0
  88. package/dist/core/ingestion/import-resolvers/types.js +6 -0
  89. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.d.ts +0 -3
  90. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +0 -9
  91. package/dist/core/ingestion/language-config.d.ts +4 -1
  92. package/dist/core/ingestion/language-provider.d.ts +121 -0
  93. package/dist/core/ingestion/language-provider.js +24 -0
  94. package/dist/core/ingestion/languages/c-cpp.d.ts +12 -0
  95. package/dist/core/ingestion/languages/c-cpp.js +71 -0
  96. package/dist/core/ingestion/languages/cobol.d.ts +1 -0
  97. package/dist/core/ingestion/languages/cobol.js +26 -0
  98. package/dist/core/ingestion/languages/csharp.d.ts +8 -0
  99. package/dist/core/ingestion/languages/csharp.js +49 -0
  100. package/dist/core/ingestion/languages/dart.d.ts +12 -0
  101. package/dist/core/ingestion/languages/dart.js +58 -0
  102. package/dist/core/ingestion/languages/go.d.ts +11 -0
  103. package/dist/core/ingestion/languages/go.js +28 -0
  104. package/dist/core/ingestion/languages/index.d.ts +38 -0
  105. package/dist/core/ingestion/languages/index.js +63 -0
  106. package/dist/core/ingestion/languages/java.d.ts +9 -0
  107. package/dist/core/ingestion/languages/java.js +29 -0
  108. package/dist/core/ingestion/languages/kotlin.d.ts +9 -0
  109. package/dist/core/ingestion/languages/kotlin.js +53 -0
  110. package/dist/core/ingestion/languages/php.d.ts +8 -0
  111. package/dist/core/ingestion/languages/php.js +145 -0
  112. package/dist/core/ingestion/languages/python.d.ts +12 -0
  113. package/dist/core/ingestion/languages/python.js +39 -0
  114. package/dist/core/ingestion/languages/ruby.d.ts +9 -0
  115. package/dist/core/ingestion/languages/ruby.js +44 -0
  116. package/dist/core/ingestion/languages/rust.d.ts +12 -0
  117. package/dist/core/ingestion/languages/rust.js +44 -0
  118. package/dist/core/ingestion/languages/swift.d.ts +12 -0
  119. package/dist/core/ingestion/languages/swift.js +133 -0
  120. package/dist/core/ingestion/languages/typescript.d.ts +10 -0
  121. package/dist/core/ingestion/languages/typescript.js +60 -0
  122. package/dist/core/ingestion/mro-processor.js +14 -15
  123. package/dist/core/ingestion/{named-binding-extraction.d.ts → named-binding-processor.d.ts} +0 -9
  124. package/dist/core/ingestion/named-binding-processor.js +42 -0
  125. package/dist/core/ingestion/named-bindings/csharp.d.ts +3 -0
  126. package/dist/core/ingestion/named-bindings/csharp.js +37 -0
  127. package/dist/core/ingestion/named-bindings/java.d.ts +3 -0
  128. package/dist/core/ingestion/named-bindings/java.js +29 -0
  129. package/dist/core/ingestion/named-bindings/kotlin.d.ts +3 -0
  130. package/dist/core/ingestion/named-bindings/kotlin.js +36 -0
  131. package/dist/core/ingestion/named-bindings/php.d.ts +3 -0
  132. package/dist/core/ingestion/named-bindings/php.js +61 -0
  133. package/dist/core/ingestion/named-bindings/python.d.ts +3 -0
  134. package/dist/core/ingestion/named-bindings/python.js +49 -0
  135. package/dist/core/ingestion/named-bindings/rust.d.ts +3 -0
  136. package/dist/core/ingestion/named-bindings/rust.js +64 -0
  137. package/dist/core/ingestion/named-bindings/types.d.ts +16 -0
  138. package/dist/core/ingestion/named-bindings/types.js +6 -0
  139. package/dist/core/ingestion/named-bindings/typescript.d.ts +3 -0
  140. package/dist/core/ingestion/named-bindings/typescript.js +58 -0
  141. package/dist/core/ingestion/parsing-processor.d.ts +5 -1
  142. package/dist/core/ingestion/parsing-processor.js +115 -16
  143. package/dist/core/ingestion/pipeline.js +925 -424
  144. package/dist/core/ingestion/resolution-context.js +1 -1
  145. package/dist/core/ingestion/route-extractors/expo.d.ts +1 -0
  146. package/dist/core/ingestion/route-extractors/expo.js +36 -0
  147. package/dist/core/ingestion/route-extractors/middleware.d.ts +47 -0
  148. package/dist/core/ingestion/route-extractors/middleware.js +143 -0
  149. package/dist/core/ingestion/route-extractors/nextjs.d.ts +3 -0
  150. package/dist/core/ingestion/route-extractors/nextjs.js +76 -0
  151. package/dist/core/ingestion/route-extractors/php.d.ts +7 -0
  152. package/dist/core/ingestion/route-extractors/php.js +21 -0
  153. package/dist/core/ingestion/route-extractors/response-shapes.d.ts +20 -0
  154. package/dist/core/ingestion/route-extractors/response-shapes.js +290 -0
  155. package/dist/core/ingestion/tree-sitter-queries.d.ts +8 -7
  156. package/dist/core/ingestion/tree-sitter-queries.js +231 -9
  157. package/dist/core/ingestion/type-env.d.ts +14 -17
  158. package/dist/core/ingestion/type-env.js +66 -14
  159. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +1 -1
  160. package/dist/core/ingestion/type-extractors/csharp.js +1 -1
  161. package/dist/core/ingestion/type-extractors/dart.d.ts +15 -0
  162. package/dist/core/ingestion/type-extractors/dart.js +371 -0
  163. package/dist/core/ingestion/type-extractors/jvm.js +1 -1
  164. package/dist/core/ingestion/type-extractors/shared.d.ts +1 -13
  165. package/dist/core/ingestion/type-extractors/shared.js +9 -102
  166. package/dist/core/ingestion/type-extractors/swift.js +334 -4
  167. package/dist/core/ingestion/type-extractors/types.d.ts +3 -1
  168. package/dist/core/ingestion/{ast-helpers.d.ts → utils/ast-helpers.d.ts} +16 -13
  169. package/dist/core/ingestion/{ast-helpers.js → utils/ast-helpers.js} +111 -32
  170. package/dist/core/ingestion/{call-analysis.js → utils/call-analysis.js} +37 -0
  171. package/dist/core/ingestion/utils/event-loop.d.ts +5 -0
  172. package/dist/core/ingestion/utils/event-loop.js +5 -0
  173. package/dist/core/ingestion/utils/language-detection.d.ts +9 -0
  174. package/dist/core/ingestion/utils/language-detection.js +70 -0
  175. package/dist/core/ingestion/utils/verbose.d.ts +1 -0
  176. package/dist/core/ingestion/utils/verbose.js +7 -0
  177. package/dist/core/ingestion/workers/parse-worker.d.ts +43 -2
  178. package/dist/core/ingestion/workers/parse-worker.js +361 -150
  179. package/dist/core/lbug/csv-generator.js +34 -1
  180. package/dist/core/lbug/lbug-adapter.js +6 -0
  181. package/dist/core/lbug/schema.d.ts +5 -3
  182. package/dist/core/lbug/schema.js +39 -2
  183. package/dist/core/tree-sitter/parser-loader.js +7 -1
  184. package/dist/core/wiki/cursor-client.d.ts +31 -0
  185. package/dist/core/wiki/cursor-client.js +127 -0
  186. package/dist/core/wiki/generator.d.ts +28 -9
  187. package/dist/core/wiki/generator.js +115 -18
  188. package/dist/core/wiki/graph-queries.d.ts +4 -0
  189. package/dist/core/wiki/graph-queries.js +7 -1
  190. package/dist/core/wiki/llm-client.d.ts +2 -0
  191. package/dist/core/wiki/llm-client.js +8 -4
  192. package/dist/core/wiki/prompts.d.ts +3 -3
  193. package/dist/core/wiki/prompts.js +6 -0
  194. package/dist/mcp/core/lbug-adapter.d.ts +5 -0
  195. package/dist/mcp/core/lbug-adapter.js +11 -1
  196. package/dist/mcp/local/local-backend.d.ts +16 -5
  197. package/dist/mcp/local/local-backend.js +711 -74
  198. package/dist/mcp/tools.js +71 -2
  199. package/dist/storage/repo-manager.d.ts +3 -0
  200. package/package.json +17 -16
  201. package/dist/core/ingestion/import-resolution.d.ts +0 -101
  202. package/dist/core/ingestion/import-resolution.js +0 -251
  203. package/dist/core/ingestion/named-binding-extraction.js +0 -373
  204. package/dist/core/ingestion/resolvers/index.d.ts +0 -18
  205. package/dist/core/ingestion/resolvers/index.js +0 -13
  206. package/dist/core/ingestion/type-extractors/index.d.ts +0 -22
  207. package/dist/core/ingestion/type-extractors/index.js +0 -31
  208. package/dist/core/ingestion/utils.d.ts +0 -20
  209. package/dist/core/ingestion/utils.js +0 -242
  210. package/scripts/patch-tree-sitter-swift.cjs +0 -74
  211. /package/dist/core/ingestion/{call-analysis.d.ts → utils/call-analysis.d.ts} +0 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Swift Language Provider
3
+ *
4
+ * Assembles all Swift-specific ingestion capabilities into a single
5
+ * LanguageProvider, following the Strategy pattern used by the pipeline.
6
+ *
7
+ * Key Swift traits:
8
+ * - importSemantics: 'wildcard' (Swift imports entire modules)
9
+ * - heritageDefaultEdge: 'IMPLEMENTS' (protocols are more common than class inheritance)
10
+ * - implicitImportWirer: all files in the same SPM target see each other
11
+ */
12
+ import { SupportedLanguages } from '../../../config/supported-languages.js';
13
+ import { defineLanguage } from '../language-provider.js';
14
+ import { typeConfig as swiftConfig } from '../type-extractors/swift.js';
15
+ import { swiftExportChecker } from '../export-detection.js';
16
+ import { resolveSwiftImport } from '../import-resolvers/swift.js';
17
+ import { SWIFT_QUERIES } from '../tree-sitter-queries.js';
18
+ import { createFieldExtractor } from '../field-extractors/generic.js';
19
+ import { swiftConfig as swiftFieldConfig } from '../field-extractors/configs/swift.js';
20
+ /**
21
+ * Group Swift files by SPM target for implicit module visibility.
22
+ * If SwiftPackageConfig is available, use target -> directory mappings.
23
+ * Otherwise, group all Swift files under a single "default" target
24
+ * (assumes a single-module Xcode project).
25
+ */
26
+ function groupSwiftFilesByTarget(swiftFiles, swiftPackageConfig) {
27
+ // No SPM config -> single target (common for Xcode projects)
28
+ if (!swiftPackageConfig || swiftPackageConfig.targets.size === 0) {
29
+ return new Map([['__default__', swiftFiles]]);
30
+ }
31
+ // Pre-convert target dirs to normalized prefix format once
32
+ const targets = [...swiftPackageConfig.targets.entries()].map(([name, dir]) => ({ name, prefix: dir.replace(/\\/g, '/') + '/' }));
33
+ const groups = new Map();
34
+ const defaultGroup = [];
35
+ for (const file of swiftFiles) {
36
+ const normalized = file.includes('\\') ? file.replace(/\\/g, '/') : file;
37
+ let assigned = false;
38
+ for (const { name, prefix } of targets) {
39
+ const idx = normalized.indexOf(prefix);
40
+ if (idx === 0 || (idx > 0 && normalized[idx - 1] === '/')) {
41
+ let group = groups.get(name);
42
+ if (!group) {
43
+ group = [];
44
+ groups.set(name, group);
45
+ }
46
+ group.push(file);
47
+ assigned = true;
48
+ break;
49
+ }
50
+ }
51
+ if (!assigned)
52
+ defaultGroup.push(file);
53
+ }
54
+ if (defaultGroup.length > 0)
55
+ groups.set('__default__', defaultGroup);
56
+ return groups;
57
+ }
58
+ /**
59
+ * Wire implicit inter-file imports for Swift.
60
+ * All files in the same SPM target see each other (full module visibility).
61
+ * Two fast paths avoid unnecessary work:
62
+ * 1. No existing imports for src -> emit all (m-1) edges without Set.has checks
63
+ * 2. Existing imports present -> skip already-connected pairs
64
+ */
65
+ function wireSwiftImplicitImports(swiftFiles, importMap, addImportEdge, projectConfig) {
66
+ const configs = projectConfig;
67
+ const targetGroups = groupSwiftFilesByTarget(swiftFiles, configs?.swiftPackageConfig ?? null);
68
+ for (const group of targetGroups.values()) {
69
+ const m = group.length;
70
+ if (m <= 1)
71
+ continue;
72
+ // All-pairs implicit edges: O(m²) is inherent for full module visibility.
73
+ for (let i = 0; i < m; i++) {
74
+ const src = group[i];
75
+ const existing = importMap.get(src);
76
+ if (!existing || existing.size === 0) {
77
+ // Fast path: no prior imports — emit all peers unconditionally
78
+ for (let j = 0; j < m; j++) {
79
+ if (i !== j)
80
+ addImportEdge(src, group[j]);
81
+ }
82
+ }
83
+ else {
84
+ // Dedup path: skip already-connected pairs
85
+ for (let j = 0; j < m; j++) {
86
+ if (i !== j && !existing.has(group[j])) {
87
+ addImportEdge(src, group[j]);
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ const BUILT_INS = new Set([
95
+ 'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
96
+ 'assert', 'assertionFailure', 'NSLog',
97
+ 'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
98
+ 'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
99
+ 'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
100
+ 'type', 'MemoryLayout',
101
+ 'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
102
+ 'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
103
+ 'sorted', 'reversed', 'enumerated', 'joined', 'split',
104
+ 'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
105
+ 'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
106
+ 'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
107
+ 'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
108
+ 'addTarget', 'removeTarget', 'addGestureRecognizer',
109
+ 'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
110
+ 'NSLocalizedString', 'Bundle',
111
+ 'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
112
+ 'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
113
+ 'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
114
+ 'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
115
+ 'performSegue', 'prepare',
116
+ 'DispatchQueue', 'async', 'sync', 'asyncAfter',
117
+ 'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
118
+ 'sink', 'store', 'assign', 'receive', 'subscribe',
119
+ 'addObserver', 'removeObserver', 'post', 'NotificationCenter',
120
+ ]);
121
+ export const swiftProvider = defineLanguage({
122
+ id: SupportedLanguages.Swift,
123
+ extensions: ['.swift'],
124
+ treeSitterQueries: SWIFT_QUERIES,
125
+ typeConfig: swiftConfig,
126
+ exportChecker: swiftExportChecker,
127
+ importResolver: resolveSwiftImport,
128
+ importSemantics: 'wildcard',
129
+ heritageDefaultEdge: 'IMPLEMENTS',
130
+ fieldExtractor: createFieldExtractor(swiftFieldConfig),
131
+ implicitImportWirer: wireSwiftImplicitImports,
132
+ builtInNames: BUILT_INS,
133
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * TypeScript and JavaScript language providers.
3
+ *
4
+ * Both languages share the same type extraction config (typescriptConfig),
5
+ * export checker (tsExportChecker), and named binding extractor
6
+ * (extractTsNamedBindings). They differ in file extensions, tree-sitter
7
+ * queries (TypeScript grammar has interface/type nodes), and language ID.
8
+ */
9
+ export declare const typescriptProvider: import("../language-provider.js").LanguageProvider;
10
+ export declare const javascriptProvider: import("../language-provider.js").LanguageProvider;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * TypeScript and JavaScript language providers.
3
+ *
4
+ * Both languages share the same type extraction config (typescriptConfig),
5
+ * export checker (tsExportChecker), and named binding extractor
6
+ * (extractTsNamedBindings). They differ in file extensions, tree-sitter
7
+ * queries (TypeScript grammar has interface/type nodes), and language ID.
8
+ */
9
+ import { SupportedLanguages } from '../../../config/supported-languages.js';
10
+ import { defineLanguage } from '../language-provider.js';
11
+ import { typeConfig as typescriptConfig } from '../type-extractors/typescript.js';
12
+ import { tsExportChecker } from '../export-detection.js';
13
+ import { resolveTypescriptImport, resolveJavascriptImport } from '../import-resolvers/standard.js';
14
+ import { extractTsNamedBindings } from '../named-bindings/typescript.js';
15
+ import { TYPESCRIPT_QUERIES, JAVASCRIPT_QUERIES } from '../tree-sitter-queries.js';
16
+ import { typescriptFieldExtractor } from '../field-extractors/typescript.js';
17
+ import { createFieldExtractor } from '../field-extractors/generic.js';
18
+ import { javascriptConfig } from '../field-extractors/configs/typescript-javascript.js';
19
+ const BUILT_INS = new Set([
20
+ 'console', 'log', 'warn', 'error', 'info', 'debug',
21
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
22
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
23
+ 'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
24
+ 'JSON', 'parse', 'stringify',
25
+ 'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
26
+ 'Map', 'Set', 'WeakMap', 'WeakSet',
27
+ 'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
28
+ 'Math', 'Date', 'RegExp', 'Error',
29
+ 'require', 'import', 'export', 'fetch', 'Response', 'Request',
30
+ 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
31
+ 'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
32
+ 'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
33
+ 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
34
+ 'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
35
+ 'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
36
+ 'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
37
+ 'hasOwnProperty', 'toString', 'valueOf',
38
+ ]);
39
+ export const typescriptProvider = defineLanguage({
40
+ id: SupportedLanguages.TypeScript,
41
+ extensions: ['.ts', '.tsx'],
42
+ treeSitterQueries: TYPESCRIPT_QUERIES,
43
+ typeConfig: typescriptConfig,
44
+ exportChecker: tsExportChecker,
45
+ importResolver: resolveTypescriptImport,
46
+ namedBindingExtractor: extractTsNamedBindings,
47
+ fieldExtractor: typescriptFieldExtractor,
48
+ builtInNames: BUILT_INS,
49
+ });
50
+ export const javascriptProvider = defineLanguage({
51
+ id: SupportedLanguages.JavaScript,
52
+ extensions: ['.js', '.jsx'],
53
+ treeSitterQueries: JAVASCRIPT_QUERIES,
54
+ typeConfig: typescriptConfig,
55
+ exportChecker: tsExportChecker,
56
+ importResolver: resolveJavascriptImport,
57
+ namedBindingExtractor: extractTsNamedBindings,
58
+ fieldExtractor: createFieldExtractor(javascriptConfig),
59
+ builtInNames: BUILT_INS,
60
+ });
@@ -19,7 +19,7 @@
19
19
  * Cypher: MATCH (c:Class)-[r:CodeRelation {type: 'OVERRIDES'}]->(m:Method)
20
20
  */
21
21
  import { generateId } from '../../lib/utils.js';
22
- import { SupportedLanguages } from '../../config/supported-languages.js';
22
+ import { getProvider } from './languages/index.js';
23
23
  // ---------------------------------------------------------------------------
24
24
  // Internal helpers
25
25
  // ---------------------------------------------------------------------------
@@ -219,9 +219,10 @@ export function computeMRO(graph) {
219
219
  if (!language)
220
220
  continue;
221
221
  const className = classNode.properties.name;
222
- // Compute linearized MRO depending on language
222
+ // Compute linearized MRO depending on language strategy
223
+ const provider = getProvider(language);
223
224
  let mroOrder;
224
- if (language === SupportedLanguages.Python) {
225
+ if (provider.mroStrategy === 'c3') {
225
226
  const c3Result = c3Linearize(classId, parentMap, c3Cache);
226
227
  mroOrder = c3Result ?? gatherAncestors(classId, parentMap);
227
228
  }
@@ -264,8 +265,8 @@ export function computeMRO(graph) {
264
265
  }
265
266
  // Detect collisions: methods defined in 2+ different ancestors
266
267
  const ambiguities = [];
267
- // Compute transitive edge types once per class (only needed for C#/Java)
268
- const needsEdgeTypes = language === SupportedLanguages.CSharp || language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin;
268
+ // Compute transitive edge types once per class (only needed for implements-split languages)
269
+ const needsEdgeTypes = provider.mroStrategy === 'implements-split';
269
270
  const classEdgeTypes = needsEdgeTypes
270
271
  ? buildTransitiveEdgeTypes(classId, parentMap, parentEdgeType)
271
272
  : undefined;
@@ -281,22 +282,20 @@ export function computeMRO(graph) {
281
282
  if (ownDefinesIt)
282
283
  continue;
283
284
  let resolution;
284
- switch (language) {
285
- case SupportedLanguages.CPlusPlus:
286
- resolution = resolveByMroOrder(methodName, defs, mroOrder, 'C++ leftmost base');
285
+ switch (provider.mroStrategy) {
286
+ case 'leftmost-base':
287
+ resolution = resolveByMroOrder(methodName, defs, mroOrder, 'leftmost base');
287
288
  break;
288
- case SupportedLanguages.CSharp:
289
- case SupportedLanguages.Java:
290
- case SupportedLanguages.Kotlin:
289
+ case 'implements-split':
291
290
  resolution = resolveCsharpJava(methodName, defs, classEdgeTypes);
292
291
  break;
293
- case SupportedLanguages.Python:
294
- resolution = resolveByMroOrder(methodName, defs, mroOrder, 'Python C3 MRO');
292
+ case 'c3':
293
+ resolution = resolveByMroOrder(methodName, defs, mroOrder, 'C3 MRO');
295
294
  break;
296
- case SupportedLanguages.Rust:
295
+ case 'qualified-syntax':
297
296
  resolution = {
298
297
  resolvedTo: null,
299
- reason: `Rust requires qualified syntax: <Type as Trait>::${methodName}()`,
298
+ reason: `requires qualified syntax: <Type as Trait>::${methodName}()`,
300
299
  confidence: 0.5,
301
300
  };
302
301
  break;
@@ -1,7 +1,5 @@
1
1
  import type { SymbolTable, SymbolDefinition } from './symbol-table.js';
2
2
  import type { NamedImportMap } from './import-processor.js';
3
- import type { NamedBinding } from './import-resolution.js';
4
- import type { SyntaxNode } from './utils.js';
5
3
  /**
6
4
  * Walk a named-binding re-export chain through NamedImportMap.
7
5
  *
@@ -18,10 +16,3 @@ import type { SyntaxNode } from './utils.js';
18
16
  * silent misses at depth=0 for non-aliased bindings.
19
17
  */
20
18
  export declare function walkBindingChain(name: string, currentFilePath: string, symbolTable: SymbolTable, namedImportMap: NamedImportMap, allDefs: SymbolDefinition[]): SymbolDefinition[] | null;
21
- export declare function extractTsNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
22
- export declare function extractPythonNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
23
- export declare function extractKotlinNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
24
- export declare function extractRustNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
25
- export declare function extractPhpNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
26
- export declare function extractCsharpNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
27
- export declare function extractJavaNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Walk a named-binding re-export chain through NamedImportMap.
3
+ *
4
+ * When file A imports { User } from B, and B re-exports { User } from C,
5
+ * the NamedImportMap for A points to B, but B has no User definition.
6
+ * This function follows the chain: A→B→C until a definition is found.
7
+ *
8
+ * Returns the definitions found at the end of the chain, or null if the
9
+ * chain breaks (missing binding, circular reference, or depth exceeded).
10
+ * Max depth 5 to prevent infinite loops.
11
+ *
12
+ * @param allDefs Pre-computed `symbolTable.lookupFuzzy(name)` result — must be the
13
+ * complete unfiltered result. Passing a file-filtered subset will cause
14
+ * silent misses at depth=0 for non-aliased bindings.
15
+ */
16
+ export function walkBindingChain(name, currentFilePath, symbolTable, namedImportMap, allDefs) {
17
+ let lookupFile = currentFilePath;
18
+ let lookupName = name;
19
+ const visited = new Set();
20
+ for (let depth = 0; depth < 5; depth++) {
21
+ const bindings = namedImportMap.get(lookupFile);
22
+ if (!bindings)
23
+ return null;
24
+ const binding = bindings.get(lookupName);
25
+ if (!binding)
26
+ return null;
27
+ const key = `${binding.sourcePath}:${binding.exportedName}`;
28
+ if (visited.has(key))
29
+ return null; // circular
30
+ visited.add(key);
31
+ const targetName = binding.exportedName;
32
+ const resolvedDefs = targetName !== lookupName || depth > 0
33
+ ? symbolTable.lookupFuzzy(targetName).filter(def => def.filePath === binding.sourcePath)
34
+ : allDefs.filter(def => def.filePath === binding.sourcePath);
35
+ if (resolvedDefs.length > 0)
36
+ return resolvedDefs;
37
+ // No definition in source file → follow re-export chain
38
+ lookupFile = binding.sourcePath;
39
+ lookupName = targetName;
40
+ }
41
+ return null;
42
+ }
@@ -0,0 +1,3 @@
1
+ import type { SyntaxNode } from '../utils/ast-helpers.js';
2
+ import type { NamedBinding } from './types.js';
3
+ export declare function extractCSharpNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
@@ -0,0 +1,37 @@
1
+ export function extractCSharpNamedBindings(importNode) {
2
+ // using_directive — three forms:
3
+ // using Alias = NS.Type; → aliasIdent + qualifiedName
4
+ // using static NS.Type; → static + qualifiedName (no alias)
5
+ // using NS; → qualifiedName only (namespace, not capturable)
6
+ if (importNode.type !== 'using_directive')
7
+ return undefined;
8
+ let aliasIdent = null;
9
+ let qualifiedName = null;
10
+ let isStatic = false;
11
+ for (let i = 0; i < importNode.childCount; i++) {
12
+ const child = importNode.child(i);
13
+ if (child?.text === 'static')
14
+ isStatic = true;
15
+ }
16
+ for (let i = 0; i < importNode.namedChildCount; i++) {
17
+ const child = importNode.namedChild(i);
18
+ if (child?.type === 'identifier' && !aliasIdent)
19
+ aliasIdent = child;
20
+ else if (child?.type === 'qualified_name')
21
+ qualifiedName = child;
22
+ }
23
+ // Form 1: using Alias = NS.Type;
24
+ if (aliasIdent && qualifiedName) {
25
+ const fullText = qualifiedName.text;
26
+ const exportedName = fullText.includes('.') ? fullText.split('.').pop() : fullText;
27
+ return [{ local: aliasIdent.text, exported: exportedName }];
28
+ }
29
+ // Form 2: using static NS.Type; — last segment is the class name
30
+ if (isStatic && qualifiedName) {
31
+ const fullText = qualifiedName.text;
32
+ const lastSegment = fullText.includes('.') ? fullText.split('.').pop() : fullText;
33
+ return [{ local: lastSegment, exported: lastSegment }];
34
+ }
35
+ // Form 3: using NS; — namespace import, can't resolve to per-symbol bindings
36
+ return undefined;
37
+ }
@@ -0,0 +1,3 @@
1
+ import { type SyntaxNode } from '../utils/ast-helpers.js';
2
+ import type { NamedBinding } from './types.js';
3
+ export declare function extractJavaNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
@@ -0,0 +1,29 @@
1
+ import { findChild } from '../utils/ast-helpers.js';
2
+ export function extractJavaNamedBindings(importNode) {
3
+ // import_declaration > scoped_identifier "com.example.models.User"
4
+ // Wildcard imports (.*) don't produce named bindings
5
+ if (importNode.type !== 'import_declaration')
6
+ return undefined;
7
+ // Check for asterisk (wildcard import) and static modifier
8
+ let isStatic = false;
9
+ for (let i = 0; i < importNode.childCount; i++) {
10
+ const child = importNode.child(i);
11
+ if (child?.type === 'asterisk')
12
+ return undefined;
13
+ if (child?.text === 'static')
14
+ isStatic = true;
15
+ }
16
+ const scopedId = findChild(importNode, 'scoped_identifier');
17
+ if (!scopedId)
18
+ return undefined;
19
+ const fullText = scopedId.text;
20
+ const lastDot = fullText.lastIndexOf('.');
21
+ if (lastDot === -1)
22
+ return undefined;
23
+ const name = fullText.slice(lastDot + 1);
24
+ // Non-static: skip lowercase names — those are package imports, not class imports.
25
+ // Static: allow lowercase — `import static models.UserFactory.getUser` imports a method.
26
+ if (!isStatic && name[0] && name[0] === name[0].toLowerCase())
27
+ return undefined;
28
+ return [{ local: name, exported: name }];
29
+ }
@@ -0,0 +1,3 @@
1
+ import { type SyntaxNode } from '../utils/ast-helpers.js';
2
+ import type { NamedBinding } from './types.js';
3
+ export declare function extractKotlinNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
@@ -0,0 +1,36 @@
1
+ import { findChild } from '../utils/ast-helpers.js';
2
+ export function extractKotlinNamedBindings(importNode) {
3
+ // import_header > identifier + import_alias > simple_identifier
4
+ if (importNode.type !== 'import_header')
5
+ return undefined;
6
+ const fullIdent = findChild(importNode, 'identifier');
7
+ if (!fullIdent)
8
+ return undefined;
9
+ const fullText = fullIdent.text;
10
+ const exportedName = fullText.includes('.') ? fullText.split('.').pop() : fullText;
11
+ const importAlias = findChild(importNode, 'import_alias');
12
+ if (importAlias) {
13
+ // Aliased: import com.example.User as U
14
+ const aliasIdent = findChild(importAlias, 'simple_identifier');
15
+ if (!aliasIdent)
16
+ return undefined;
17
+ return [{ local: aliasIdent.text, exported: exportedName }];
18
+ }
19
+ // Non-aliased: import com.example.User → local="User", exported="User"
20
+ // Also handles top-level function imports: import models.getUser → local="getUser"
21
+ // Skip wildcard imports (ending in *)
22
+ if (fullText.endsWith('.*') || fullText.endsWith('*'))
23
+ return undefined;
24
+ // Skip class-member imports (e.g., import util.OneArg.writeAudit) where the
25
+ // second-to-last segment is PascalCase (a class name). Multiple member imports
26
+ // with the same function name would collide in NamedImportMap, breaking
27
+ // arity-based disambiguation. Top-level function imports (import models.getUser)
28
+ // and class imports (import models.User) have package-only prefixes.
29
+ const segments = fullText.split('.');
30
+ if (segments.length >= 3) {
31
+ const parentSegment = segments[segments.length - 2];
32
+ if (parentSegment[0] && parentSegment[0] === parentSegment[0].toUpperCase())
33
+ return undefined;
34
+ }
35
+ return [{ local: exportedName, exported: exportedName }];
36
+ }
@@ -0,0 +1,3 @@
1
+ import type { SyntaxNode } from '../utils/ast-helpers.js';
2
+ import type { NamedBinding } from './types.js';
3
+ export declare function extractPhpNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
@@ -0,0 +1,61 @@
1
+ export function extractPhpNamedBindings(importNode) {
2
+ // namespace_use_declaration > namespace_use_clause* (flat)
3
+ // namespace_use_declaration > namespace_use_group > namespace_use_clause* (grouped)
4
+ if (importNode.type !== 'namespace_use_declaration')
5
+ return undefined;
6
+ // Skip 'use function' and 'use const' declarations — these import callables/constants,
7
+ // not class types, and should not be added to namedImportMap as type bindings.
8
+ const useTypeNode = importNode.childForFieldName?.('type');
9
+ if (useTypeNode && (useTypeNode.text === 'function' || useTypeNode.text === 'const')) {
10
+ return undefined;
11
+ }
12
+ const bindings = [];
13
+ // Collect all clauses — from direct children AND from namespace_use_group
14
+ const clauses = [];
15
+ for (let i = 0; i < importNode.namedChildCount; i++) {
16
+ const child = importNode.namedChild(i);
17
+ if (child?.type === 'namespace_use_clause') {
18
+ clauses.push(child);
19
+ }
20
+ else if (child?.type === 'namespace_use_group') {
21
+ for (let j = 0; j < child.namedChildCount; j++) {
22
+ const groupChild = child.namedChild(j);
23
+ if (groupChild?.type === 'namespace_use_clause')
24
+ clauses.push(groupChild);
25
+ }
26
+ }
27
+ }
28
+ for (const clause of clauses) {
29
+ // Flat imports: qualified_name + name (alias)
30
+ let qualifiedName = null;
31
+ const names = [];
32
+ for (let j = 0; j < clause.namedChildCount; j++) {
33
+ const child = clause.namedChild(j);
34
+ if (child?.type === 'qualified_name')
35
+ qualifiedName = child;
36
+ else if (child?.type === 'name')
37
+ names.push(child);
38
+ }
39
+ if (qualifiedName && names.length > 0) {
40
+ // Flat aliased import: use App\Models\Repo as R;
41
+ const fullText = qualifiedName.text;
42
+ const exportedName = fullText.includes('\\') ? fullText.split('\\').pop() : fullText;
43
+ bindings.push({ local: names[0].text, exported: exportedName });
44
+ }
45
+ else if (qualifiedName && names.length === 0) {
46
+ // Flat non-aliased import: use App\Models\User;
47
+ const fullText = qualifiedName.text;
48
+ const lastSegment = fullText.includes('\\') ? fullText.split('\\').pop() : fullText;
49
+ bindings.push({ local: lastSegment, exported: lastSegment });
50
+ }
51
+ else if (!qualifiedName && names.length >= 2) {
52
+ // Grouped aliased import: {Repo as R} — first name = exported, second = alias
53
+ bindings.push({ local: names[1].text, exported: names[0].text });
54
+ }
55
+ else if (!qualifiedName && names.length === 1) {
56
+ // Grouped non-aliased import: {User} in use App\Models\{User, Repo as R}
57
+ bindings.push({ local: names[0].text, exported: names[0].text });
58
+ }
59
+ }
60
+ return bindings.length > 0 ? bindings : undefined;
61
+ }
@@ -0,0 +1,3 @@
1
+ import { type SyntaxNode } from '../utils/ast-helpers.js';
2
+ import type { NamedBinding } from './types.js';
3
+ export declare function extractPythonNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
@@ -0,0 +1,49 @@
1
+ import { findChild } from '../utils/ast-helpers.js';
2
+ export function extractPythonNamedBindings(importNode) {
3
+ // Handle: from x import User, Repo as R
4
+ if (importNode.type === 'import_from_statement') {
5
+ const bindings = [];
6
+ for (let i = 0; i < importNode.namedChildCount; i++) {
7
+ const child = importNode.namedChild(i);
8
+ if (!child)
9
+ continue;
10
+ if (child.type === 'dotted_name') {
11
+ // Skip the module_name (first dotted_name is the source module)
12
+ const fieldName = importNode.childForFieldName?.('module_name');
13
+ if (fieldName && child.startIndex === fieldName.startIndex)
14
+ continue;
15
+ // This is an imported name: from x import User
16
+ const name = child.text;
17
+ if (name)
18
+ bindings.push({ local: name, exported: name });
19
+ }
20
+ if (child.type === 'aliased_import') {
21
+ // from x import Repo as R
22
+ const dottedName = findChild(child, 'dotted_name');
23
+ const aliasIdent = findChild(child, 'identifier');
24
+ if (dottedName && aliasIdent) {
25
+ bindings.push({ local: aliasIdent.text, exported: dottedName.text });
26
+ }
27
+ }
28
+ }
29
+ return bindings.length > 0 ? bindings : undefined;
30
+ }
31
+ // Handle: import numpy as np (import_statement with aliased_import child)
32
+ // Tagged with isModuleAlias so applyImportResult routes these directly to
33
+ // moduleAliasMap (e.g. "np" → "numpy.py") instead of namedImportMap.
34
+ if (importNode.type === 'import_statement') {
35
+ const bindings = [];
36
+ for (let i = 0; i < importNode.namedChildCount; i++) {
37
+ const child = importNode.namedChild(i);
38
+ if (!child || child.type !== 'aliased_import')
39
+ continue;
40
+ const dottedName = findChild(child, 'dotted_name');
41
+ const aliasIdent = findChild(child, 'identifier');
42
+ if (dottedName && aliasIdent) {
43
+ bindings.push({ local: aliasIdent.text, exported: dottedName.text, isModuleAlias: true });
44
+ }
45
+ }
46
+ return bindings.length > 0 ? bindings : undefined;
47
+ }
48
+ return undefined;
49
+ }
@@ -0,0 +1,3 @@
1
+ import type { SyntaxNode } from '../utils/ast-helpers.js';
2
+ import type { NamedBinding } from './types.js';
3
+ export declare function extractRustNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
@@ -0,0 +1,64 @@
1
+ export function extractRustNamedBindings(importNode) {
2
+ // use_declaration may contain use_as_clause at any depth
3
+ if (importNode.type !== 'use_declaration')
4
+ return undefined;
5
+ const bindings = [];
6
+ collectRustBindings(importNode, bindings);
7
+ return bindings.length > 0 ? bindings : undefined;
8
+ }
9
+ function collectRustBindings(node, bindings) {
10
+ if (node.type === 'use_as_clause') {
11
+ // First identifier = exported name, second identifier = local alias
12
+ const idents = [];
13
+ for (let i = 0; i < node.namedChildCount; i++) {
14
+ const child = node.namedChild(i);
15
+ if (child?.type === 'identifier')
16
+ idents.push(child.text);
17
+ // For scoped_identifier, extract the last segment
18
+ if (child?.type === 'scoped_identifier') {
19
+ const nameNode = child.childForFieldName?.('name');
20
+ if (nameNode)
21
+ idents.push(nameNode.text);
22
+ }
23
+ }
24
+ if (idents.length === 2) {
25
+ bindings.push({ local: idents[1], exported: idents[0] });
26
+ }
27
+ return;
28
+ }
29
+ // Terminal identifier in a use_list: use crate::models::{User, Repo}
30
+ if (node.type === 'identifier' && node.parent?.type === 'use_list') {
31
+ bindings.push({ local: node.text, exported: node.text });
32
+ return;
33
+ }
34
+ // Skip scoped_identifier that serves as path prefix in scoped_use_list
35
+ // e.g. use crate::models::{User, Repo} — the path node "crate::models" is not an importable symbol
36
+ if (node.type === 'scoped_identifier' && node.parent?.type === 'scoped_use_list') {
37
+ return; // path prefix — the use_list sibling handles the actual symbols
38
+ }
39
+ // Terminal scoped_identifier: use crate::models::User;
40
+ // Only extract if this is a leaf (no deeper use_list/use_as_clause/scoped_use_list)
41
+ if (node.type === 'scoped_identifier') {
42
+ let hasDeeper = false;
43
+ for (let i = 0; i < node.namedChildCount; i++) {
44
+ const child = node.namedChild(i);
45
+ if (child?.type === 'use_list' || child?.type === 'use_as_clause' || child?.type === 'scoped_use_list') {
46
+ hasDeeper = true;
47
+ break;
48
+ }
49
+ }
50
+ if (!hasDeeper) {
51
+ const nameNode = node.childForFieldName?.('name');
52
+ if (nameNode) {
53
+ bindings.push({ local: nameNode.text, exported: nameNode.text });
54
+ }
55
+ return;
56
+ }
57
+ }
58
+ // Recurse into children
59
+ for (let i = 0; i < node.namedChildCount; i++) {
60
+ const child = node.namedChild(i);
61
+ if (child)
62
+ collectRustBindings(child, bindings);
63
+ }
64
+ }