gitnexus 1.5.3 → 1.6.1

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 (304) hide show
  1. package/README.md +10 -0
  2. package/dist/_shared/graph/types.d.ts +1 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +1 -0
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/language-detection.d.ts.map +1 -1
  7. package/dist/_shared/language-detection.js +2 -0
  8. package/dist/_shared/language-detection.js.map +1 -1
  9. package/dist/_shared/languages.d.ts +1 -0
  10. package/dist/_shared/languages.d.ts.map +1 -1
  11. package/dist/_shared/languages.js +1 -0
  12. package/dist/_shared/languages.js.map +1 -1
  13. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  14. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.js +3 -1
  16. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  17. package/dist/_shared/mro-strategy.d.ts +19 -0
  18. package/dist/_shared/mro-strategy.d.ts.map +1 -0
  19. package/dist/_shared/mro-strategy.js +2 -0
  20. package/dist/_shared/mro-strategy.js.map +1 -0
  21. package/dist/cli/ai-context.d.ts +1 -0
  22. package/dist/cli/ai-context.js +28 -4
  23. package/dist/cli/analyze.d.ts +2 -0
  24. package/dist/cli/analyze.js +30 -4
  25. package/dist/cli/group.d.ts +2 -0
  26. package/dist/cli/group.js +233 -0
  27. package/dist/cli/index.js +3 -0
  28. package/dist/cli/serve.js +4 -1
  29. package/dist/cli/setup.js +34 -3
  30. package/dist/config/ignore-service.js +8 -3
  31. package/dist/core/augmentation/engine.js +1 -1
  32. package/dist/core/git-staleness.d.ts +13 -0
  33. package/dist/core/git-staleness.js +29 -0
  34. package/dist/core/group/bridge-db.d.ts +82 -0
  35. package/dist/core/group/bridge-db.js +460 -0
  36. package/dist/core/group/bridge-schema.d.ts +27 -0
  37. package/dist/core/group/bridge-schema.js +55 -0
  38. package/dist/core/group/config-parser.d.ts +3 -0
  39. package/dist/core/group/config-parser.js +83 -0
  40. package/dist/core/group/contract-extractor.d.ts +7 -0
  41. package/dist/core/group/contract-extractor.js +1 -0
  42. package/dist/core/group/extractors/fs-utils.d.ts +10 -0
  43. package/dist/core/group/extractors/fs-utils.js +24 -0
  44. package/dist/core/group/extractors/grpc-extractor.d.ts +25 -0
  45. package/dist/core/group/extractors/grpc-extractor.js +386 -0
  46. package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
  47. package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
  48. package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
  49. package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
  50. package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
  51. package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
  52. package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
  53. package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
  54. package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
  55. package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
  56. package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
  57. package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
  58. package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
  59. package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
  60. package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
  61. package/dist/core/group/extractors/http-patterns/go.js +215 -0
  62. package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
  63. package/dist/core/group/extractors/http-patterns/index.js +44 -0
  64. package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
  65. package/dist/core/group/extractors/http-patterns/java.js +253 -0
  66. package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
  67. package/dist/core/group/extractors/http-patterns/node.js +354 -0
  68. package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
  69. package/dist/core/group/extractors/http-patterns/php.js +70 -0
  70. package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
  71. package/dist/core/group/extractors/http-patterns/python.js +133 -0
  72. package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
  73. package/dist/core/group/extractors/http-patterns/types.js +1 -0
  74. package/dist/core/group/extractors/http-route-extractor.d.ts +21 -0
  75. package/dist/core/group/extractors/http-route-extractor.js +391 -0
  76. package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
  77. package/dist/core/group/extractors/manifest-extractor.js +235 -0
  78. package/dist/core/group/extractors/topic-extractor.d.ts +8 -0
  79. package/dist/core/group/extractors/topic-extractor.js +97 -0
  80. package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
  81. package/dist/core/group/extractors/topic-patterns/go.js +120 -0
  82. package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
  83. package/dist/core/group/extractors/topic-patterns/index.js +38 -0
  84. package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
  85. package/dist/core/group/extractors/topic-patterns/java.js +80 -0
  86. package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
  87. package/dist/core/group/extractors/topic-patterns/node.js +155 -0
  88. package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
  89. package/dist/core/group/extractors/topic-patterns/python.js +116 -0
  90. package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
  91. package/dist/core/group/extractors/topic-patterns/types.js +10 -0
  92. package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
  93. package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
  94. package/dist/core/group/matching.d.ts +13 -0
  95. package/dist/core/group/matching.js +198 -0
  96. package/dist/core/group/normalization.d.ts +3 -0
  97. package/dist/core/group/normalization.js +115 -0
  98. package/dist/core/group/service-boundary-detector.d.ts +8 -0
  99. package/dist/core/group/service-boundary-detector.js +155 -0
  100. package/dist/core/group/service.d.ts +46 -0
  101. package/dist/core/group/service.js +160 -0
  102. package/dist/core/group/storage.d.ts +9 -0
  103. package/dist/core/group/storage.js +91 -0
  104. package/dist/core/group/sync.d.ts +21 -0
  105. package/dist/core/group/sync.js +148 -0
  106. package/dist/core/group/types.d.ts +130 -0
  107. package/dist/core/group/types.js +1 -0
  108. package/dist/core/ingestion/binding-accumulator.d.ts +212 -0
  109. package/dist/core/ingestion/binding-accumulator.js +336 -0
  110. package/dist/core/ingestion/call-processor.d.ts +155 -24
  111. package/dist/core/ingestion/call-processor.js +1129 -247
  112. package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
  113. package/dist/core/ingestion/class-extractors/generic.js +135 -0
  114. package/dist/core/ingestion/class-types.d.ts +34 -0
  115. package/dist/core/ingestion/class-types.js +1 -0
  116. package/dist/core/ingestion/cobol-processor.d.ts +1 -1
  117. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
  118. package/dist/core/ingestion/entry-point-scoring.js +1 -0
  119. package/dist/core/ingestion/field-types.d.ts +2 -2
  120. package/dist/core/ingestion/filesystem-walker.js +8 -0
  121. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  122. package/dist/core/ingestion/framework-detection.js +1 -0
  123. package/dist/core/ingestion/heritage-processor.d.ts +8 -15
  124. package/dist/core/ingestion/heritage-processor.js +15 -28
  125. package/dist/core/ingestion/import-processor.d.ts +1 -11
  126. package/dist/core/ingestion/import-processor.js +1 -13
  127. package/dist/core/ingestion/import-resolvers/utils.js +1 -0
  128. package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
  129. package/dist/core/ingestion/import-resolvers/vue.js +9 -0
  130. package/dist/core/ingestion/language-config.js +1 -1
  131. package/dist/core/ingestion/language-provider.d.ts +14 -3
  132. package/dist/core/ingestion/languages/c-cpp.js +168 -1
  133. package/dist/core/ingestion/languages/csharp.js +20 -0
  134. package/dist/core/ingestion/languages/dart.js +26 -4
  135. package/dist/core/ingestion/languages/go.js +22 -0
  136. package/dist/core/ingestion/languages/index.d.ts +1 -0
  137. package/dist/core/ingestion/languages/index.js +2 -0
  138. package/dist/core/ingestion/languages/java.js +17 -0
  139. package/dist/core/ingestion/languages/kotlin.js +24 -1
  140. package/dist/core/ingestion/languages/php.js +23 -11
  141. package/dist/core/ingestion/languages/python.js +9 -0
  142. package/dist/core/ingestion/languages/ruby.js +43 -0
  143. package/dist/core/ingestion/languages/rust.js +38 -0
  144. package/dist/core/ingestion/languages/swift.js +31 -0
  145. package/dist/core/ingestion/languages/typescript.d.ts +1 -0
  146. package/dist/core/ingestion/languages/typescript.js +52 -3
  147. package/dist/core/ingestion/languages/vue.d.ts +13 -0
  148. package/dist/core/ingestion/languages/vue.js +81 -0
  149. package/dist/core/ingestion/markdown-processor.d.ts +1 -1
  150. package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
  151. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
  152. package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
  153. package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
  154. package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
  155. package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
  156. package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
  157. package/dist/core/ingestion/method-extractors/configs/jvm.js +14 -4
  158. package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
  159. package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
  160. package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
  161. package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
  162. package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
  163. package/dist/core/ingestion/method-extractors/configs/ruby.js +286 -0
  164. package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
  165. package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
  166. package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
  167. package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
  168. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +85 -8
  169. package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
  170. package/dist/core/ingestion/method-extractors/generic.js +84 -17
  171. package/dist/core/ingestion/method-types.d.ts +29 -0
  172. package/dist/core/ingestion/model/field-registry.d.ts +18 -0
  173. package/dist/core/ingestion/model/field-registry.js +22 -0
  174. package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
  175. package/dist/core/ingestion/model/heritage-map.js +159 -0
  176. package/dist/core/ingestion/model/index.d.ts +20 -0
  177. package/dist/core/ingestion/model/index.js +41 -0
  178. package/dist/core/ingestion/model/method-registry.d.ts +62 -0
  179. package/dist/core/ingestion/model/method-registry.js +130 -0
  180. package/dist/core/ingestion/model/registration-table.d.ts +139 -0
  181. package/dist/core/ingestion/model/registration-table.js +224 -0
  182. package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
  183. package/dist/core/ingestion/model/resolution-context.js +337 -0
  184. package/dist/core/ingestion/model/resolve.d.ts +56 -0
  185. package/dist/core/ingestion/model/resolve.js +297 -0
  186. package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
  187. package/dist/core/ingestion/model/semantic-model.js +120 -0
  188. package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
  189. package/dist/core/ingestion/model/symbol-table.js +206 -0
  190. package/dist/core/ingestion/model/type-registry.d.ts +39 -0
  191. package/dist/core/ingestion/model/type-registry.js +62 -0
  192. package/dist/core/ingestion/mro-processor.d.ts +5 -4
  193. package/dist/core/ingestion/mro-processor.js +311 -107
  194. package/dist/core/ingestion/parsing-processor.d.ts +5 -4
  195. package/dist/core/ingestion/parsing-processor.js +224 -87
  196. package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
  197. package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
  198. package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
  199. package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
  200. package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
  201. package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
  202. package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
  203. package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
  204. package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
  205. package/dist/core/ingestion/pipeline-phases/index.js +22 -0
  206. package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
  207. package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
  208. package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
  209. package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
  210. package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
  211. package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
  212. package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
  213. package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
  214. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
  215. package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
  216. package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
  217. package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
  218. package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
  219. package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
  220. package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
  221. package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
  222. package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
  223. package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
  224. package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
  225. package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
  226. package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
  227. package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
  228. package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
  229. package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
  230. package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
  231. package/dist/core/ingestion/pipeline-phases/types.js +37 -0
  232. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +35 -0
  233. package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +174 -0
  234. package/dist/core/ingestion/pipeline.d.ts +18 -10
  235. package/dist/core/ingestion/pipeline.js +66 -1410
  236. package/dist/core/ingestion/process-processor.js +1 -1
  237. package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
  238. package/dist/core/ingestion/tree-sitter-queries.js +90 -0
  239. package/dist/core/ingestion/type-env.d.ts +15 -2
  240. package/dist/core/ingestion/type-env.js +163 -102
  241. package/dist/core/ingestion/type-extractors/csharp.js +17 -0
  242. package/dist/core/ingestion/type-extractors/jvm.js +11 -0
  243. package/dist/core/ingestion/type-extractors/php.js +0 -55
  244. package/dist/core/ingestion/type-extractors/ruby.js +0 -32
  245. package/dist/core/ingestion/type-extractors/swift.js +13 -0
  246. package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
  247. package/dist/core/ingestion/type-extractors/typescript.js +66 -69
  248. package/dist/core/ingestion/utils/ast-helpers.d.ts +32 -44
  249. package/dist/core/ingestion/utils/ast-helpers.js +157 -573
  250. package/dist/core/ingestion/utils/env.d.ts +10 -0
  251. package/dist/core/ingestion/utils/env.js +10 -0
  252. package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
  253. package/dist/core/ingestion/utils/graph-sort.js +100 -0
  254. package/dist/core/ingestion/utils/method-props.d.ts +32 -0
  255. package/dist/core/ingestion/utils/method-props.js +147 -0
  256. package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
  257. package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
  258. package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
  259. package/dist/core/ingestion/workers/parse-worker.js +469 -200
  260. package/dist/core/lbug/lbug-adapter.d.ts +6 -0
  261. package/dist/core/lbug/lbug-adapter.js +134 -27
  262. package/dist/core/lbug/pool-adapter.d.ts +76 -0
  263. package/dist/core/lbug/pool-adapter.js +522 -0
  264. package/dist/core/run-analyze.d.ts +2 -0
  265. package/dist/core/run-analyze.js +1 -1
  266. package/dist/core/search/bm25-index.js +1 -1
  267. package/dist/core/tree-sitter/parser-loader.js +1 -0
  268. package/dist/core/wiki/graph-queries.js +1 -1
  269. package/dist/mcp/core/embedder.js +6 -5
  270. package/dist/mcp/core/lbug-adapter.d.ts +3 -63
  271. package/dist/mcp/core/lbug-adapter.js +3 -484
  272. package/dist/mcp/local/local-backend.d.ts +31 -2
  273. package/dist/mcp/local/local-backend.js +255 -46
  274. package/dist/mcp/resources.js +5 -4
  275. package/dist/mcp/staleness.d.ts +3 -13
  276. package/dist/mcp/staleness.js +2 -31
  277. package/dist/mcp/tools.js +80 -4
  278. package/dist/server/analyze-job.d.ts +2 -0
  279. package/dist/server/analyze-job.js +4 -0
  280. package/dist/server/api.d.ts +20 -1
  281. package/dist/server/api.js +306 -71
  282. package/dist/server/git-clone.d.ts +2 -1
  283. package/dist/server/git-clone.js +98 -5
  284. package/dist/storage/git.d.ts +13 -0
  285. package/dist/storage/git.js +25 -0
  286. package/dist/storage/repo-manager.js +1 -1
  287. package/package.json +9 -3
  288. package/scripts/patch-tree-sitter-swift.cjs +78 -0
  289. package/vendor/tree-sitter-proto/binding.gyp +30 -0
  290. package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
  291. package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
  292. package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
  293. package/vendor/tree-sitter-proto/package.json +18 -0
  294. package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
  295. package/vendor/tree-sitter-proto/src/parser.c +10149 -0
  296. package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
  297. package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
  298. package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
  299. package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
  300. package/dist/core/ingestion/named-binding-processor.js +0 -42
  301. package/dist/core/ingestion/resolution-context.d.ts +0 -58
  302. package/dist/core/ingestion/resolution-context.js +0 -135
  303. package/dist/core/ingestion/symbol-table.d.ts +0 -79
  304. package/dist/core/ingestion/symbol-table.js +0 -115
