@duytransipher/gitnexus 1.4.6-sipher.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.
Files changed (224) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +261 -0
  3. package/dist/cli/ai-context.d.ts +23 -0
  4. package/dist/cli/ai-context.js +265 -0
  5. package/dist/cli/analyze.d.ts +12 -0
  6. package/dist/cli/analyze.js +345 -0
  7. package/dist/cli/augment.d.ts +13 -0
  8. package/dist/cli/augment.js +33 -0
  9. package/dist/cli/clean.d.ts +10 -0
  10. package/dist/cli/clean.js +60 -0
  11. package/dist/cli/eval-server.d.ts +37 -0
  12. package/dist/cli/eval-server.js +389 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +137 -0
  15. package/dist/cli/lazy-action.d.ts +6 -0
  16. package/dist/cli/lazy-action.js +18 -0
  17. package/dist/cli/list.d.ts +6 -0
  18. package/dist/cli/list.js +30 -0
  19. package/dist/cli/mcp.d.ts +8 -0
  20. package/dist/cli/mcp.js +36 -0
  21. package/dist/cli/serve.d.ts +4 -0
  22. package/dist/cli/serve.js +6 -0
  23. package/dist/cli/setup.d.ts +8 -0
  24. package/dist/cli/setup.js +367 -0
  25. package/dist/cli/sipher-patched.d.ts +2 -0
  26. package/dist/cli/sipher-patched.js +77 -0
  27. package/dist/cli/skill-gen.d.ts +26 -0
  28. package/dist/cli/skill-gen.js +549 -0
  29. package/dist/cli/status.d.ts +6 -0
  30. package/dist/cli/status.js +36 -0
  31. package/dist/cli/tool.d.ts +60 -0
  32. package/dist/cli/tool.js +180 -0
  33. package/dist/cli/wiki.d.ts +15 -0
  34. package/dist/cli/wiki.js +365 -0
  35. package/dist/config/ignore-service.d.ts +26 -0
  36. package/dist/config/ignore-service.js +284 -0
  37. package/dist/config/supported-languages.d.ts +15 -0
  38. package/dist/config/supported-languages.js +16 -0
  39. package/dist/core/augmentation/engine.d.ts +26 -0
  40. package/dist/core/augmentation/engine.js +240 -0
  41. package/dist/core/embeddings/embedder.d.ts +60 -0
  42. package/dist/core/embeddings/embedder.js +251 -0
  43. package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
  44. package/dist/core/embeddings/embedding-pipeline.js +356 -0
  45. package/dist/core/embeddings/index.d.ts +9 -0
  46. package/dist/core/embeddings/index.js +9 -0
  47. package/dist/core/embeddings/text-generator.d.ts +24 -0
  48. package/dist/core/embeddings/text-generator.js +182 -0
  49. package/dist/core/embeddings/types.d.ts +87 -0
  50. package/dist/core/embeddings/types.js +32 -0
  51. package/dist/core/graph/graph.d.ts +2 -0
  52. package/dist/core/graph/graph.js +66 -0
  53. package/dist/core/graph/types.d.ts +66 -0
  54. package/dist/core/graph/types.js +1 -0
  55. package/dist/core/ingestion/ast-cache.d.ts +11 -0
  56. package/dist/core/ingestion/ast-cache.js +35 -0
  57. package/dist/core/ingestion/call-processor.d.ts +23 -0
  58. package/dist/core/ingestion/call-processor.js +793 -0
  59. package/dist/core/ingestion/call-routing.d.ts +68 -0
  60. package/dist/core/ingestion/call-routing.js +129 -0
  61. package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
  62. package/dist/core/ingestion/cluster-enricher.js +170 -0
  63. package/dist/core/ingestion/community-processor.d.ts +39 -0
  64. package/dist/core/ingestion/community-processor.js +312 -0
  65. package/dist/core/ingestion/constants.d.ts +16 -0
  66. package/dist/core/ingestion/constants.js +16 -0
  67. package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
  68. package/dist/core/ingestion/entry-point-scoring.js +353 -0
  69. package/dist/core/ingestion/export-detection.d.ts +18 -0
  70. package/dist/core/ingestion/export-detection.js +231 -0
  71. package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
  72. package/dist/core/ingestion/filesystem-walker.js +81 -0
  73. package/dist/core/ingestion/framework-detection.d.ts +54 -0
  74. package/dist/core/ingestion/framework-detection.js +411 -0
  75. package/dist/core/ingestion/heritage-processor.d.ts +28 -0
  76. package/dist/core/ingestion/heritage-processor.js +251 -0
  77. package/dist/core/ingestion/import-processor.d.ts +34 -0
  78. package/dist/core/ingestion/import-processor.js +398 -0
  79. package/dist/core/ingestion/language-config.d.ts +46 -0
  80. package/dist/core/ingestion/language-config.js +167 -0
  81. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  82. package/dist/core/ingestion/mro-processor.js +369 -0
  83. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  84. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  85. package/dist/core/ingestion/parsing-processor.d.ts +19 -0
  86. package/dist/core/ingestion/parsing-processor.js +315 -0
  87. package/dist/core/ingestion/pipeline.d.ts +6 -0
  88. package/dist/core/ingestion/pipeline.js +401 -0
  89. package/dist/core/ingestion/process-processor.d.ts +51 -0
  90. package/dist/core/ingestion/process-processor.js +315 -0
  91. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  92. package/dist/core/ingestion/resolution-context.js +132 -0
  93. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  94. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  95. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  96. package/dist/core/ingestion/resolvers/go.js +42 -0
  97. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  98. package/dist/core/ingestion/resolvers/index.js +13 -0
  99. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  100. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  101. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  102. package/dist/core/ingestion/resolvers/php.js +35 -0
  103. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  104. package/dist/core/ingestion/resolvers/python.js +52 -0
  105. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  106. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  107. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  108. package/dist/core/ingestion/resolvers/rust.js +73 -0
  109. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  110. package/dist/core/ingestion/resolvers/standard.js +123 -0
  111. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  112. package/dist/core/ingestion/resolvers/utils.js +122 -0
  113. package/dist/core/ingestion/structure-processor.d.ts +2 -0
  114. package/dist/core/ingestion/structure-processor.js +36 -0
  115. package/dist/core/ingestion/symbol-table.d.ts +63 -0
  116. package/dist/core/ingestion/symbol-table.js +85 -0
  117. package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
  118. package/dist/core/ingestion/tree-sitter-queries.js +888 -0
  119. package/dist/core/ingestion/type-env.d.ts +49 -0
  120. package/dist/core/ingestion/type-env.js +613 -0
  121. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  123. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  124. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  125. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  126. package/dist/core/ingestion/type-extractors/go.js +467 -0
  127. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  128. package/dist/core/ingestion/type-extractors/index.js +31 -0
  129. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  130. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  131. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  132. package/dist/core/ingestion/type-extractors/php.js +549 -0
  133. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  134. package/dist/core/ingestion/type-extractors/python.js +455 -0
  135. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  136. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  137. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  138. package/dist/core/ingestion/type-extractors/rust.js +456 -0
  139. package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
  140. package/dist/core/ingestion/type-extractors/shared.js +810 -0
  141. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  142. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  143. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  144. package/dist/core/ingestion/type-extractors/types.js +1 -0
  145. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  146. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  147. package/dist/core/ingestion/utils.d.ts +138 -0
  148. package/dist/core/ingestion/utils.js +1290 -0
  149. package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
  150. package/dist/core/ingestion/workers/parse-worker.js +1126 -0
  151. package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
  152. package/dist/core/ingestion/workers/worker-pool.js +128 -0
  153. package/dist/core/lbug/csv-generator.d.ts +33 -0
  154. package/dist/core/lbug/csv-generator.js +366 -0
  155. package/dist/core/lbug/lbug-adapter.d.ts +103 -0
  156. package/dist/core/lbug/lbug-adapter.js +769 -0
  157. package/dist/core/lbug/schema.d.ts +53 -0
  158. package/dist/core/lbug/schema.js +430 -0
  159. package/dist/core/search/bm25-index.d.ts +23 -0
  160. package/dist/core/search/bm25-index.js +96 -0
  161. package/dist/core/search/hybrid-search.d.ts +49 -0
  162. package/dist/core/search/hybrid-search.js +118 -0
  163. package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
  164. package/dist/core/tree-sitter/parser-loader.js +63 -0
  165. package/dist/core/wiki/generator.d.ts +120 -0
  166. package/dist/core/wiki/generator.js +939 -0
  167. package/dist/core/wiki/graph-queries.d.ts +80 -0
  168. package/dist/core/wiki/graph-queries.js +238 -0
  169. package/dist/core/wiki/html-viewer.d.ts +10 -0
  170. package/dist/core/wiki/html-viewer.js +297 -0
  171. package/dist/core/wiki/llm-client.d.ts +43 -0
  172. package/dist/core/wiki/llm-client.js +186 -0
  173. package/dist/core/wiki/prompts.d.ts +53 -0
  174. package/dist/core/wiki/prompts.js +174 -0
  175. package/dist/lib/utils.d.ts +1 -0
  176. package/dist/lib/utils.js +3 -0
  177. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  178. package/dist/mcp/compatible-stdio-transport.js +200 -0
  179. package/dist/mcp/core/embedder.d.ts +27 -0
  180. package/dist/mcp/core/embedder.js +108 -0
  181. package/dist/mcp/core/lbug-adapter.d.ts +57 -0
  182. package/dist/mcp/core/lbug-adapter.js +455 -0
  183. package/dist/mcp/local/local-backend.d.ts +181 -0
  184. package/dist/mcp/local/local-backend.js +1722 -0
  185. package/dist/mcp/resources.d.ts +31 -0
  186. package/dist/mcp/resources.js +411 -0
  187. package/dist/mcp/server.d.ts +23 -0
  188. package/dist/mcp/server.js +296 -0
  189. package/dist/mcp/staleness.d.ts +15 -0
  190. package/dist/mcp/staleness.js +29 -0
  191. package/dist/mcp/tools.d.ts +24 -0
  192. package/dist/mcp/tools.js +292 -0
  193. package/dist/server/api.d.ts +10 -0
  194. package/dist/server/api.js +344 -0
  195. package/dist/server/mcp-http.d.ts +13 -0
  196. package/dist/server/mcp-http.js +100 -0
  197. package/dist/storage/git.d.ts +6 -0
  198. package/dist/storage/git.js +35 -0
  199. package/dist/storage/repo-manager.d.ts +138 -0
  200. package/dist/storage/repo-manager.js +299 -0
  201. package/dist/types/pipeline.d.ts +32 -0
  202. package/dist/types/pipeline.js +18 -0
  203. package/dist/unreal/bridge.d.ts +4 -0
  204. package/dist/unreal/bridge.js +113 -0
  205. package/dist/unreal/config.d.ts +6 -0
  206. package/dist/unreal/config.js +55 -0
  207. package/dist/unreal/types.d.ts +105 -0
  208. package/dist/unreal/types.js +1 -0
  209. package/hooks/claude/gitnexus-hook.cjs +238 -0
  210. package/hooks/claude/pre-tool-use.sh +79 -0
  211. package/hooks/claude/session-start.sh +42 -0
  212. package/package.json +100 -0
  213. package/scripts/ensure-cli-executable.cjs +21 -0
  214. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  215. package/scripts/setup-unreal-gitnexus.ps1 +191 -0
  216. package/skills/gitnexus-cli.md +82 -0
  217. package/skills/gitnexus-debugging.md +89 -0
  218. package/skills/gitnexus-exploring.md +78 -0
  219. package/skills/gitnexus-guide.md +64 -0
  220. package/skills/gitnexus-impact-analysis.md +97 -0
  221. package/skills/gitnexus-pr-review.md +163 -0
  222. package/skills/gitnexus-refactoring.md +121 -0
  223. package/vendor/leiden/index.cjs +355 -0
  224. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,810 @@