@@ -1,4 +1,4 @@
1
- import { FUNCTION_NODE_TYPES, extractFunctionName, CLASS_CONTAINER_TYPES, } from './utils/ast-helpers.js';
1
+ import { FUNCTION_NODE_TYPES, CLASS_CONTAINER_TYPES, genericFuncName, } from './utils/ast-helpers.js';
2
2
  import { CALL_EXPRESSION_TYPES } from './utils/call-analysis.js';
3
3
  import { TYPED_PARAMETER_TYPES } from './type-extractors/shared.js';
4
4
  import { getProvider } from './languages/index.js';
@@ -6,7 +6,21 @@ import { extractSimpleTypeName, extractVarName, stripNullable, extractReturnType
6
6
  /** File-level scope key */
7
7
  const FILE_SCOPE = '';
8
8
  /** Shared empty map for files with no file-scope bindings. */
9
- const EMPTY_FILE_SCOPE = new Map();
9
+ /**
10
+ * Create a fresh empty Map for the "no file-scope bindings" fallback.
11
+ *
12
+ * **Why not a shared sentinel**: we previously used a module-level
13
+ * `const EMPTY_FILE_SCOPE = new Map()` typed as `ReadonlyMap` and shared
14
+ * across every TypeEnv instance. That was a latent singleton-poisoning
15
+ * footgun: any caller that did `(fileScope() as Map).set(...)` — or any
16
+ * future refactor that widened the return type — would silently corrupt
17
+ * every subsequent "empty" return for the process lifetime. A Proxy
18
+ * wrapper was considered but broke Map's internal-slot methods (`.size`,
19
+ * iteration protocol). Allocating a fresh empty Map per call is a few
20
+ * bytes per file — immediately GC'd, no measurable cost even at 10k files
21
+ * — and eliminates the shared-mutation hazard entirely.
22
+ */
23
+ const emptyFileScope = () => new Map();
10
24
  /** Fallback for languages where class names aren't in a 'name' field (e.g. Kotlin uses type_identifier). */
11
25
  const findTypeIdentifierChild = (node) => {
12
26
  for (let i = 0; i < node.childCount; i++) {
@@ -53,7 +67,7 @@ const fastStripNullable = (typeName) => {
53
67
  : stripNullable(typeName);
54
68
  };
55
69
  /** Implementation of the lookup logic — shared between TypeEnvironment and the legacy export. */
56
- const lookupInEnv = (env, varName, callNode, patternOverrides, enclosingFunctionFinder) => {
70
+ const lookupInEnv = (env, varName, callNode, patternOverrides, enclosingFunctionFinder, extractFunctionNameHook) => {
57
71
  // Self/this receiver: resolve to enclosing class name via AST walk
58
72
  if (varName === 'self' || varName === 'this' || varName === '$this') {
59
73
  return findEnclosingClassName(callNode);
@@ -64,7 +78,7 @@ const lookupInEnv = (env, varName, callNode, patternOverrides, enclosingFunction
64
78
  return findEnclosingParentClassName(callNode);
65
79
  }
66
80
  // Determine the enclosing function scope for the call
67
- const scopeKey = findEnclosingScopeKey(callNode, enclosingFunctionFinder);
81
+ const scopeKey = findEnclosingScopeKey(callNode, enclosingFunctionFinder, extractFunctionNameHook);
68
82
  // Check position-indexed pattern overrides first (e.g., Kotlin when/is smart casts).
69
83
  // These take priority over flat scopeEnv because they represent per-branch narrowing.
70
84
  if (scopeKey && patternOverrides) {
@@ -272,11 +286,11 @@ const extractParentClassFromNode = (classNode) => {
272
286
  * it is consulted for each ancestor before the default FUNCTION_NODE_TYPES check.
273
287
  * This handles languages like Dart where the function body is a sibling of the
274
288
  * signature instead of a child. */
275
- const findEnclosingScopeKey = (node, enclosingFunctionFinder) => {
289
+ const findEnclosingScopeKey = (node, enclosingFunctionFinder, extractFunctionNameHook) => {
276
290
  let current = node.parent;
277
291
  while (current) {
278
292
  if (FUNCTION_NODE_TYPES.has(current.type)) {
279
- const { funcName } = extractFunctionName(current);
293
+ const funcName = extractFunctionNameHook?.(current)?.funcName ?? genericFuncName(current);
280
294
  if (funcName)
281
295
  return `${funcName}@${current.startIndex}`;
282
296
  }
@@ -300,10 +314,10 @@ const findEnclosingScopeKey = (node, enclosingFunctionFinder) => {
300
314
  * using cross-file type information when available.
301
315
  *
302
316
  * Only `.has()` is exposed — the SymbolTable doesn't support iteration.
303
- * Results are memoized to avoid redundant lookupFuzzy scans across declarations.
317
+ * Results are memoized to avoid redundant class-index scans across declarations.
304
318
  */
305
- const createClassNameLookup = (localNames, symbolTable) => {
306
- if (!symbolTable)
319
+ const createClassNameLookup = (localNames, model) => {
320
+ if (!model)
307
321
  return localNames;
308
322
  const memo = new Map();
309
323
  return {
@@ -313,8 +327,8 @@ const createClassNameLookup = (localNames, symbolTable) => {
313
327
  const cached = memo.get(name);
314
328
  if (cached !== undefined)
315
329
  return cached;
316
- const result = symbolTable
317
- .lookupFuzzy(name)
330
+ const result = model.types
331
+ .lookupClassByName(name)
318
332
  .some((def) => def.type === 'Class' || def.type === 'Enum' || def.type === 'Struct');
319
333
  memo.set(name, result);
320
334
  return result;
@@ -361,17 +375,16 @@ const SKIP_SUBTREE_TYPES = new Set([
361
375
  'regex_pattern',
362
376
  ]);
363
377
  const CLASS_LIKE_TYPES = new Set(['Class', 'Struct', 'Interface']);
378
+ const lookupClassDefsByName = (model, name, allowedTypes = CLASS_LIKE_TYPES) => model.types.lookupClassByName(name).filter((d) => allowedTypes.has(d.type));
364
379
  /** Memoize class definition lookups during fixpoint iteration.
365
380
  * SymbolTable is immutable during type resolution, so results never change.
366
381
  * Eliminates redundant array allocations + filter scans across iterations. */
367
- const createClassDefCache = (symbolTable) => {
382
+ const createClassDefCache = (model) => {
368
383
  const cache = new Map();
369
384
  return (typeName) => {
370
385
  let result = cache.get(typeName);
371
386
  if (result === undefined) {
372
- result = symbolTable
373
- ? symbolTable.lookupFuzzy(typeName).filter((d) => CLASS_LIKE_TYPES.has(d.type))
374
- : [];
387
+ result = model ? lookupClassDefsByName(model, typeName) : [];
375
388
  cache.set(typeName, result);
376
389
  }
377
390
  return result;
@@ -484,60 +497,75 @@ const walkParentChain = (typeName, parentMap, getClassDefs, lookupOnClass) => {
484
497
  * Uses SymbolTable to find the class nodeId for the receiver's type, then
485
498
  * looks up the field via the eagerly-populated fieldByOwner index.
486
499
  * Falls back to MRO parent chain walking if direct lookup fails (Phase 11A). */
487
- const resolveFieldType = (receiver, field, scopeEnv, symbolTable, getClassDefs, parentMap) => {
488
- if (!symbolTable)
500
+ const resolveFieldType = (receiver, field, scopeEnv, model, getClassDefs, parentMap) => {
501
+ if (!model)
489
502
  return undefined;
490
503
  const receiverType = scopeEnv.get(receiver);
491
504
  if (!receiverType)
492
505
  return undefined;
493
- const lookup = getClassDefs ??
494
- ((name) => symbolTable.lookupFuzzy(name).filter((d) => CLASS_LIKE_TYPES.has(d.type)));
506
+ const lookup = getClassDefs ?? ((name) => lookupClassDefsByName(model, name));
495
507
  const classDefs = lookup(receiverType);
496
508
  if (classDefs.length !== 1)
497
509
  return undefined;
498
510
  // Direct lookup first
499
- const fieldDef = symbolTable.lookupFieldByOwner(classDefs[0].nodeId, field);
511
+ const fieldDef = model.fields.lookupFieldByOwner(classDefs[0].nodeId, field);
500
512
  if (fieldDef?.declaredType)
501
513
  return extractReturnTypeName(fieldDef.declaredType);
502
514
  // MRO parent chain walking on miss
503
515
  const inherited = walkParentChain(receiverType, parentMap, lookup, (nodeId) => {
504
- const f = symbolTable.lookupFieldByOwner(nodeId, field);
516
+ const f = model.fields.lookupFieldByOwner(nodeId, field);
505
517
  return f?.declaredType ? extractReturnTypeName(f.declaredType) : undefined;
506
518
  });
507
519
  return inherited;
508
520
  };
509
521
  /** Resolve a method's return type given a receiver variable and method name.
510
522
  * Uses SymbolTable to find class nodeIds for the receiver's type, then
511
- * looks up the method via lookupFuzzyCallable filtered by ownerId.
523
+ * looks up the method via owner-scoped lookupMethodByOwner.
512
524
  * Falls back to MRO parent chain walking if direct lookup fails (Phase 11A). */
513
- const resolveMethodReturnType = (receiver, method, scopeEnv, symbolTable, getClassDefs, parentMap) => {
514
- if (!symbolTable)
525
+ const resolveMethodReturnType = (receiver, method, scopeEnv, model, getClassDefs, parentMap) => {
526
+ if (!model)
515
527
  return undefined;
516
- const receiverType = scopeEnv.get(receiver);
528
+ let receiverType = scopeEnv.get(receiver);
529
+ // When substituteThisReceiver replaced $this/self with the enclosing class name,
530
+ // the receiver IS the type — look it up directly as a class name.
531
+ if (!receiverType) {
532
+ const lookup = getClassDefs ?? ((name) => lookupClassDefsByName(model, name));
533
+ if (lookup(receiver).length > 0)
534
+ receiverType = receiver;
535
+ }
517
536
  if (!receiverType)
518
537
  return undefined;
519
- const lookup = getClassDefs ??
520
- ((name) => symbolTable.lookupFuzzy(name).filter((d) => CLASS_LIKE_TYPES.has(d.type)));
538
+ const lookup = getClassDefs ?? ((name) => lookupClassDefsByName(model, name));
521
539
  const classDefs = lookup(receiverType);
522
540
  if (classDefs.length === 0)
523
541
  return undefined;
524
542
  // Direct lookup first
525
- const classNodeIds = new Set(classDefs.map((d) => d.nodeId));
526
- const methods = symbolTable
527
- .lookupFuzzyCallable(method)
528
- .filter((d) => d.ownerId && classNodeIds.has(d.ownerId));
543
+ const directMethodLookups = classDefs.map((d) => ({
544
+ classDef: d,
545
+ methodDef: model.methods.lookupMethodByOwner(d.nodeId, method),
546
+ }));
547
+ const hasAmbiguousDirectLookup = directMethodLookups.some(({ classDef, methodDef }) => {
548
+ if (methodDef)
549
+ return false;
550
+ return model.symbols
551
+ .lookupExactAll(classDef.filePath, method)
552
+ .some((d) => d.ownerId === classDef.nodeId);
553
+ });
554
+ if (hasAmbiguousDirectLookup)
555
+ return undefined;
556
+ const methods = directMethodLookups
557
+ .map(({ methodDef }) => methodDef)
558
+ .filter((d) => d !== undefined);
529
559
  if (methods.length === 1 && methods[0].returnType) {
530
560
  return extractReturnTypeName(methods[0].returnType);
531
561
  }
532
562
  // MRO parent chain walking on miss
533
563
  if (methods.length === 0) {
534
564
  const inherited = walkParentChain(receiverType, parentMap, lookup, (nodeId) => {
535
- const parentMethods = symbolTable
536
- .lookupFuzzyCallable(method)
537
- .filter((d) => d.ownerId === nodeId);
538
- if (parentMethods.length !== 1 || !parentMethods[0].returnType)
565
+ const parentMethod = model.methods.lookupMethodByOwner(nodeId, method);
566
+ if (!parentMethod?.returnType)
539
567
  return undefined;
540
- return extractReturnTypeName(parentMethods[0].returnType);
568
+ return extractReturnTypeName(parentMethod.returnType);
541
569
  });
542
570
  return inherited;
543
571
  }
@@ -555,10 +583,10 @@ const resolveMethodReturnType = (receiver, method, scopeEnv, symbolTable, getCla
555
583
  * Termination: finite entries, each bound at most once (first-writer-wins), max 10 iterations.
556
584
  */
557
585
  const MAX_FIXPOINT_ITERATIONS = 10;
558
- const resolveFixpointBindings = (pendingItems, env, returnTypeLookup, symbolTable, parentMap) => {
586
+ const resolveFixpointBindings = (pendingItems, env, returnTypeLookup, model, parentMap) => {
559
587
  if (pendingItems.length === 0)
560
588
  return;
561
- const getClassDefs = createClassDefCache(symbolTable);
589
+ const getClassDefs = createClassDefCache(model);
562
590
  const resolved = new Set();
563
591
  for (let iter = 0; iter < MAX_FIXPOINT_ITERATIONS; iter++) {
564
592
  let changed = false;
@@ -583,10 +611,10 @@ const resolveFixpointBindings = (pendingItems, env, returnTypeLookup, symbolTabl
583
611
  typeName = scopeEnv.get(item.rhs) ?? env.get(FILE_SCOPE)?.get(item.rhs);
584
612
  break;
585
613
  case 'fieldAccess':
586
- typeName = resolveFieldType(item.receiver, item.field, scopeEnv, symbolTable, getClassDefs, parentMap);
614
+ typeName = resolveFieldType(item.receiver, item.field, scopeEnv, model, getClassDefs, parentMap);
587
615
  break;
588
616
  case 'methodCallResult':
589
- typeName = resolveMethodReturnType(item.receiver, item.method, scopeEnv, symbolTable, getClassDefs, parentMap);
617
+ typeName = resolveMethodReturnType(item.receiver, item.method, scopeEnv, model, getClassDefs, parentMap);
590
618
  break;
591
619
  default: {
592
620
  // Exhaustive check: TypeScript will error here if a new PendingAssignment
@@ -630,50 +658,68 @@ export const buildTypeEnv = (tree, language, options) => {
630
658
  // Clear per-file memoization caches from the previous file.
631
659
  enclosingClassNameCache.clear();
632
660
  enclosingParentClassNameCache.clear();
633
- const symbolTable = options?.symbolTable;
661
+ const model = options?.model;
634
662
  const parentMap = options?.parentMap;
663
+ const extractFuncNameHook = options?.extractFunctionName;
635
664
  const env = new Map();
665
+ let flushed = false;
636
666
  const patternOverrides = new Map();
637
667
  // Phase P: maps `scope\0varName` → constructor type when a declaration has BOTH
638
668
  // a base type annotation AND a more specific constructor initializer.
639
669
  // e.g., `Animal a = new Dog()` → constructorTypeMap.set('func@42\0a', 'Dog')
640
670
  const constructorTypeMap = new Map();
641
671
  const localClassNames = new Set();
642
- const classNames = createClassNameLookup(localClassNames, symbolTable);
672
+ const classNames = createClassNameLookup(localClassNames, model);
643
673
  const provider = getProvider(language);
644
674
  const config = provider.typeConfig;
645
675
  const bindings = [];
646
676
  // Build ReturnTypeLookup: SymbolTable is authoritative when it has an unambiguous match.
647
677
  // Cross-file importedReturnTypes are consulted ONLY when SymbolTable has 0 matches.
648
678
  // Ambiguous (2+) → undefined, no cross-file fallback (conservative, local-first principle).
679
+ // Post-A4 Unit 4: callableByName no longer holds Method/Constructor, so
680
+ // for-loop binding inference must also consult methodsByName to find
681
+ // return types on class methods (e.g. `user.getItems()` iteration).
682
+ // Take `model` as an explicit argument so the non-null precondition
683
+ // is visible at the type level. Callers must enter these via an
684
+ // `if (model)` guard on their side and pass the narrowed reference.
685
+ const getCallableUnionCount = (m, callee) => {
686
+ return (m.symbols.lookupCallableByName(callee).length + m.methods.lookupMethodByName(callee).length);
687
+ };
688
+ const getFirstCallable = (m, callee) => {
689
+ const free = m.symbols.lookupCallableByName(callee);
690
+ if (free.length > 0)
691
+ return free[0];
692
+ const methods = m.methods.lookupMethodByName(callee);
693
+ return methods.length > 0 ? methods[0] : undefined;
694
+ };
649
695
  const returnTypeLookup = {
650
696
  lookupReturnType(callee) {
651
697
  // SymbolTable is authoritative when it has an unambiguous match
652
- if (symbolTable) {
698
+ if (model) {
653
699
  if (provider.isBuiltInName(callee))
654
700
  return undefined;
655
- const callables = symbolTable.lookupFuzzyCallable(callee);
656
- if (callables.length === 1) {
657
- const rawReturn = callables[0].returnType;
701
+ const count = getCallableUnionCount(model, callee);
702
+ if (count === 1) {
703
+ const rawReturn = getFirstCallable(model, callee)?.returnType;
658
704
  if (rawReturn)
659
705
  return extractReturnTypeName(rawReturn);
660
706
  }
661
707
  // Ambiguous (2+) → return undefined (conservative, no cross-file fallback)
662
- if (callables.length > 1)
708
+ if (count > 1)
663
709
  return undefined;
664
710
  }
665
711
  // No match (0 results or no symbolTable) → fall back to cross-file
666
712
  return options?.importedReturnTypes?.get(callee);
667
713
  },
668
714
  lookupRawReturnType(callee) {
669
- if (symbolTable) {
715
+ if (model) {
670
716
  if (provider.isBuiltInName(callee))
671
717
  return undefined;
672
- const callables = symbolTable.lookupFuzzyCallable(callee);
673
- if (callables.length === 1)
674
- return callables[0].returnType;
718
+ const count = getCallableUnionCount(model, callee);
719
+ if (count === 1)
720
+ return getFirstCallable(model, callee)?.returnType;
675
721
  // Ambiguous (2+) → return undefined (conservative, no cross-file fallback)
676
- if (callables.length > 1)
722
+ if (count > 1)
677
723
  return undefined;
678
724
  }
679
725
  // Cross-file fallback uses importedRawReturnTypes (raw declared types, e.g., 'User[]')
@@ -789,47 +835,18 @@ export const buildTypeEnv = (tree, language, options) => {
789
835
  // This decouples type node capture from scopeEnv success — container types
790
836
  // (User[], []User, List[User]) that fail extractSimpleTypeName still get
791
837
  // their AST type node recorded for Strategy 1 for-loop resolution.
792
- // Try direct extraction first (works for Go var_spec, Python assignment, Rust let_declaration).
793
- // Try direct type field first, then unwrap wrapper nodes (C# field_declaration,
794
- // local_declaration_statement wrap their type inside a variable_declaration child).
795
- let typeNode = node.childForFieldName('type');
838
+ //
839
+ // Prefer language-specific locator when provided (keeps buildTypeEnv generic),
840
+ // then fall back to a small set of safe, cross-grammar heuristics.
841
+ let typeNode = config.getDeclarationTypeNode?.(node) ?? node.childForFieldName('type') ?? null;
842
+ // Fallback: some grammars wrap type annotations in a `type_annotation` child
843
+ // instead of exposing a named `type` field on the declaration node.
796
844
  if (!typeNode) {
797
- // C# field_declaration / local_declaration_statement wrap type inside variable_declaration.
798
- // Use manual loop instead of namedChildren.find() to avoid array allocation on hot path.
799
- let wrapped = node.childForFieldName('declaration');
800
- if (!wrapped) {
801
- for (let i = 0; i < node.namedChildCount; i++) {
802
- const c = node.namedChild(i);
803
- if (c?.type === 'variable_declaration') {
804
- wrapped = c;
805
- break;
806
- }
807
- }
808
- }
809
- if (wrapped) {
810
- typeNode = wrapped.childForFieldName('type');
811
- // Kotlin: variable_declaration stores the type as user_type / nullable_type
812
- // child rather than a named 'type' field.
813
- if (!typeNode) {
814
- for (let i = 0; i < wrapped.namedChildCount; i++) {
815
- const c = wrapped.namedChild(i);
816
- if (c && (c.type === 'user_type' || c.type === 'nullable_type')) {
817
- typeNode = c;
818
- break;
819
- }
820
- }
821
- }
822
- }
823
- // Swift: property_declaration has type_annotation as a direct child (not a 'type' field).
824
- // Extract the inner type node (array_type, user_type, etc.) for declarationTypeNodes.
825
- if (!typeNode) {
826
- for (let i = 0; i < node.namedChildCount; i++) {
827
- const c = node.namedChild(i);
828
- if (c?.type === 'type_annotation') {
829
- // Use the inner type (array_type, user_type) rather than the annotation wrapper
830
- typeNode = c.firstNamedChild ?? c;
831
- break;
832
- }
845
+ for (let i = 0; i < node.namedChildCount; i++) {
846
+ const c = node.namedChild(i);
847
+ if (c?.type === 'type_annotation') {
848
+ typeNode = c.firstNamedChild ?? c;
849
+ break;
833
850
  }
834
851
  }
835
852
  }
@@ -900,7 +917,10 @@ export const buildTypeEnv = (tree, language, options) => {
900
917
  }
901
918
  }
902
919
  };
903
- const walk = (node, currentScope) => {
920
+ const stack = [
921
+ { node: tree.rootNode, scope: FILE_SCOPE },
922
+ ];
923
+ const processNode = (node, currentScope) => {
904
924
  // Fast skip: subtrees that can never contain type-relevant nodes (leaf-like literals).
905
925
  if (SKIP_SUBTREE_TYPES.has(node.type))
906
926
  return;
@@ -916,7 +936,7 @@ export const buildTypeEnv = (tree, language, options) => {
916
936
  // Detect scope boundaries (function/method definitions)
917
937
  let scope = currentScope;
918
938
  if (FUNCTION_NODE_TYPES.has(node.type)) {
919
- const { funcName } = extractFunctionName(node);
939
+ const funcName = extractFuncNameHook?.(node)?.funcName ?? genericFuncName(node);
920
940
  if (funcName)
921
941
  scope = `${funcName}@${node.startIndex}`;
922
942
  }
@@ -1014,20 +1034,25 @@ export const buildTypeEnv = (tree, language, options) => {
1014
1034
  }
1015
1035
  }
1016
1036
  }
1017
- // Recurse into children
1018
- for (let i = 0; i < node.childCount; i++) {
1037
+ // Push children onto stack (reverse order so first child is processed first)
1038
+ for (let i = node.childCount - 1; i >= 0; i--) {
1019
1039
  const child = node.child(i);
1020
1040
  if (child)
1021
- walk(child, scope);
1041
+ stack.push({ node: child, scope });
1022
1042
  }
1023
1043
  };
1024
- walk(tree.rootNode, FILE_SCOPE);
1044
+ // Iterative traversal using explicit stack instead of recursion
1045
+ // to avoid "Maximum call stack size exceeded" on large files (2000+ lines)
1046
+ while (stack.length > 0) {
1047
+ const { node, scope } = stack.pop();
1048
+ processNode(node, scope);
1049
+ }
1025
1050
  // Phase 14: Seed cross-file bindings from upstream files AFTER walk
1026
1051
  // (local declarations from walk() take precedence — first-writer-wins)
1027
1052
  if (options?.importedBindings && options.importedBindings.size > 0) {
1028
1053
  seedImportedBindings(env, options.importedBindings);
1029
1054
  }
1030
- resolveFixpointBindings(pendingItems, env, returnTypeLookup, symbolTable, parentMap);
1055
+ resolveFixpointBindings(pendingItems, env, returnTypeLookup, model, parentMap);
1031
1056
  // Post-fixpoint for-loop replay (Phase 10 / ex-9B loop-fixpoint bridge):
1032
1057
  // For-loop nodes whose iterables were unresolved at walk-time may now be
1033
1058
  // resolvable because the fixpoint bound the iterable's type.
@@ -1054,14 +1079,50 @@ export const buildTypeEnv = (tree, language, options) => {
1054
1079
  return scopeEnv && !scopeEnv.has(item.lhs);
1055
1080
  });
1056
1081
  if (unresolvedBefore.length > 0) {
1057
- resolveFixpointBindings(unresolvedBefore, env, returnTypeLookup, symbolTable);
1082
+ resolveFixpointBindings(unresolvedBefore, env, returnTypeLookup, model);
1058
1083
  }
1059
1084
  }
1060
1085
  return {
1061
- lookup: (varName, callNode) => lookupInEnv(env, varName, callNode, patternOverrides, options?.enclosingFunctionFinder),
1086
+ lookup: (varName, callNode) => lookupInEnv(env, varName, callNode, patternOverrides, options?.enclosingFunctionFinder, extractFuncNameHook),
1062
1087
  constructorBindings: bindings,
1063
- fileScope: () => env.get(FILE_SCOPE) ?? EMPTY_FILE_SCOPE,
1088
+ fileScope: () => env.get(FILE_SCOPE) ?? emptyFileScope(),
1064
1089
  allScopes: () => env,
1065
1090
  constructorTypeMap,
1091
+ flush(filePath, accumulator) {
1092
+ if (flushed) {
1093
+ throw new Error(`[TypeEnvironment] flush called twice for ${filePath} — flush is single-use`);
1094
+ }
1095
+ // Narrow flush() to iterate only the FILE_SCOPE entry, mirroring the
1096
+ // worker-path narrowing in parse-worker.ts (commit 803631fe). Before
1097
+ // this change, both execution paths had the same asymmetry bug: the
1098
+ // worker path was fixed but the sequential path (this code) still
1099
+ // wrote function-scope entries into long-lived accumulator storage
1100
+ // that no consumer reads until Phase 9 lands.
1101
+ //
1102
+ // Phase 9 reversion: when a downstream consumer of function-scope
1103
+ // bindings exists, restore the nested iteration:
1104
+ //
1105
+ // for (const [scope, scopeMap] of env) {
1106
+ // for (const [varName, typeName] of scopeMap) {
1107
+ // entries.push({ scope, varName, typeName });
1108
+ // }
1109
+ // }
1110
+ //
1111
+ // See BindingAccumulator class JSDoc and FileScopeBindings JSDoc in
1112
+ // parse-worker.ts for the full reversion checklist.
1113
+ const fileScope = env.get(FILE_SCOPE) ?? emptyFileScope();
1114
+ const entries = [];
1115
+ for (const [varName, typeName] of fileScope) {
1116
+ entries.push({ scope: '', varName, typeName });
1117
+ }
1118
+ if (entries.length > 0) {
1119
+ accumulator.appendFile(filePath, entries);
1120
+ }
1121
+ // Mark the env as flushed AFTER the successful append. If appendFile
1122
+ // throws (e.g., accumulator is already finalized due to a lifecycle
1123
+ // ordering bug), the caller can catch and retry — the single-use
1124
+ // guard now tracks "data was written", not "flush was attempted".
1125
+ flushed = true;
1126
+ },
1066
1127
  };
1067
1128
  };
@@ -549,6 +549,23 @@ const inferLiteralType = (node) => {
549
549
  };
550
550
  export const typeConfig = {
551
551
  declarationNodeTypes: DECLARATION_NODE_TYPES,
552
+ getDeclarationTypeNode: (node) => {
553
+ // C# field_declaration / local_declaration_statement wrap type inside variable_declaration.
554
+ // Prefer the wrapper node's `type` field when present.
555
+ const direct = node.childForFieldName('type');
556
+ if (direct)
557
+ return direct;
558
+ const wrapped = node.childForFieldName('declaration') ??
559
+ (() => {
560
+ for (let i = 0; i < node.namedChildCount; i++) {
561
+ const c = node.namedChild(i);
562
+ if (c?.type === 'variable_declaration')
563
+ return c;
564
+ }
565
+ return null;
566
+ })();
567
+ return wrapped?.childForFieldName('type') ?? null;
568
+ },
552
569
  forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
553
570
  patternBindingNodeTypes: new Set([
554
571
  'is_pattern_expression',
@@ -831,6 +831,17 @@ const extractKotlinPatternBinding = (node, scopeEnv, declarationTypeNodes, scope
831
831
  export const kotlinTypeConfig = {
832
832
  allowPatternBindingOverwrite: true,
833
833
  declarationNodeTypes: KOTLIN_DECLARATION_NODE_TYPES,
834
+ getDeclarationTypeNode: (node) => {
835
+ // Kotlin property_declaration wraps the actual declaration in variable_declaration.
836
+ // The type is commonly a user_type / nullable_type child (positional, not 'type' field).
837
+ const varDecl = node.type === 'property_declaration' ? findChild(node, 'variable_declaration') : node;
838
+ if (varDecl) {
839
+ return (varDecl.childForFieldName('type') ??
840
+ findChild(varDecl, 'user_type') ??
841
+ findChild(varDecl, 'nullable_type'));
842
+ }
843
+ return node.childForFieldName('type') ?? findChild(node, 'user_type') ?? null;
844
+ },
834
845
  forLoopNodeTypes: KOTLIN_FOR_LOOP_NODE_TYPES,
835
846
  patternBindingNodeTypes: new Set(['type_test', 'equality_expression']),
836
847
  extractDeclaration: extractKotlinDeclaration,
@@ -323,60 +323,6 @@ const scanConstructorBinding = (node) => {
323
323
  }
324
324
  return undefined;
325
325
  };
326
- /** Regex to extract PHPDoc @return annotations: `@return User` */
327
- const PHPDOC_RETURN_RE = /@return\s+(\S+)/;
328
- /**
329
- * Normalize a PHPDoc return type for storage in the SymbolTable.
330
- * Unlike normalizePhpType (which strips User[] → User for scopeEnv), this preserves
331
- * array notation so lookupRawReturnType can extract element types for for-loop resolution.
332
- * \App\Models\User[] → User[]
333
- * ?User → User
334
- * Collection<User> → Collection<User> (preserved for extractElementTypeFromString)
335
- */
336
- const normalizePhpReturnType = (raw) => {
337
- // Strip nullable prefix: ?User[] → User[]
338
- let type = raw.startsWith('?') ? raw.slice(1) : raw;
339
- // Strip union with null/false/void: User[]|null → User[]
340
- const parts = type
341
- .split('|')
342
- .filter((p) => p !== 'null' && p !== 'false' && p !== 'void' && p !== 'mixed');
343
- if (parts.length !== 1)
344
- return undefined;
345
- type = parts[0];
346
- // Strip namespace: \App\Models\User[] → User[]
347
- const segments = type.split('\\');
348
- type = segments[segments.length - 1];
349
- // Skip uninformative types
350
- if (type === 'mixed' ||
351
- type === 'void' ||
352
- type === 'self' ||
353
- type === 'static' ||
354
- type === 'object' ||
355
- type === 'array')
356
- return undefined;
357
- if (/^\w+(\[\])?$/.test(type) || /^\w+\s*</.test(type))
358
- return type;
359
- return undefined;
360
- };
361
- /**
362
- * Extract return type from PHPDoc `@return Type` annotation preceding a method.
363
- * Walks backwards through preceding siblings looking for comment nodes.
364
- * Preserves array notation (e.g., User[]) for for-loop element type extraction.
365
- */
366
- const extractReturnType = (node) => {
367
- let sibling = node.previousSibling;
368
- while (sibling) {
369
- if (sibling.type === 'comment') {
370
- const match = PHPDOC_RETURN_RE.exec(sibling.text);
371
- if (match)
372
- return normalizePhpReturnType(match[1]);
373
- }
374
- else if (sibling.isNamed && !SKIP_NODE_TYPES.has(sibling.type))
375
- break;
376
- sibling = sibling.previousSibling;
377
- }
378
- return undefined;
379
- };
380
326
  /** PHP: $alias = $user → assignment_expression with variable_name left/right.
381
327
  * PHP TypeEnv stores variables WITH $ prefix ($user → User), so we keep $ in lhs/rhs. */
382
328
  const extractPendingAssignment = (node, scopeEnv) => {
@@ -583,7 +529,6 @@ export const typeConfig = {
583
529
  extractParameter,
584
530
  extractInitializer,
585
531
  scanConstructorBinding,
586
- extractReturnType,
587
532
  extractForLoopBinding,
588
533
  extractPendingAssignment,
589
534
  };
@@ -23,8 +23,6 @@ import { extractRubyConstructorAssignment, extractSimpleTypeName, extractElement
23
23
  const YARD_PARAM_RE = /@param\s+(\w+)\s+\[([^\]]+)\]/g;
24
24
  /** Alternate YARD order: `@param [Type] name` */
25
25
  const YARD_PARAM_ALT_RE = /@param\s+\[([^\]]+)\]\s+(\w+)/g;
26
- /** Regex to extract @return annotations: `@return [Type]` */
27
- const YARD_RETURN_RE = /@return\s+\[([^\]]+)\]/;
28
26
  /**
29
27
  * Extract the simple type name from a YARD type string.
30
28
  * Handles:
@@ -190,35 +188,6 @@ const extractInitializer = (node, env, classNames) => {
190
188
  env.set(result.varName, result.calleeName);
191
189
  }
192
190
  };
193
- /**
194
- * Extract return type from YARD `@return [Type]` annotation preceding a method.
195
- * Reuses the same comment-walking strategy as collectYardParams: try direct
196
- * siblings first, fall back to parent (body_statement) siblings for class methods.
197
- */
198
- const extractReturnType = (node) => {
199
- const search = (startNode) => {
200
- let sibling = startNode.previousSibling;
201
- while (sibling) {
202
- if (sibling.type === 'comment') {
203
- const match = YARD_RETURN_RE.exec(sibling.text);
204
- if (match)
205
- return extractYardTypeName(match[1]);
206
- }
207
- else if (sibling.isNamed) {
208
- break;
209
- }
210
- sibling = sibling.previousSibling;
211
- }
212
- return undefined;
213
- };
214
- const result = search(node);
215
- if (result)
216
- return result;
217
- if (node.parent?.type === 'body_statement') {
218
- return search(node.parent);
219
- }
220
- return undefined;
221
- };
222
191
  /**
223
192
  * Ruby constructor binding scanner: captures both `user = User.new` and
224
193
  * plain call assignments like `user = get_user()`.
@@ -403,7 +372,6 @@ export const typeConfig = {
403
372
  extractParameter,
404
373
  extractInitializer,
405
374
  scanConstructorBinding,
406
- extractReturnType,
407
375
  extractForLoopBinding,
408
376
  extractPendingAssignment,
409
377
  };
@@ -461,6 +461,19 @@ function extractSwiftElementTypeFromTypeNode(typeNode) {
461
461
  }
462
462
  export const typeConfig = {
463
463
  declarationNodeTypes: DECLARATION_NODE_TYPES,
464
+ getDeclarationTypeNode: (node) => {
465
+ // Swift: many declarations store type as a type_annotation child (not a 'type' field).
466
+ // Prefer a direct 'type' field if present, else unwrap type_annotation to its inner type.
467
+ const direct = node.childForFieldName('type');
468
+ if (direct)
469
+ return direct;
470
+ for (let i = 0; i < node.namedChildCount; i++) {
471
+ const c = node.namedChild(i);
472
+ if (c?.type === 'type_annotation')
473
+ return c.firstNamedChild ?? c;
474
+ }
475
+ return null;
476
+ },
464
477
  forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
465
478
  extractDeclaration,
466
479
  extractParameter,