1
+ /** Empty set for containers that have no key-yielding methods */
2
+ const NO_KEYS = new Set();
3
+ /** Standard key-yielding methods across languages */
4
+ const STD_KEY_METHODS = new Set(['keys']);
5
+ const JAVA_KEY_METHODS = new Set(['keySet']);
6
+ const CSHARP_KEY_METHODS = new Set(['Keys']);
7
+ /** Standard value-yielding methods across languages */
8
+ const STD_VALUE_METHODS = new Set(['values', 'get', 'pop', 'remove']);
9
+ const CSHARP_VALUE_METHODS = new Set(['Values', 'TryGetValue']);
10
+ const SINGLE_ELEMENT_METHODS = new Set([
11
+ 'iter', 'into_iter', 'iterator', 'get', 'first', 'last', 'pop',
12
+ 'peek', 'poll', 'find', 'filter', 'map',
13
+ ]);
14
+ const CONTAINER_DESCRIPTORS = new Map([
15
+ // --- Map / Dict types (arity 2: key + value) ---
16
+ ['Map', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
17
+ ['WeakMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
18
+ ['HashMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
19
+ ['BTreeMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
20
+ ['LinkedHashMap', { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
21
+ ['TreeMap', { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
22
+ ['dict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
23
+ ['Dict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
24
+ ['Dictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
25
+ ['SortedDictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
26
+ ['Record', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
27
+ ['OrderedDict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
28
+ ['ConcurrentHashMap', { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
29
+ ['ConcurrentDictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
30
+ // --- Single-element containers (arity 1) ---
31
+ ['Array', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
32
+ ['List', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
33
+ ['ArrayList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
34
+ ['LinkedList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
35
+ ['Vec', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
36
+ ['VecDeque', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
37
+ ['Set', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
38
+ ['HashSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
39
+ ['BTreeSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
40
+ ['TreeSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
41
+ ['Queue', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
42
+ ['Deque', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
43
+ ['Stack', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
44
+ ['Sequence', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
45
+ ['Iterable', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
46
+ ['Iterator', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
47
+ ['IEnumerable', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
48
+ ['IList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
49
+ ['ICollection', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
50
+ ['Collection', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
51
+ ['ObservableCollection', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
52
+ ['IEnumerator', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
53
+ ['SortedSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
54
+ ['Stream', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
55
+ ['MutableList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
56
+ ['MutableSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
57
+ ['LinkedHashSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
58
+ ['ArrayDeque', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
59
+ ['PriorityQueue', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
60
+ ['MutableMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
61
+ ['list', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
62
+ ['set', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
63
+ ['tuple', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
64
+ ['frozenset', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
65
+ ]);
66
+ /** Determine which type arg to extract based on container type name and access method.
67
+ *
68
+ * Resolution order:
69
+ * 1. If container is known and method is in keyMethods → 'first'
70
+ * 2. If container is known with arity 1 → 'last' (same as 'first' for single-arg)
71
+ * 3. If container is unknown → fall back to method name heuristic
72
+ * 4. Default: 'last' (value type)
73
+ */
74
+ export function methodToTypeArgPosition(methodName, containerTypeName) {
75
+ if (containerTypeName) {
76
+ const desc = CONTAINER_DESCRIPTORS.get(containerTypeName);
77
+ if (desc) {
78
+ // Single-element container: always 'last' (= only arg)
79
+ if (desc.arity === 1)
80
+ return 'last';
81
+ // Multi-element: check if method yields key type
82
+ if (methodName && desc.keyMethods.has(methodName))
83
+ return 'first';
84
+ // Default for multi-element: value type
85
+ return 'last';
86
+ }
87
+ }
88
+ // Fallback for unknown containers: simple method name heuristic
89
+ if (methodName && (methodName === 'keys' || methodName === 'keySet' || methodName === 'Keys')) {
90
+ return 'first';
91
+ }
92
+ return 'last';
93
+ }
94
+ /** Look up the container descriptor for a type name. Exported for heritage-chain lookups. */
95
+ export function getContainerDescriptor(typeName) {
96
+ return CONTAINER_DESCRIPTORS.get(typeName);
97
+ }
98
+ /**
99
+ * Shared 3-strategy fallback for resolving the element type of a container variable.
100
+ * Used by all for-loop extractors to resolve the loop variable's type from the iterable.
101
+ *
102
+ * Strategy 1: declarationTypeNodes — raw AST type annotation node (handles container types
103
+ * where extractSimpleTypeName returned undefined, e.g., User[], List[User])
104
+ * Strategy 2: scopeEnv string — extractElementTypeFromString on the stored type string
105
+ * Strategy 3: AST walk — language-specific upward walk to enclosing function parameters
106
+ *
107
+ * @param extractFromTypeNode Language-specific function to extract element type from AST node
108
+ * @param findParamElementType Optional language-specific AST walk to find parameter type
109
+ * @param typeArgPos Which generic type arg to extract: 'first' for keys, 'last' for values (default)
110
+ */
111
+ export function resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractFromTypeNode, findParamElementType, typeArgPos = 'last') {
112
+ // Strategy 1: declarationTypeNodes AST node (check current scope, then file scope)
113
+ const typeNode = declarationTypeNodes.get(`${scope}\0${iterableName}`)
114
+ ?? (scope !== '' ? declarationTypeNodes.get(`\0${iterableName}`) : undefined);
115
+ if (typeNode) {
116
+ const t = extractFromTypeNode(typeNode, typeArgPos);
117
+ if (t)
118
+ return t;
119
+ }
120
+ // Strategy 2: scopeEnv string → extractElementTypeFromString
121
+ const iterableType = scopeEnv.get(iterableName);
122
+ if (iterableType) {
123
+ const el = extractElementTypeFromString(iterableType, typeArgPos);
124
+ if (el)
125
+ return el;
126
+ }
127
+ // Strategy 3: AST walk to function parameters
128
+ if (findParamElementType)
129
+ return findParamElementType(iterableName, node, typeArgPos);
130
+ return undefined;
131
+ }
132
+ /** Known single-arg nullable wrapper types that unwrap to their inner type
133
+ * for receiver resolution. Optional<User> → "User", Option<User> → "User".
134
+ * Only nullable wrappers — NOT containers (List, Vec) or async wrappers (Promise, Future).
135
+ * See WRAPPER_GENERICS below for the full set used in return-type inference. */
136
+ const NULLABLE_WRAPPER_TYPES = new Set([
137
+ 'Optional', // Java
138
+ 'Option', // Rust, Scala
139
+ 'Maybe', // Haskell-style, Kotlin Arrow
140
+ ]);
141
+ /**
142
+ * Extract the simple type name from a type AST node.
143
+ * Handles generic types (e.g., List<User> → List), qualified names
144
+ * (e.g., models.User → User), and nullable types (e.g., User? → User).
145
+ * Returns undefined for complex types (unions, intersections, function types).
146
+ */
147
+ export const extractSimpleTypeName = (typeNode, depth = 0) => {
148
+ if (depth > 50 || typeNode.text.length > 2048)
149
+ return undefined;
150
+ // Direct type identifier (includes Ruby 'constant' for class names)
151
+ if (typeNode.type === 'type_identifier' || typeNode.type === 'identifier'
152
+ || typeNode.type === 'simple_identifier' || typeNode.type === 'constant') {
153
+ return typeNode.text;
154
+ }
155
+ // Qualified/scoped names: take the last segment (e.g., models.User → User, Models::User → User)
156
+ if (typeNode.type === 'scoped_identifier' || typeNode.type === 'qualified_identifier'
157
+ || typeNode.type === 'scoped_type_identifier' || typeNode.type === 'qualified_name'
158
+ || typeNode.type === 'qualified_type'
159
+ || typeNode.type === 'member_expression' || typeNode.type === 'member_access_expression'
160
+ || typeNode.type === 'attribute'
161
+ || typeNode.type === 'scope_resolution'
162
+ || typeNode.type === 'selector_expression') {
163
+ const last = typeNode.lastNamedChild;
164
+ if (last && (last.type === 'type_identifier' || last.type === 'identifier'
165
+ || last.type === 'simple_identifier' || last.type === 'name'
166
+ || last.type === 'constant' || last.type === 'property_identifier'
167
+ || last.type === 'field_identifier')) {
168
+ return last.text;
169
+ }
170
+ }
171
+ // C++ template_type (e.g., vector<User>, map<string, User>): extract base name
172
+ if (typeNode.type === 'template_type') {
173
+ const base = typeNode.childForFieldName('name') ?? typeNode.firstNamedChild;
174
+ if (base)
175
+ return extractSimpleTypeName(base, depth + 1);
176
+ }
177
+ // Generic types: extract the base type (e.g., List<User> → List)
178
+ // For nullable wrappers (Optional<User>, Option<User>), unwrap to inner type.
179
+ if (typeNode.type === 'generic_type' || typeNode.type === 'parameterized_type'
180
+ || typeNode.type === 'generic_name') {
181
+ const base = typeNode.childForFieldName('name')
182
+ ?? typeNode.childForFieldName('type')
183
+ ?? typeNode.firstNamedChild;
184
+ if (!base)
185
+ return undefined;
186
+ const baseName = extractSimpleTypeName(base, depth + 1);
187
+ // Unwrap known nullable wrappers: Optional<User> → User, Option<User> → User
188
+ if (baseName && NULLABLE_WRAPPER_TYPES.has(baseName)) {
189
+ const args = extractGenericTypeArgs(typeNode);
190
+ if (args.length >= 1)
191
+ return args[0];
192
+ }
193
+ return baseName;
194
+ }
195
+ // Nullable types (Kotlin User?, C# User?)
196
+ if (typeNode.type === 'nullable_type') {
197
+ const inner = typeNode.firstNamedChild;
198
+ if (inner)
199
+ return extractSimpleTypeName(inner, depth + 1);
200
+ }
201
+ // Nullable union types (TS/JS: User | null, User | undefined, User | null | undefined)
202
+ // Extract the single non-null/undefined type from the union.
203
+ if (typeNode.type === 'union_type') {
204
+ const nonNullTypes = [];
205
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
206
+ const child = typeNode.namedChild(i);
207
+ if (!child)
208
+ continue;
209
+ // Skip null/undefined/void literal types
210
+ const text = child.text;
211
+ if (text === 'null' || text === 'undefined' || text === 'void')
212
+ continue;
213
+ nonNullTypes.push(child);
214
+ }
215
+ // Only unwrap if exactly one meaningful type remains
216
+ if (nonNullTypes.length === 1) {
217
+ return extractSimpleTypeName(nonNullTypes[0], depth + 1);
218
+ }
219
+ }
220
+ // Type annotations that wrap the actual type (TS/Python: `: Foo`, Kotlin: user_type)
221
+ if (typeNode.type === 'type_annotation' || typeNode.type === 'type'
222
+ || typeNode.type === 'user_type') {
223
+ const inner = typeNode.firstNamedChild;
224
+ if (inner)
225
+ return extractSimpleTypeName(inner, depth + 1);
226
+ }
227
+ // Pointer/reference types (C++, Rust): User*, &User, &mut User
228
+ if (typeNode.type === 'pointer_type' || typeNode.type === 'reference_type') {
229
+ // Skip mutable_specifier for Rust &mut references — firstNamedChild would be
230
+ // `mutable_specifier` not the actual type. Walk named children to find the type.
231
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
232
+ const child = typeNode.namedChild(i);
233
+ if (child && child.type !== 'mutable_specifier') {
234
+ return extractSimpleTypeName(child, depth + 1);
235
+ }
236
+ }
237
+ }
238
+ // Primitive/predefined types: string, int, float, bool, number, unknown, any
239
+ // PHP: primitive_type; TS/JS: predefined_type
240
+ if (typeNode.type === 'primitive_type' || typeNode.type === 'predefined_type') {
241
+ return typeNode.text;
242
+ }
243
+ // PHP named_type / optional_type
244
+ if (typeNode.type === 'named_type' || typeNode.type === 'optional_type') {
245
+ const inner = typeNode.childForFieldName('name') ?? typeNode.firstNamedChild;
246
+ if (inner)
247
+ return extractSimpleTypeName(inner, depth + 1);
248
+ }
249
+ // Name node (PHP)
250
+ if (typeNode.type === 'name') {
251
+ return typeNode.text;
252
+ }
253
+ return undefined;
254
+ };
255
+ /**
256
+ * Extract variable name from a declarator or pattern node.
257
+ * Returns the simple identifier text, or undefined for destructuring/complex patterns.
258
+ */
259
+ export const extractVarName = (node) => {
260
+ if (node.type === 'identifier' || node.type === 'simple_identifier'
261
+ || node.type === 'variable_name' || node.type === 'name'
262
+ || node.type === 'constant' || node.type === 'property_identifier') {
263
+ return node.text;
264
+ }
265
+ // variable_declarator (Java/C#): has a 'name' field
266
+ if (node.type === 'variable_declarator') {
267
+ const nameChild = node.childForFieldName('name');
268
+ if (nameChild)
269
+ return extractVarName(nameChild);
270
+ }
271
+ // Rust: let mut x = ... — mut_pattern wraps an identifier
272
+ if (node.type === 'mut_pattern') {
273
+ const inner = node.firstNamedChild;
274
+ if (inner)
275
+ return extractVarName(inner);
276
+ }
277
+ return undefined;
278
+ };
279
+ /** Node types for function/method parameters with type annotations */
280
+ export const TYPED_PARAMETER_TYPES = new Set([
281
+ 'required_parameter', // TS: (x: Foo)
282
+ 'optional_parameter', // TS: (x?: Foo)
283
+ 'formal_parameter', // Java/Kotlin
284
+ 'parameter', // C#/Rust/Go/Python/Swift
285
+ 'typed_parameter', // Python: def f(x: Foo) — distinct from 'parameter' in tree-sitter-python
286
+ 'parameter_declaration', // C/C++ void f(Type name)
287
+ 'simple_parameter', // PHP function(Foo $x)
288
+ 'property_promotion_parameter', // PHP 8.0+ constructor promotion: __construct(private Foo $x)
289
+ 'closure_parameter', // Rust: |user: User| — typed closure parameters
290
+ ]);
291
+ /**
292
+ * Extract type arguments from a generic type node.
293
+ * e.g., List<User, String> → ['User', 'String'], Vec<User> → ['User']
294
+ *
295
+ * Used by extractSimpleTypeName to unwrap nullable wrappers (Optional<User> → User).
296
+ *
297
+ * Handles language-specific AST structures:
298
+ * - TS/Java/Rust/Go: generic_type > type_arguments > type nodes
299
+ * - C#: generic_type > type_argument_list > type nodes
300
+ * - Kotlin: generic_type > type_arguments > type_projection > type nodes
301
+ *
302
+ * Note: Go slices/maps use slice_type/map_type, not generic_type — those are
303
+ * NOT handled here. Use language-specific extractors for Go container types.
304
+ *
305
+ * @param typeNode A generic_type or parameterized_type AST node (or any node —
306
+ * returns [] for non-generic types).
307
+ * @returns Array of resolved type argument names. Unresolvable arguments are omitted.
308
+ */
309
+ export const extractGenericTypeArgs = (typeNode, depth = 0) => {
310
+ if (depth > 50)
311
+ return [];
312
+ // Unwrap wrapper nodes that may sit above the generic_type
313
+ if (typeNode.type === 'type_annotation' || typeNode.type === 'type'
314
+ || typeNode.type === 'user_type' || typeNode.type === 'nullable_type'
315
+ || typeNode.type === 'optional_type') {
316
+ const inner = typeNode.firstNamedChild;
317
+ if (inner)
318
+ return extractGenericTypeArgs(inner, depth + 1);
319
+ return [];
320
+ }
321
+ // Only process generic/parameterized type nodes (includes C#'s generic_name)
322
+ if (typeNode.type !== 'generic_type' && typeNode.type !== 'parameterized_type'
323
+ && typeNode.type !== 'generic_name') {
324
+ return [];
325
+ }
326
+ // Find the type_arguments / type_argument_list child
327
+ let argsNode = null;
328
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
329
+ const child = typeNode.namedChild(i);
330
+ if (child && (child.type === 'type_arguments' || child.type === 'type_argument_list')) {
331
+ argsNode = child;
332
+ break;
333
+ }
334
+ }
335
+ if (!argsNode)
336
+ return [];
337
+ const result = [];
338
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
339
+ let argNode = argsNode.namedChild(i);
340
+ if (!argNode)
341
+ continue;
342
+ // Kotlin: type_arguments > type_projection > user_type > type_identifier
343
+ if (argNode.type === 'type_projection') {
344
+ argNode = argNode.firstNamedChild;
345
+ if (!argNode)
346
+ continue;
347
+ }
348
+ const name = extractSimpleTypeName(argNode);
349
+ if (name)
350
+ result.push(name);
351
+ }
352
+ return result;
353
+ };
354
+ /**
355
+ * Match Ruby constructor assignment: `user = User.new` or `service = Models::User.new`.
356
+ * Returns { varName, calleeName } or undefined if the node is not a Ruby constructor assignment.
357
+ * Handles both simple constants and scope_resolution (namespaced) receivers.
358
+ */
359
+ export const extractRubyConstructorAssignment = (node) => {
360
+ if (node.type !== 'assignment')
361
+ return undefined;
362
+ const left = node.childForFieldName('left');
363
+ const right = node.childForFieldName('right');
364
+ if (!left || !right)
365
+ return undefined;
366
+ if (left.type !== 'identifier' && left.type !== 'constant')
367
+ return undefined;
368
+ if (right.type !== 'call')
369
+ return undefined;
370
+ const method = right.childForFieldName('method');
371
+ if (!method || method.text !== 'new')
372
+ return undefined;
373
+ const receiver = right.childForFieldName('receiver');
374
+ if (!receiver)
375
+ return undefined;
376
+ let calleeName;
377
+ if (receiver.type === 'constant') {
378
+ calleeName = receiver.text;
379
+ }
380
+ else if (receiver.type === 'scope_resolution') {
381
+ // Models::User → extract last segment "User"
382
+ const last = receiver.lastNamedChild;
383
+ if (!last || last.type !== 'constant')
384
+ return undefined;
385
+ calleeName = last.text;
386
+ }
387
+ else {
388
+ return undefined;
389
+ }
390
+ return { varName: left.text, calleeName };
391
+ };
392
+ /**
393
+ * Check if an AST node has an explicit type annotation.
394
+ * Checks both named fields ('type') and child nodes ('type_annotation').
395
+ * Used by constructor binding scanners to skip annotated declarations.
396
+ */
397
+ export const hasTypeAnnotation = (node) => {
398
+ if (node.childForFieldName('type'))
399
+ return true;
400
+ for (let i = 0; i < node.childCount; i++) {
401
+ if (node.child(i)?.type === 'type_annotation')
402
+ return true;
403
+ }
404
+ return false;
405
+ };
406
+ /** Bare nullable keywords that should not produce a receiver binding. */
407
+ const NULLABLE_KEYWORDS = new Set(['null', 'undefined', 'void', 'None', 'nil']);
408
+ /**
409
+ * Strip nullable wrappers from a type name string.
410
+ * Used by both lookupInEnv (TypeEnv annotations) and extractReturnTypeName
411
+ * (return-type text) to normalize types before receiver lookup.
412
+ *
413
+ * "User | null" → "User"
414
+ * "User | undefined" → "User"
415
+ * "User | null | undefined" → "User"
416
+ * "User?" → "User"
417
+ * "User | Repo" → undefined (genuine union — refuse)
418
+ * "null" → undefined
419
+ */
420
+ export const stripNullable = (typeName) => {
421
+ let text = typeName.trim();
422
+ if (!text)
423
+ return undefined;
424
+ if (NULLABLE_KEYWORDS.has(text))
425
+ return undefined;
426
+ // Strip nullable suffix: User? → User
427
+ if (text.endsWith('?'))
428
+ text = text.slice(0, -1).trim();
429
+ // Strip union with null/undefined/None/nil/void
430
+ if (text.includes('|')) {
431
+ const parts = text.split('|').map(p => p.trim()).filter(p => p !== '' && !NULLABLE_KEYWORDS.has(p));
432
+ if (parts.length === 1)
433
+ return parts[0];
434
+ return undefined; // genuine union or all-nullable — refuse
435
+ }
436
+ return text || undefined;
437
+ };
438
+ /**
439
+ * Unwrap an await_expression to get the inner value.
440
+ * Returns the node itself if not an await_expression, or null if input is null.
441
+ */
442
+ export const unwrapAwait = (node) => {
443
+ if (!node)
444
+ return null;
445
+ return node.type === 'await_expression' ? node.firstNamedChild : node;
446
+ };
447
+ /**
448
+ * Extract the callee name from a call_expression node.
449
+ * Navigates to the 'function' field (or first named child) and extracts a simple type name.
450
+ */
451
+ export const extractCalleeName = (callNode) => {
452
+ const func = callNode.childForFieldName('function') ?? callNode.firstNamedChild;
453
+ if (!func)
454
+ return undefined;
455
+ return extractSimpleTypeName(func);
456
+ };
457
+ /** Find the first named child with the given node type */
458
+ export const findChildByType = (node, type) => {
459
+ for (let i = 0; i < node.namedChildCount; i++) {
460
+ const child = node.namedChild(i);
461
+ if (child?.type === type)
462
+ return child;
463
+ }
464
+ return null;
465
+ };
466
+ // Internal helper: extract the first comma-separated argument from a string,
467
+ // respecting nested angle-bracket and square-bracket depth.
468
+ function extractFirstArg(args) {
469
+ let depth = 0;
470
+ for (let i = 0; i < args.length; i++) {
471
+ const ch = args[i];
472
+ if (ch === '<' || ch === '[')
473
+ depth++;
474
+ else if (ch === '>' || ch === ']')
475
+ depth--;
476
+ else if (ch === ',' && depth === 0)
477
+ return args.slice(0, i).trim();
478
+ }
479
+ return args.trim();
480
+ }
481
+ /**
482
+ * Extract element type from a container type string.
483
+ * Uses bracket-balanced parsing (no regex) for generic argument extraction.
484
+ * Returns undefined for ambiguous or unparseable strings.
485
+ *
486
+ * Handles:
487
+ * - Array<User> → User (generic angle brackets)
488
+ * - User[] → User (array suffix)
489
+ * - []User → User (Go slice prefix)
490
+ * - List[User] → User (Python subscript)
491
+ * - [User] → User (Swift array sugar)
492
+ * - vector<User> → User (C++ container)
493
+ * - Vec<User> → User (Rust container)
494
+ *
495
+ * For multi-argument generics (Map<K, V>), returns the first or last type arg
496
+ * based on `pos` ('first' for keys, 'last' for values — default 'last').
497
+ * Returns undefined when the extracted type is not a simple word.
498
+ */
499
+ export function extractElementTypeFromString(typeStr, pos = 'last') {
500
+ if (!typeStr || typeStr.length === 0 || typeStr.length > 2048)
501
+ return undefined;
502
+ // 1. Array suffix: User[] → User
503
+ if (typeStr.endsWith('[]')) {
504
+ const base = typeStr.slice(0, -2).trim();
505
+ return base && /^\w+$/.test(base) ? base : undefined;
506
+ }
507
+ // 2. Go slice prefix: []User → User
508
+ if (typeStr.startsWith('[]')) {
509
+ const element = typeStr.slice(2).trim();
510
+ return element && /^\w+$/.test(element) ? element : undefined;
511
+ }
512
+ // 3. Swift array sugar: [User] → User
513
+ // Must start with '[', end with ']', and contain no angle brackets
514
+ // (to avoid confusing with List[User] handled below).
515
+ if (typeStr.startsWith('[') && typeStr.endsWith(']') && !typeStr.includes('<')) {
516
+ const element = typeStr.slice(1, -1).trim();
517
+ return element && /^\w+$/.test(element) ? element : undefined;
518
+ }
519
+ // 4. Generic bracket-balanced extraction: Array<User> / List[User] / Vec<User>
520
+ // Find the first opening bracket (< or [) and pick the one that appears first.
521
+ const openAngle = typeStr.indexOf('<');
522
+ const openSquare = typeStr.indexOf('[');
523
+ let openIdx = -1;
524
+ let openChar = '';
525
+ let closeChar = '';
526
+ if (openAngle >= 0 && (openSquare < 0 || openAngle < openSquare)) {
527
+ openIdx = openAngle;
528
+ openChar = '<';
529
+ closeChar = '>';
530
+ }
531
+ else if (openSquare >= 0) {
532
+ openIdx = openSquare;
533
+ openChar = '[';
534
+ closeChar = ']';
535
+ }
536
+ if (openIdx < 0)
537
+ return undefined;
538
+ // Walk bracket-balanced from the character after the opening bracket to find
539
+ // the matching close bracket, tracking depth for nested brackets.
540
+ // All bracket types (<, >, [, ]) contribute to depth uniformly, but only the
541
+ // selected closeChar can match at depth 0 (prevents cross-bracket miscounting).
542
+ let depth = 0;
543
+ const start = openIdx + 1;
544
+ let lastCommaIdx = -1; // Track last top-level comma for 'last' position
545
+ for (let i = start; i < typeStr.length; i++) {
546
+ const ch = typeStr[i];
547
+ if (ch === '<' || ch === '[') {
548
+ depth++;
549
+ }
550
+ else if (ch === '>' || ch === ']') {
551
+ if (depth === 0) {
552
+ // At depth 0 — only match if it is our selected close bracket.
553
+ if (ch !== closeChar)
554
+ return undefined; // mismatched bracket = malformed
555
+ if (pos === 'last' && lastCommaIdx >= 0) {
556
+ // Return last arg (text after last comma)
557
+ const lastArg = typeStr.slice(lastCommaIdx + 1, i).trim();
558
+ return lastArg && /^\w+$/.test(lastArg) ? lastArg : undefined;
559
+ }
560
+ const inner = typeStr.slice(start, i).trim();
561
+ const firstArg = extractFirstArg(inner);
562
+ return firstArg && /^\w+$/.test(firstArg) ? firstArg : undefined;
563
+ }
564
+ depth--;
565
+ }
566
+ else if (ch === ',' && depth === 0) {
567
+ if (pos === 'first') {
568
+ // Return first arg (text before first comma)
569
+ const arg = typeStr.slice(start, i).trim();
570
+ return arg && /^\w+$/.test(arg) ? arg : undefined;
571
+ }
572
+ lastCommaIdx = i;
573
+ }
574
+ }
575
+ return undefined;
576
+ }
577
+ // ── Return type text helpers ─────────────────────────────────────────────
578
+ // extractReturnTypeName works on raw return-type text already stored in
579
+ // SymbolDefinition (e.g. "User", "Promise<User>", "User | null", "*User").
580
+ // Extracts the base user-defined type name.
581
+ /** Primitive / built-in types that should NOT produce a receiver binding. */
582
+ const PRIMITIVE_TYPES = new Set([
583
+ 'string', 'number', 'boolean', 'void', 'int', 'float', 'double', 'long',
584
+ 'short', 'byte', 'char', 'bool', 'str', 'i8', 'i16', 'i32', 'i64',
585
+ 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'usize', 'isize',
586
+ 'undefined', 'null', 'None', 'nil',
587
+ ]);
588
+ /**
589
+ * Extract a simple type name from raw return-type text.
590
+ * Handles common patterns:
591
+ * "User" → "User"
592
+ * "Promise<User>" → "User" (unwrap wrapper generics)
593
+ * "Option<User>" → "User"
594
+ * "Result<User, Error>" → "User" (first type arg)
595
+ * "User | null" → "User" (strip nullable union)
596
+ * "User?" → "User" (strip nullable suffix)
597
+ * "*User" → "User" (Go pointer)
598
+ * "&User" → "User" (Rust reference)
599
+ * Returns undefined for complex types or primitives.
600
+ */
601
+ const WRAPPER_GENERICS = new Set([
602
+ 'Promise', 'Observable', 'Future', 'CompletableFuture', 'Task', 'ValueTask', // async wrappers
603
+ 'Option', 'Some', 'Optional', 'Maybe', // nullable wrappers
604
+ 'Result', 'Either', // result wrappers
605
+ // Rust smart pointers (Deref to inner type)
606
+ 'Rc', 'Arc', 'Weak', // pointer types
607
+ 'MutexGuard', 'RwLockReadGuard', 'RwLockWriteGuard', // guard types
608
+ 'Ref', 'RefMut', // RefCell guards
609
+ 'Cow', // copy-on-write
610
+ // Containers (List, Array, Vec, Set, etc.) are intentionally excluded —
611
+ // methods are called on the container, not the element type.
612
+ // Non-wrapper generics return the base type (e.g., List) via the else branch.
613
+ ]);
614
+ /**
615
+ * Extracts the first type argument from a comma-separated generic argument string,
616
+ * respecting nested angle brackets. For example:
617
+ * "Result<User, Error>" → "Result<User, Error>" (no top-level comma)
618
+ * "User, Error" → "User"
619
+ * "Map<K, V>, string" → "Map<K, V>"
620
+ */
621
+ function extractFirstGenericArg(args) {
622
+ let depth = 0;
623
+ for (let i = 0; i < args.length; i++) {
624
+ if (args[i] === '<')
625
+ depth++;
626
+ else if (args[i] === '>')
627
+ depth--;
628
+ else if (args[i] === ',' && depth === 0)
629
+ return args.slice(0, i).trim();
630
+ }
631
+ return args.trim();
632
+ }
633
+ /**
634
+ * Extract the first non-lifetime type argument from a generic argument string.
635
+ * Skips Rust lifetime parameters (e.g., `'a`, `'_`) to find the actual type.
636
+ * "'_, User" → "User"
637
+ * "'a, User" → "User"
638
+ * "User, Error" → "User" (no lifetime — delegates to extractFirstGenericArg)
639
+ */
640
+ function extractFirstTypeArg(args) {
641
+ let remaining = args;
642
+ while (remaining) {
643
+ const first = extractFirstGenericArg(remaining);
644
+ if (!first.startsWith("'"))
645
+ return first;
646
+ // Skip past this lifetime arg + the comma separator
647
+ const commaIdx = remaining.indexOf(',', first.length);
648
+ if (commaIdx < 0)
649
+ return first; // only lifetimes — fall through
650
+ remaining = remaining.slice(commaIdx + 1).trim();
651
+ }
652
+ return args.trim();
653
+ }
654
+ const MAX_RETURN_TYPE_INPUT_LENGTH = 2048;
655
+ const MAX_RETURN_TYPE_LENGTH = 512;
656
+ export const extractReturnTypeName = (raw, depth = 0) => {
657
+ if (depth > 10)
658
+ return undefined;
659
+ if (raw.length > MAX_RETURN_TYPE_INPUT_LENGTH)
660
+ return undefined;
661
+ let text = raw.trim();
662
+ if (!text)
663
+ return undefined;
664
+ // Strip pointer/reference prefixes: *User, &User, &mut User
665
+ text = text.replace(/^[&*]+\s*(mut\s+)?/, '');
666
+ // Strip nullable suffix: User?
667
+ text = text.replace(/\?$/, '');
668
+ // Handle union types: "User | null" → "User"
669
+ if (text.includes('|')) {
670
+ const parts = text.split('|').map(p => p.trim()).filter(p => p !== 'null' && p !== 'undefined' && p !== 'void' && p !== 'None' && p !== 'nil');
671
+ if (parts.length === 1)
672
+ text = parts[0];
673
+ else
674
+ return undefined; // genuine union — too complex
675
+ }
676
+ // Handle generics: Promise<User> → unwrap if wrapper, else take base
677
+ const genericMatch = text.match(/^(\w+)\s*<(.+)>$/);
678
+ if (genericMatch) {
679
+ const [, base, args] = genericMatch;
680
+ if (WRAPPER_GENERICS.has(base)) {
681
+ // Take the first non-lifetime type argument, using bracket-balanced splitting
682
+ // so that nested generics like Result<User, Error> are not split at the inner
683
+ // comma. Lifetime parameters (Rust 'a, '_) are skipped.
684
+ const firstArg = extractFirstTypeArg(args);
685
+ return extractReturnTypeName(firstArg, depth + 1);
686
+ }
687
+ // Non-wrapper generic: return the base type (e.g., Map<K,V> → Map)
688
+ return PRIMITIVE_TYPES.has(base.toLowerCase()) ? undefined : base;
689
+ }
690
+ // Bare wrapper type without generic argument (e.g. Task, Promise, Option)
691
+ // should not produce a binding — these are meaningless without a type parameter
692
+ if (WRAPPER_GENERICS.has(text))
693
+ return undefined;
694
+ // Handle qualified names: models.User → User, Models::User → User, \App\Models\User → User
695
+ if (text.includes('::') || text.includes('.') || text.includes('\\')) {
696
+ text = text.split(/::|[.\\]/).pop();
697
+ }
698
+ // Final check: skip primitives
699
+ if (PRIMITIVE_TYPES.has(text) || PRIMITIVE_TYPES.has(text.toLowerCase()))
700
+ return undefined;
701
+ // Must start with uppercase (class/type convention) or be a valid identifier
702
+ if (!/^[A-Z_]\w*$/.test(text))
703
+ return undefined;
704
+ // If the final extracted type name is too long, reject it
705
+ if (text.length > MAX_RETURN_TYPE_LENGTH)
706
+ return undefined;
707
+ return text;
708
+ };
709
+ // ── Property declared-type extraction ────────────────────────────────────
710
+ // Shared between parse-worker (worker path) and parsing-processor (sequential path).
711
+ /**
712
+ * Extract the declared type of a property/field from its AST definition node.
713
+ * Handles cross-language patterns:
714
+ * - TypeScript: `name: Type` → type_annotation child
715
+ * - Java: `Type name` → type child on field_declaration
716
+ * - C#: `Type Name { get; set; }` → type child on property_declaration
717
+ * - Go: `Name Type` → type child on field_declaration
718
+ * - Kotlin: `var name: Type` → variable_declaration child with type field
719
+ *
720
+ * Returns the normalized type name, or undefined if no type can be extracted.
721
+ */
722
+ export const extractPropertyDeclaredType = (definitionNode) => {
723
+ if (!definitionNode)
724
+ return undefined;
725
+ // Strategy 1: Look for a `type` or `type_annotation` named field
726
+ const typeNode = definitionNode.childForFieldName?.('type');
727
+ if (typeNode) {
728
+ const typeName = extractSimpleTypeName(typeNode);
729
+ if (typeName)
730
+ return typeName;
731
+ // Fallback: use the raw text (for complex types like User[] or List<User>)
732
+ const text = typeNode.text?.trim();
733
+ if (text && text.length < 100)
734
+ return text;
735
+ }
736
+ // Strategy 2: Walk children looking for type_annotation (TypeScript pattern)
737
+ for (let i = 0; i < definitionNode.childCount; i++) {
738
+ const child = definitionNode.child(i);
739
+ if (!child)
740
+ continue;
741
+ if (child.type === 'type_annotation') {
742
+ // Type annotation has the actual type as a child
743
+ for (let j = 0; j < child.childCount; j++) {
744
+ const typeChild = child.child(j);
745
+ if (typeChild && typeChild.type !== ':') {
746
+ const typeName = extractSimpleTypeName(typeChild);
747
+ if (typeName)
748
+ return typeName;
749
+ const text = typeChild.text?.trim();
750
+ if (text && text.length < 100)
751
+ return text;
752
+ }
753
+ }
754
+ }
755
+ }
756
+ // Strategy 3: For Java field_declaration, the type is a sibling of variable_declarator
757
+ // AST: (field_declaration type: (type_identifier) declarator: (variable_declarator ...))
758
+ const parentDecl = definitionNode.parent;
759
+ if (parentDecl) {
760
+ const parentType = parentDecl.childForFieldName?.('type');
761
+ if (parentType) {
762
+ const typeName = extractSimpleTypeName(parentType);
763
+ if (typeName)
764
+ return typeName;
765
+ }
766
+ }
767
+ // Strategy 4: Kotlin property_declaration — type is nested inside variable_declaration child
768
+ // AST: (property_declaration (variable_declaration (simple_identifier) ":" (user_type (type_identifier))))
769
+ // Kotlin's variable_declaration has NO named 'type' field — children are all positional.
770
+ for (let i = 0; i < definitionNode.childCount; i++) {
771
+ const child = definitionNode.child(i);
772
+ if (child?.type === 'variable_declaration') {
773
+ // Try named field first (works for other languages sharing this strategy)
774
+ const varType = child.childForFieldName?.('type');
775
+ if (varType) {
776
+ const typeName = extractSimpleTypeName(varType);
777
+ if (typeName)
778
+ return typeName;
779
+ const text = varType.text?.trim();
780
+ if (text && text.length < 100)
781
+ return text;
782
+ }
783
+ // Fallback: walk unnamed children for user_type / type_identifier (Kotlin)
784
+ for (let j = 0; j < child.namedChildCount; j++) {
785
+ const varChild = child.namedChild(j);
786
+ if (varChild && (varChild.type === 'user_type' || varChild.type === 'type_identifier'
787
+ || varChild.type === 'nullable_type' || varChild.type === 'generic_type')) {
788
+ const typeName = extractSimpleTypeName(varChild);
789
+ if (typeName)
790
+ return typeName;
791
+ }
792
+ }
793
+ }
794
+ }
795
+ // Strategy 5: PHP @var PHPDoc — look for preceding comment with @var Type
796
+ // Handles pre-PHP-7.4 code: /** @var Address */ public $address;
797
+ const prevSibling = definitionNode.previousNamedSibling ?? definitionNode.parent?.previousNamedSibling;
798
+ if (prevSibling?.type === 'comment') {
799
+ const commentText = prevSibling.text;
800
+ const varMatch = commentText?.match(/@var\s+([A-Z][\w\\]*)/);
801
+ if (varMatch) {
802
+ // Strip namespace prefix: \App\Models\User → User
803
+ const raw = varMatch[1];
804
+ const base = raw.includes('\\') ? raw.split('\\').pop() : raw;
805
+ if (base && /^[A-Z]\w*$/.test(base))
806
+ return base;
807
+ }
808
+ }
809
+ return undefined;
810
+ };