gitnexus 1.6.11-rc.8 → 1.6.11

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 (531) hide show
  1. package/README.md +95 -26
  2. package/dist/_shared/graph/types.d.ts +81 -2
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
  5. package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
  6. package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
  7. package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
  8. package/dist/_shared/language-detection.d.ts.map +1 -1
  9. package/dist/_shared/language-detection.js +2 -0
  10. package/dist/_shared/language-detection.js.map +1 -1
  11. package/dist/_shared/languages.d.ts +2 -1
  12. package/dist/_shared/languages.d.ts.map +1 -1
  13. package/dist/_shared/languages.js +1 -0
  14. package/dist/_shared/languages.js.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.d.ts +2 -2
  16. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  17. package/dist/_shared/lbug/schema-constants.js +4 -0
  18. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  19. package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
  20. package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -0
  21. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  22. package/dist/_shared/scope-resolution/language-classification.d.ts +3 -0
  23. package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/language-classification.js +4 -0
  25. package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
  26. package/dist/_shared/scope-resolution/reference-site.d.ts +10 -0
  27. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  28. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
  29. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
  30. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  31. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  32. package/dist/_shared/scope-resolution/types.d.ts +15 -0
  33. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  34. package/dist/cli/ai-context.d.ts +4 -0
  35. package/dist/cli/ai-context.js +90 -14
  36. package/dist/cli/analyze-config.js +14 -0
  37. package/dist/cli/analyze-options.d.ts +14 -0
  38. package/dist/cli/analyze-watch.d.ts +20 -0
  39. package/dist/cli/analyze-watch.js +434 -0
  40. package/dist/cli/analyze.js +27 -9
  41. package/dist/cli/auto-sync.d.ts +1 -0
  42. package/dist/cli/auto-sync.js +107 -0
  43. package/dist/cli/command-banner.d.ts +15 -0
  44. package/dist/cli/command-banner.js +42 -0
  45. package/dist/cli/detect-changes-format.js +21 -0
  46. package/dist/cli/doctor.d.ts +7 -0
  47. package/dist/cli/doctor.js +14 -0
  48. package/dist/cli/group.js +7 -1
  49. package/dist/cli/help-i18n.js +4 -0
  50. package/dist/cli/i18n/en.d.ts +18 -3
  51. package/dist/cli/i18n/en.js +18 -3
  52. package/dist/cli/i18n/resources.d.ts +33 -3
  53. package/dist/cli/i18n/zh-CN.d.ts +15 -0
  54. package/dist/cli/i18n/zh-CN.js +18 -3
  55. package/dist/cli/index.js +34 -7
  56. package/dist/cli/mcp.d.ts +15 -0
  57. package/dist/cli/mcp.js +44 -0
  58. package/dist/cli/optional-grammars.d.ts +3 -2
  59. package/dist/cli/optional-grammars.js +19 -3
  60. package/dist/cli/setup.js +41 -15
  61. package/dist/cli/update-notice.d.ts +34 -0
  62. package/dist/cli/update-notice.js +91 -0
  63. package/dist/cli/update.d.ts +21 -0
  64. package/dist/cli/update.js +93 -0
  65. package/dist/cli/watch.d.ts +1 -20
  66. package/dist/cli/watch.js +5 -371
  67. package/dist/core/analysis-feature-registry.d.ts +2 -0
  68. package/dist/core/analysis-feature-registry.js +15 -0
  69. package/dist/core/analyzer-identity.js +21 -3
  70. package/dist/core/auto-sync/analysis-worker-launch.d.ts +30 -0
  71. package/dist/core/auto-sync/analysis-worker-launch.js +166 -0
  72. package/dist/core/auto-sync/config.d.ts +33 -0
  73. package/dist/core/auto-sync/config.js +320 -0
  74. package/dist/core/auto-sync/index.d.ts +6 -0
  75. package/dist/core/auto-sync/index.js +6 -0
  76. package/dist/core/auto-sync/path-security.d.ts +9 -0
  77. package/dist/core/auto-sync/path-security.js +251 -0
  78. package/dist/core/auto-sync/repo.d.ts +1 -0
  79. package/dist/core/auto-sync/repo.js +6 -0
  80. package/dist/core/auto-sync/runner.d.ts +44 -0
  81. package/dist/core/auto-sync/runner.js +428 -0
  82. package/dist/core/auto-sync/starter.d.ts +50 -0
  83. package/dist/core/auto-sync/starter.js +497 -0
  84. package/dist/core/auto-sync/state.d.ts +37 -0
  85. package/dist/core/auto-sync/state.js +119 -0
  86. package/dist/core/group/config-parser.js +8 -1
  87. package/dist/core/group/cross-impact.d.ts +3 -0
  88. package/dist/core/group/cross-impact.js +36 -4
  89. package/dist/core/group/extractors/http-patterns/java.js +22 -8
  90. package/dist/core/group/extractors/http-patterns/kotlin.js +86 -31
  91. package/dist/core/group/extractors/http-patterns/node.js +145 -8
  92. package/dist/core/group/extractors/http-patterns/python.js +11 -7
  93. package/dist/core/group/extractors/http-route-extractor.js +63 -17
  94. package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
  95. package/dist/core/group/group-lock.js +5 -6
  96. package/dist/core/group/normalization.d.ts +38 -1
  97. package/dist/core/group/normalization.js +59 -3
  98. package/dist/core/group/service.d.ts +11 -0
  99. package/dist/core/group/service.js +13 -1
  100. package/dist/core/group/storage.js +2 -2
  101. package/dist/core/group/sync.d.ts +24 -1
  102. package/dist/core/group/sync.js +46 -8
  103. package/dist/core/group/types.d.ts +13 -0
  104. package/dist/core/incremental/derived-writeback.d.ts +36 -0
  105. package/dist/core/incremental/derived-writeback.js +68 -0
  106. package/dist/core/incremental/spring-config-drift.d.ts +12 -0
  107. package/dist/core/incremental/spring-config-drift.js +46 -0
  108. package/dist/core/incremental/subgraph-extract.d.ts +7 -4
  109. package/dist/core/incremental/subgraph-extract.js +30 -5
  110. package/dist/core/ingestion/asyncapi/document.d.ts +226 -0
  111. package/dist/core/ingestion/asyncapi/document.js +758 -0
  112. package/dist/core/ingestion/asyncapi/protocol.d.ts +79 -0
  113. package/dist/core/ingestion/asyncapi/protocol.js +203 -0
  114. package/dist/core/ingestion/call-extractors/configs/zig.d.ts +2 -0
  115. package/dist/core/ingestion/call-extractors/configs/zig.js +4 -0
  116. package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +100 -0
  117. package/dist/core/ingestion/call-extractors/zig-static-gating.js +319 -0
  118. package/dist/core/ingestion/call-processor.js +3 -1
  119. package/dist/core/ingestion/class-extractors/configs/zig.d.ts +2 -0
  120. package/dist/core/ingestion/class-extractors/configs/zig.js +32 -0
  121. package/dist/core/ingestion/class-extractors/generic.js +4 -1
  122. package/dist/core/ingestion/class-types.d.ts +6 -2
  123. package/dist/core/ingestion/destination-key.d.ts +59 -0
  124. package/dist/core/ingestion/destination-key.js +61 -0
  125. package/dist/core/ingestion/entry-point-scoring.d.ts +6 -2
  126. package/dist/core/ingestion/entry-point-scoring.js +8 -44
  127. package/dist/core/ingestion/export-detection.d.ts +37 -0
  128. package/dist/core/ingestion/export-detection.js +60 -0
  129. package/dist/core/ingestion/field-extractors/configs/zig.d.ts +8 -0
  130. package/dist/core/ingestion/field-extractors/configs/zig.js +49 -0
  131. package/dist/core/ingestion/field-extractors/generic.d.ts +1 -1
  132. package/dist/core/ingestion/field-extractors/generic.js +12 -1
  133. package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
  134. package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
  135. package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +5 -0
  136. package/dist/core/ingestion/frameworks/spring/analysis-features.js +9 -0
  137. package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +8 -1
  138. package/dist/core/ingestion/frameworks/spring/argument-facts.d.ts +80 -0
  139. package/dist/core/ingestion/frameworks/spring/argument-facts.js +85 -0
  140. package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +1 -0
  141. package/dist/core/ingestion/frameworks/spring/config-bindings.js +2 -1
  142. package/dist/core/ingestion/frameworks/spring/destinations.d.ts +539 -0
  143. package/dist/core/ingestion/frameworks/spring/destinations.js +840 -0
  144. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
  145. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
  146. package/dist/core/ingestion/frameworks/spring/message-producers.d.ts +78 -0
  147. package/dist/core/ingestion/frameworks/spring/message-producers.js +90 -0
  148. package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +29 -0
  149. package/dist/core/ingestion/frameworks/spring/vendor-prefixes.d.ts +7 -0
  150. package/dist/core/ingestion/frameworks/spring/vendor-prefixes.js +22 -0
  151. package/dist/core/ingestion/import-resolvers/configs/zig.d.ts +13 -0
  152. package/dist/core/ingestion/import-resolvers/configs/zig.js +26 -0
  153. package/dist/core/ingestion/import-resolvers/zig.d.ts +34 -0
  154. package/dist/core/ingestion/import-resolvers/zig.js +193 -0
  155. package/dist/core/ingestion/language-config.d.ts +177 -0
  156. package/dist/core/ingestion/language-config.js +719 -0
  157. package/dist/core/ingestion/language-provider.d.ts +178 -1
  158. package/dist/core/ingestion/language-provider.js +22 -0
  159. package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
  160. package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
  161. package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
  162. package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
  163. package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
  164. package/dist/core/ingestion/languages/index.d.ts +1 -0
  165. package/dist/core/ingestion/languages/index.js +2 -0
  166. package/dist/core/ingestion/languages/java/analysis-features.d.ts +1 -1
  167. package/dist/core/ingestion/languages/java/analysis-features.js +14 -6
  168. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +8 -0
  169. package/dist/core/ingestion/languages/java/capture-side-channel.js +34 -0
  170. package/dist/core/ingestion/languages/java/captures.js +22 -1
  171. package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
  172. package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
  173. package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
  174. package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
  175. package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
  176. package/dist/core/ingestion/languages/java/spring-di.d.ts +17 -1
  177. package/dist/core/ingestion/languages/java/spring-di.js +44 -2
  178. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
  179. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
  180. package/dist/core/ingestion/languages/java/spring-message-producers.d.ts +20 -0
  181. package/dist/core/ingestion/languages/java/spring-message-producers.js +83 -0
  182. package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +11 -1
  183. package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +13 -3
  184. package/dist/core/ingestion/languages/java.js +18 -8
  185. package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
  186. package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
  187. package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
  188. package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
  189. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +15 -0
  190. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +50 -0
  191. package/dist/core/ingestion/languages/kotlin/captures.js +24 -1
  192. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
  193. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
  194. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +4 -0
  195. package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
  196. package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
  197. package/dist/core/ingestion/languages/kotlin/spring-config-bindings.d.ts +18 -0
  198. package/dist/core/ingestion/languages/kotlin/spring-config-bindings.js +411 -0
  199. package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +51 -1
  200. package/dist/core/ingestion/languages/kotlin/spring-di.js +86 -6
  201. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
  202. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
  203. package/dist/core/ingestion/languages/kotlin/spring-message-producers.d.ts +17 -0
  204. package/dist/core/ingestion/languages/kotlin/spring-message-producers.js +119 -0
  205. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +16 -0
  206. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +77 -2
  207. package/dist/core/ingestion/languages/kotlin.js +24 -0
  208. package/dist/core/ingestion/languages/python.js +2 -0
  209. package/dist/core/ingestion/languages/rust/query.js +8 -8
  210. package/dist/core/ingestion/languages/zig/captures.d.ts +260 -0
  211. package/dist/core/ingestion/languages/zig/captures.js +2070 -0
  212. package/dist/core/ingestion/languages/zig/index.d.ts +4 -0
  213. package/dist/core/ingestion/languages/zig/index.js +4 -0
  214. package/dist/core/ingestion/languages/zig/interpret.d.ts +36 -0
  215. package/dist/core/ingestion/languages/zig/interpret.js +176 -0
  216. package/dist/core/ingestion/languages/zig/query.d.ts +3 -0
  217. package/dist/core/ingestion/languages/zig/query.js +426 -0
  218. package/dist/core/ingestion/languages/zig/range-binding.d.ts +37 -0
  219. package/dist/core/ingestion/languages/zig/range-binding.js +373 -0
  220. package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +12 -0
  221. package/dist/core/ingestion/languages/zig/scope-resolver.js +58 -0
  222. package/dist/core/ingestion/languages/zig/simple-hooks.d.ts +25 -0
  223. package/dist/core/ingestion/languages/zig/simple-hooks.js +143 -0
  224. package/dist/core/ingestion/languages/zig.d.ts +32 -0
  225. package/dist/core/ingestion/languages/zig.js +130 -0
  226. package/dist/core/ingestion/method-extractors/configs/zig.d.ts +2 -0
  227. package/dist/core/ingestion/method-extractors/configs/zig.js +120 -0
  228. package/dist/core/ingestion/method-extractors/generic.js +16 -4
  229. package/dist/core/ingestion/method-types.d.ts +10 -4
  230. package/dist/core/ingestion/model/field-registry.d.ts +3 -3
  231. package/dist/core/ingestion/model/field-registry.js +11 -7
  232. package/dist/core/ingestion/model/method-registry.d.ts +3 -3
  233. package/dist/core/ingestion/model/method-registry.js +13 -8
  234. package/dist/core/ingestion/model/registration-table.js +3 -0
  235. package/dist/core/ingestion/model/type-registry.js +8 -4
  236. package/dist/core/ingestion/pipeline-phases/di.js +47 -14
  237. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  238. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  239. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +7 -1
  240. package/dist/core/ingestion/pipeline-phases/parse-impl.js +79 -91
  241. package/dist/core/ingestion/pipeline-phases/parse.d.ts +17 -0
  242. package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
  243. package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
  244. package/dist/core/ingestion/pipeline-phases/routes.js +27 -18
  245. package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
  246. package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
  247. package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
  248. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
  249. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
  250. package/dist/core/ingestion/pipeline-phases/spring-destinations.d.ts +123 -0
  251. package/dist/core/ingestion/pipeline-phases/spring-destinations.js +688 -0
  252. package/dist/core/ingestion/pipeline.d.ts +30 -1
  253. package/dist/core/ingestion/pipeline.js +52 -14
  254. package/dist/core/ingestion/resolve-references.js +5 -0
  255. package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
  256. package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
  257. package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
  258. package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
  259. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +12 -18
  260. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +214 -33
  261. package/dist/core/ingestion/route-extractors/kotlin-spring.d.ts +13 -0
  262. package/dist/core/ingestion/route-extractors/kotlin-spring.js +259 -0
  263. package/dist/core/ingestion/route-extractors/python-decorator-handler.d.ts +6 -0
  264. package/dist/core/ingestion/route-extractors/python-decorator-handler.js +16 -0
  265. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +30 -1
  266. package/dist/core/ingestion/route-extractors/spring-shared.js +77 -9
  267. package/dist/core/ingestion/route-extractors/spring.js +9 -5
  268. package/dist/core/ingestion/scope-extractor.js +8 -0
  269. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +62 -0
  270. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +4 -0
  271. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +2 -0
  272. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +28 -0
  273. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -2
  274. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +23 -2
  275. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +24 -3
  276. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +11 -1
  277. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +26 -6
  278. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +11 -0
  279. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +29 -3
  280. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +8 -1
  281. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +35 -9
  282. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -4
  283. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +277 -9
  284. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  285. package/dist/core/ingestion/scope-resolution/pipeline/run.js +7 -3
  286. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +43 -9
  287. package/dist/core/ingestion/scope-resolution/scope/walkers.js +71 -10
  288. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
  289. package/dist/core/ingestion/tree-sitter-queries.js +161 -0
  290. package/dist/core/ingestion/type-extractors/zig.d.ts +2 -0
  291. package/dist/core/ingestion/type-extractors/zig.js +17 -0
  292. package/dist/core/ingestion/utils/ast-helpers.d.ts +40 -1
  293. package/dist/core/ingestion/utils/ast-helpers.js +124 -6
  294. package/dist/core/ingestion/utils/callable-flow-captures.d.ts +9 -0
  295. package/dist/core/ingestion/utils/callable-flow-captures.js +75 -14
  296. package/dist/core/ingestion/utils/test-file-path.d.ts +28 -0
  297. package/dist/core/ingestion/utils/test-file-path.js +87 -0
  298. package/dist/core/ingestion/variable-extractors/configs/zig.d.ts +2 -0
  299. package/dist/core/ingestion/variable-extractors/configs/zig.js +71 -0
  300. package/dist/core/ingestion/workers/parse-worker.js +85 -14
  301. package/dist/core/install-context.d.ts +4 -0
  302. package/dist/core/install-context.js +152 -0
  303. package/dist/core/lbug/csv-generator.d.ts +10 -1
  304. package/dist/core/lbug/csv-generator.js +92 -22
  305. package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
  306. package/dist/core/lbug/graph-emit-sink.js +4 -2
  307. package/dist/core/lbug/lbug-adapter.d.ts +100 -5
  308. package/dist/core/lbug/lbug-adapter.js +236 -24
  309. package/dist/core/lbug/schema.d.ts +6 -5
  310. package/dist/core/lbug/schema.js +84 -22
  311. package/dist/core/net/url-guard.d.ts +6 -0
  312. package/dist/core/net/url-guard.js +147 -0
  313. package/dist/core/package-version.d.ts +5 -0
  314. package/dist/core/package-version.js +10 -0
  315. package/dist/core/run-analyze.d.ts +21 -4
  316. package/dist/core/run-analyze.js +426 -117
  317. package/dist/core/search/fts-indexes.d.ts +19 -1
  318. package/dist/core/search/fts-indexes.js +28 -1
  319. package/dist/core/tree-sitter/parser-loader.js +16 -0
  320. package/dist/core/update-cache.d.ts +39 -0
  321. package/dist/core/update-cache.js +141 -0
  322. package/dist/core/update-check.d.ts +31 -0
  323. package/dist/core/update-check.js +274 -0
  324. package/dist/mcp/local/local-backend.d.ts +27 -13
  325. package/dist/mcp/local/local-backend.js +148 -60
  326. package/dist/mcp/repository-policy.d.ts +5 -1
  327. package/dist/mcp/repository-policy.js +48 -4
  328. package/dist/mcp/resources.js +10 -1
  329. package/dist/mcp/server.js +8 -9
  330. package/dist/mcp/tools.js +32 -22
  331. package/dist/server/analyze-launch.d.ts +2 -0
  332. package/dist/server/analyze-launch.js +2 -0
  333. package/dist/server/analyze-worker-protocol.d.ts +6 -1
  334. package/dist/server/analyze-worker.d.ts +1 -0
  335. package/dist/server/analyze-worker.js +38 -20
  336. package/dist/server/api.d.ts +5 -1
  337. package/dist/server/api.js +105 -88
  338. package/dist/server/git-clone.d.ts +40 -16
  339. package/dist/server/git-clone.js +253 -199
  340. package/dist/server/grep-params.d.ts +18 -0
  341. package/dist/server/grep-params.js +83 -0
  342. package/dist/server/grep-scan.d.ts +23 -0
  343. package/dist/server/grep-scan.js +107 -0
  344. package/dist/server/grep-worker.d.ts +1 -0
  345. package/dist/server/grep-worker.js +12 -0
  346. package/dist/server/mcp-http.d.ts +8 -0
  347. package/dist/server/mcp-http.js +16 -1
  348. package/dist/server/update-controller.d.ts +29 -0
  349. package/dist/server/update-controller.js +82 -0
  350. package/dist/storage/file-lock.d.ts +15 -0
  351. package/dist/storage/file-lock.js +155 -0
  352. package/dist/storage/fs-atomic.d.ts +24 -0
  353. package/dist/storage/fs-atomic.js +86 -2
  354. package/dist/storage/global-dir.d.ts +2 -0
  355. package/dist/storage/global-dir.js +6 -0
  356. package/dist/storage/parse-cache.d.ts +45 -7
  357. package/dist/storage/parse-cache.js +189 -42
  358. package/dist/storage/parsedfile-store.d.ts +41 -60
  359. package/dist/storage/parsedfile-store.js +247 -171
  360. package/dist/storage/repo-manager.d.ts +10 -4
  361. package/dist/storage/repo-manager.js +35 -25
  362. package/dist/storage/repo-meta.d.ts +34 -0
  363. package/dist/storage/v8-sidecar.d.ts +48 -0
  364. package/dist/storage/v8-sidecar.js +347 -0
  365. package/dist/types/pipeline.d.ts +14 -0
  366. package/dist/utils/process-identity.d.ts +2 -0
  367. package/dist/utils/process-identity.js +35 -0
  368. package/package.json +10 -4
  369. package/scripts/assert-web-assets.mjs +35 -0
  370. package/scripts/build-web.js +72 -0
  371. package/scripts/build.js +31 -28
  372. package/scripts/cross-platform-tests.ts +5 -0
  373. package/skills/gitnexus-cli.md +5 -1
  374. package/web/assets/ProcessFlowModal-DAeX2BHW.js +24 -0
  375. package/web/assets/{abnfDiagram-QNN2D4NM-Cq2djWWe.js → abnfDiagram-PAR5IXJI-J3OdeHLl.js} +1 -1
  376. package/web/assets/{agent-BWAXPjC5.js → agent-BMRqXorA.js} +6 -4
  377. package/web/assets/architecture-7GRP2DOG-M7G3SWCO-G3sWyX6M.js +1 -0
  378. package/web/assets/architectureDiagram-2PNWDENW-ysKcRlGT.js +36 -0
  379. package/web/assets/blockDiagram-PUWGUBOA-oLXTa1lY.js +129 -0
  380. package/web/assets/c4Diagram-3NLNHZP5-D3iu4eC0.js +38 -0
  381. package/web/assets/{chunk-2SREHG4O-Dip42xOq.js → chunk-2NMSUPJH-B24w-wng.js} +1 -1
  382. package/web/assets/{chunk-VJFVEZTB-CX66F2ep.js → chunk-32MCUW2P-ByUyt0Sa.js} +3 -3
  383. package/web/assets/{chunk-4RFN2BYJ-DHHWuC-n.js → chunk-3NPNU5GP-DocZ07Sg.js} +1 -1
  384. package/web/assets/{chunk-XNMVGMAZ-Cg_LnPtj.js → chunk-3SMGFRGC-CAu4JRLv.js} +1 -1
  385. package/web/assets/{chunk-W44A43WB-BN8EQc_u.js → chunk-4CAJECRU-DRYMwG9z.js} +1 -1
  386. package/web/assets/chunk-4OADUZ5Q-CxrSvOwZ.js +10 -0
  387. package/web/assets/{chunk-DMV4VAQV-Bn6WXDSX.js → chunk-5IQCK5RY-CCMhl4so.js} +2 -2
  388. package/web/assets/{chunk-RRFMTAIC-CMN0wcYA.js → chunk-5MVFYWFU-Db9KvrYO.js} +1 -1
  389. package/web/assets/chunk-CLS4B6BI-Uvx5QNsu.js +168 -0
  390. package/web/assets/chunk-DZP67EKU-B3Mt380j.js +70 -0
  391. package/web/assets/{chunk-6L755F7B-CMhLBxi2.js → chunk-EICP7K5X-CqwE9hui.js} +1 -1
  392. package/web/assets/{chunk-BI6VK774-D0oaWk8V.js → chunk-GDY2UAB4-CtQQR0a8.js} +1 -1
  393. package/web/assets/chunk-H7VHZCWX-CE1nAmt6.js +1 -0
  394. package/web/assets/chunk-IJBDOHL6-BUKtY6oF.js +1 -0
  395. package/web/assets/{chunk-OIYT25JQ-CQ6KOZGX.js → chunk-JFFVMJIA-0fJenqia.js} +1 -1
  396. package/web/assets/{chunk-HNC4WDU7-DoU3KnA7.js → chunk-JZGDI5JK-BeTMSSkJ.js} +1 -1
  397. package/web/assets/{chunk-EQFTRU2I-1ix7TzW9.js → chunk-KD6SPIPN-Qp1Pflah.js} +1 -1
  398. package/web/assets/chunk-NF366YI6-CpSpya3n.js +231 -0
  399. package/web/assets/chunk-NUDPKBEF-CU1-BQIW.js +34 -0
  400. package/web/assets/{chunk-SATU7PGQ-B1zhgOvc.js → chunk-OJINMHTS-zOCQe4eI.js} +1 -1
  401. package/web/assets/{chunk-E5QJAATJ-dh-5CtIV.js → chunk-Q6NGPIAH-yyBomb63.js} +1 -1
  402. package/web/assets/chunk-QJSWEUOL-D_rf9gPD.js +128 -0
  403. package/web/assets/chunk-R7JDMUKV-CRoqKpD6.js +1 -0
  404. package/web/assets/{chunk-VE5CLXGZ-C1vB7wpY.js → chunk-RCXAM2GK-CUqrWQDV.js} +1 -1
  405. package/web/assets/{chunk-66DQ2XMT-CM0xjFAN.js → chunk-RT4MAWR3-zXy9-Lvt.js} +1 -1
  406. package/web/assets/{chunk-TZUXEDM2-B7p9AcTw.js → chunk-SFMINX4W-J2UrvMpD.js} +1 -1
  407. package/web/assets/{chunk-LORZLZWG-B57uGw2N.js → chunk-SLDQ463B-CHY6e_bQ.js} +2 -2
  408. package/web/assets/{chunk-FWYVLQTC-BgDItRzJ.js → chunk-TCEHX7R3-CXPDEVbm.js} +1 -1
  409. package/web/assets/{chunk-224SPVON-B8gxtXpy.js → chunk-VQC2EELJ-Dc8fIy4R.js} +1 -1
  410. package/web/assets/chunk-VRZKL3EH-DB3KixTQ.js +1 -0
  411. package/web/assets/{chunk-A7VWPJGB-B4O3oQdQ.js → chunk-VTHDQ7BV-b7FsPQZO.js} +1 -1
  412. package/web/assets/chunk-VY2OSTJR-Dkgc3Brg.js +2 -0
  413. package/web/assets/chunk-WUBWJARI-7FwILJ3k.js +2 -0
  414. package/web/assets/{chunk-D6VWDJW2-BxcBdZ9c.js → chunk-YBMQ5JGJ-BSnyWZmd.js} +1 -1
  415. package/web/assets/chunk-YDMLL4PJ-IHhMngTz.js +2 -0
  416. package/web/assets/chunk-Z6V44ARX-BjCEsF0Q.js +62 -0
  417. package/web/assets/chunk-ZJJ7VQJP-Dz089b-K.js +321 -0
  418. package/web/assets/chunk-ZNLK7HRR-B0iFpmW4.js +1 -0
  419. package/web/assets/chunk-ZQN7UHIL-Cy7KAjQt.js +1 -0
  420. package/web/assets/{chunk-L5GCOVLC-CE2w8Nm_.js → chunk-ZS6VBONO-DBjJO0w9.js} +2 -2
  421. package/web/assets/{chunk-NV3KIAZN-5yvcpMiu.js → chunk-ZUNWM646-DNRJyNPe.js} +1 -1
  422. package/web/assets/classDiagram-FGAMPOII-C5Ibyode.js +1 -0
  423. package/web/assets/classDiagram-v2-AI5RJEK4-C5Ibyode.js +1 -0
  424. package/web/assets/{cose-bilkent-RZA4XIPR-BHpK4Crp.js → cose-bilkent-Q72C2OJ3-DMeFXnK8.js} +1 -1
  425. package/web/assets/cynefin-OW5HDTMX-6OCTWOGG-vlqakYcO.js +1 -0
  426. package/web/assets/{cynefinDiagram-TR7LZUCG-C3Iar9lZ.js → cynefinDiagram-2OOWUY4M-BY6DZkTt.js} +1 -1
  427. package/web/assets/dagre-MPVFI544-DWO70fy_.js +4 -0
  428. package/web/assets/diagram-AE4NWXFW-f7JRVRUL.js +24 -0
  429. package/web/assets/{diagram-L7DVE3PN-GIRyVUtt.js → diagram-BUJID5UG-BXZv1rCS.js} +2 -2
  430. package/web/assets/{diagram-JCD7PQ7C-DC5-9FRr.js → diagram-C44CZ5NI-CRNfvjCq.js} +1 -1
  431. package/web/assets/diagram-EYOLEXXS-DZOjKQVE.js +30 -0
  432. package/web/assets/diagram-P7B6PWOJ-D3SQmw3_.js +3 -0
  433. package/web/assets/{ebnfDiagram-DRHRQJWO-D8_CI4ZF.js → ebnfDiagram-FTPZ3PV6-jKWpIDN2.js} +1 -1
  434. package/web/assets/erDiagram-H3ERR26V-BqJntEuJ.js +99 -0
  435. package/web/assets/eventmodeling-NTZA5JFV-WKSWQHFS-Bq4FqtBL.js +1 -0
  436. package/web/assets/flowDiagram-YHGXBVSY-Dnlk2bEu.js +1 -0
  437. package/web/assets/ganttDiagram-RWFTB7ED-Bl6Y1K5T.js +292 -0
  438. package/web/assets/gitGraph-4MIJSDKK-JH2HKYUO-B3yc4GkH.js +1 -0
  439. package/web/assets/gitGraphDiagram-MAR6GKOP-CkNvsrdw.js +106 -0
  440. package/web/assets/{index-CJDpyH-n.css → index-C2_aej58.css} +1 -1
  441. package/web/assets/{index-DDRie6_8.js → index-Dj3vQOHK.js} +51 -51
  442. package/web/assets/info-A6RAGUB7-3TX3OFWW-BM3N5_Dv.js +1 -0
  443. package/web/assets/infoDiagram-LV547AFH-COkFutAf.js +2 -0
  444. package/web/assets/{ishikawaDiagram-4JDSWWDM-DmqWtjkl.js → ishikawaDiagram-PUJQGNDS-Bb8FylSR.js} +2 -2
  445. package/web/assets/{journeyDiagram-QUS3FAKT-XAzVzrQW.js → journeyDiagram-EW5QJT4W-BzoHUBhM.js} +4 -4
  446. package/web/assets/{kanban-definition-D4L7SQKK-YurRnmYn.js → kanban-definition-JSQERAZB-Uy2Pi0yQ.js} +4 -4
  447. package/web/assets/katex-CZ4GXH2S-C0Fcou7U.js +257 -0
  448. package/web/assets/{mindmap-definition-66QP5TD6-ChrWWnil.js → mindmap-definition-VBJCLQLM-B-CnOnt_.js} +3 -3
  449. package/web/assets/packet-AYTQ26CC-MKII6P3Y-Bd52ZE3V.js +1 -0
  450. package/web/assets/{pegDiagram-YKIO5C6U-QGeQLEq2.js → pegDiagram-7WILOQTJ-D9sneKXg.js} +1 -1
  451. package/web/assets/pie-WAS4IAKB-6B4OPMJ5-DW5Xy69Z.js +1 -0
  452. package/web/assets/pieDiagram-EFA7CBSB-Dx2NQofL.js +39 -0
  453. package/web/assets/{quadrantDiagram-VO5QLCXA-B7098wSc.js → quadrantDiagram-WARVYA7P-DPKL9_0A.js} +3 -3
  454. package/web/assets/radar-RG4KPBEZ-UPTR3XUL-CkzibT2h.js +1 -0
  455. package/web/assets/railroad-74A4TZTK-MEFUPFMS-D9zF-mDN.js +1 -0
  456. package/web/assets/railroad-abnf-HS5TGJTU-R5YG2S5D-ruacjUbX.js +1 -0
  457. package/web/assets/railroad-ebnf-LZEXJU2U-G5QEX2L6-S1zc5OiO.js +1 -0
  458. package/web/assets/railroad-peg-WCYAUIDC-H5I2ZRY2-Cbcr0XzY.js +1 -0
  459. package/web/assets/{railroadDiagram-M77LTMSB-AXu4vxk_.js → railroadDiagram-5NIQVNLQ-DsweTo14.js} +1 -1
  460. package/web/assets/{requirementDiagram-RASO5HTO-B-l8InpK.js → requirementDiagram-SXREGOKZ-CXwdNSVR.js} +4 -4
  461. package/web/assets/{sankeyDiagram-F32LOJ6L-Kd7zjJgX.js → sankeyDiagram-VV74M2VP-CA6jtP9P.js} +3 -3
  462. package/web/assets/{sequenceDiagram-URATNSBD-BlcdY9DS.js → sequenceDiagram-52ZFIFDL-k_5rEbzm.js} +15 -15
  463. package/web/assets/src-Df5C1Nz4.js +1 -0
  464. package/web/assets/stateDiagram-3QAKPIVI-BejHTf31.js +1 -0
  465. package/web/assets/stateDiagram-v2-HN2P4RDZ-BrfoRuOc.js +1 -0
  466. package/web/assets/swimlanes-QLVP42QD-CQSVO5D6.js +1 -0
  467. package/web/assets/swimlanesDiagram-JKPDBFCN-DrWB9kcD.js +8 -0
  468. package/web/assets/{timeline-definition-WJXWRH4I-DUfp1R3E.js → timeline-definition-GS45XAXW-C0Ks8rPP.js} +4 -4
  469. package/web/assets/treeView-Q6P3EWNA-ZRJGANNQ-DmKOCo6L.js +1 -0
  470. package/web/assets/treemap-WGGIJYW6-K64VN5AS-DO3xY92U.js +1 -0
  471. package/web/assets/{vennDiagram-K3VCMQLI-Bj-FN-XS.js → vennDiagram-QIQU2ZRJ-rgPiS837.js} +3 -3
  472. package/web/assets/wardley-WFR3VGLG-D67OWCCH-DiDPBNft.js +1 -0
  473. package/web/assets/wardleyDiagram-U6YNM66N-DRVr7SPi.js +78 -0
  474. package/web/assets/{xychartDiagram-5VRUVLCW-CK_rbSWE.js → xychartDiagram-XJAZEDNM-COtlZzYb.js} +3 -3
  475. package/web/index.html +19 -17
  476. package/web/assets/ProcessFlowModal-Badh2Pbg.js +0 -24
  477. package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +0 -1
  478. package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +0 -36
  479. package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +0 -132
  480. package/web/assets/c4Diagram-X2LASM7V-CnhoT4aV.js +0 -10
  481. package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +0 -127
  482. package/web/assets/chunk-5VCL7Z4A-CQ-2dYWj.js +0 -1
  483. package/web/assets/chunk-6BELYETK-ChsYtfHW.js +0 -1
  484. package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +0 -2
  485. package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +0 -32
  486. package/web/assets/chunk-KRXBNO2N-C-UNuKKf.js +0 -1
  487. package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +0 -321
  488. package/web/assets/chunk-LIEV3EAG-pyKGssV3.js +0 -1
  489. package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +0 -62
  490. package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +0 -10
  491. package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +0 -156
  492. package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +0 -70
  493. package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +0 -231
  494. package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +0 -2
  495. package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +0 -1
  496. package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +0 -1
  497. package/web/assets/chunk-WJBAP47W-BckNkuEN.js +0 -1
  498. package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +0 -1
  499. package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +0 -1
  500. package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +0 -1
  501. package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +0 -4
  502. package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +0 -30
  503. package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +0 -3
  504. package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +0 -24
  505. package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +0 -85
  506. package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +0 -1
  507. package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +0 -1
  508. package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +0 -292
  509. package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +0 -1
  510. package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +0 -106
  511. package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +0 -1
  512. package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +0 -2
  513. package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +0 -257
  514. package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +0 -1
  515. package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +0 -1
  516. package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +0 -39
  517. package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +0 -1
  518. package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +0 -1
  519. package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +0 -1
  520. package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +0 -1
  521. package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +0 -1
  522. package/web/assets/src-C5sx_gMp.js +0 -1
  523. package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +0 -1
  524. package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +0 -1
  525. package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +0 -2
  526. package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +0 -8
  527. package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +0 -1
  528. package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +0 -1
  529. package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +0 -1
  530. package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +0 -78
  531. /package/web/assets/{sizeCapture-5GORAGP4-BEqQzsxs.js → sizeCapture-FGA5AGYP-BEqQzsxs.js} +0 -0
@@ -0,0 +1,2070 @@
1
+ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
2
+ import { getZigParser, getZigScopeQuery } from './query.js';
3
+ import { getTreeSitterBufferSize } from '../../constants.js';
4
+ import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
5
+ import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
6
+ import { hasZigPubKeyword } from '../../export-detection.js';
7
+ import { normalizeZigTypeName } from './interpret.js';
8
+ import { buildZigBoolConstMap, collectZigStaticGatedRanges, isPositionStaticGated, } from '../../call-extractors/zig-static-gating.js';
9
+ /**
10
+ * A call capture inside an `if (CONST_FALSE)` body (or the `else` of an
11
+ * `if (CONST_TRUE)`) gets this extra key; `scope-extractor` turns it into
12
+ * `ReferenceSite.staticGated`
13
+ * and the emitters copy it onto the CALLS edge. Same idiom as Go's
14
+ * `@reference.callee-position`: a zero-range marker, present or absent, so
15
+ * every ungated site's capture set stays byte-identical.
16
+ */
17
+ const ZERO_RANGE = Object.freeze({ startLine: 0, startCol: 0, endLine: 0, endCol: 0 });
18
+ const STATIC_GATED_MARKER = Object.freeze({
19
+ name: '@reference.static-gated',
20
+ range: ZERO_RANGE,
21
+ text: '',
22
+ });
23
+ const NO_IMPORT_ALIASES = new Map();
24
+ /** Stamp `@reference.static-gated` onto every call capture whose anchor sits
25
+ * in a statically dead range. File-local constants only for now (v1): the
26
+ * cross-file alias walk in `zig-static-gating.ts` needs the repo file list,
27
+ * which the capture layer does not see. */
28
+ function stampZigStaticGating(out, root) {
29
+ // No early return on an empty constant table: `collectZigStaticGatedRanges`
30
+ // also folds bare literals (`if (false) { ... }`), which need no constants.
31
+ const bools = buildZigBoolConstMap(root);
32
+ const ranges = collectZigStaticGatedRanges(root, bools, NO_IMPORT_ALIASES, () => undefined);
33
+ if (ranges.length === 0)
34
+ return out;
35
+ return out.map((m) => {
36
+ const key = Object.keys(m).find((k) => k.startsWith('@reference.call.'));
37
+ if (key === undefined)
38
+ return m;
39
+ const anchor = m[key];
40
+ if (anchor === undefined)
41
+ return m;
42
+ return isPositionStaticGated(anchor.range.startLine, anchor.range.startCol, ranges)
43
+ ? { ...m, '@reference.static-gated': STATIC_GATED_MARKER }
44
+ : m;
45
+ });
46
+ }
47
+ /** Zig container node types: `struct`, `enum`, `union` and the fieldless
48
+ * `opaque` all bind through `const T = <container> {…}` and may own methods.
49
+ * Single source for the class/field/method extractor configs. */
50
+ export const ZIG_CONTAINER_TYPES = new Set([
51
+ 'struct_declaration',
52
+ 'enum_declaration',
53
+ 'union_declaration',
54
+ 'opaque_declaration',
55
+ ]);
56
+ /** Is `node` the `@import(…)` builtin call? */
57
+ function isZigImportBuiltin(node) {
58
+ if (node?.type !== 'builtin_function')
59
+ return false;
60
+ const builtin = node.namedChild(0);
61
+ return builtin?.type === 'builtin_identifier' && builtin.text === '@import';
62
+ }
63
+ /** The `@import(…)` call at the ROOT of a value expression: the value itself
64
+ * (`@import("x.zig")`) or the leftmost object of a member chain
65
+ * (`@import("x.zig").Foo`, `@import("std").mem.Allocator`). Null otherwise. */
66
+ export function zigImportRootOf(value) {
67
+ let cur = value;
68
+ while (cur?.type === 'field_expression')
69
+ cur = cur.childForFieldName('object');
70
+ return isZigImportBuiltin(cur) ? cur : null;
71
+ }
72
+ /** Is this `@import(…)` builtin the receiver of a member call —
73
+ * `@import("dump.zig").root(...)` — or of a qualified construction —
74
+ * `@import("a.zig").Thing{…}` — i.e. the `object` of a `field_expression`
75
+ * that is the `function` of a `call_expression` or the type of a
76
+ * `struct_initializer`? A deeper chain (`@import("x.zig").Foo.init()`) is
77
+ * not: its receiver is `….Foo`, and the builtin is only the module the chain
78
+ * starts from (the namespace chain walk resolves it from there). */
79
+ export function isZigInlineImportReceiver(importNode) {
80
+ const field = importNode.parent;
81
+ if (field?.type !== 'field_expression')
82
+ return false;
83
+ if (field.childForFieldName('object')?.id !== importNode.id)
84
+ return false;
85
+ const use = field.parent;
86
+ if (use === null || use === undefined)
87
+ return false;
88
+ if (use.type === 'call_expression')
89
+ return use.childForFieldName('function')?.id === field.id;
90
+ // `@import("a.zig").Thing{…}` — the module is the receiver of a qualified
91
+ // CONSTRUCTION (the `@reference.call.constructor` rule with a receiver, see
92
+ // the query), exactly as it is of a member call. Without the namespace
93
+ // binding the site had a receiver nothing was bound to, so the aggregate
94
+ // event had no target (PR #1432 review, 8.11).
95
+ return use.type === 'struct_initializer' && use.namedChild(0)?.id === field.id;
96
+ }
97
+ /** Is this variable_declaration a container binding (`const T = struct {…}`)
98
+ * or an import binding (`const x = @import("…")`, `const X = @import("…").X`)?
99
+ * Those groups are emitted by their dedicated query rules; the plain
100
+ * @declaration.variable / @definition.const match for the same node must be
101
+ * dropped so the name binds exactly once (and no Const node shadows the
102
+ * Struct / import). Shared by the scope walker, the variable extractor and
103
+ * the provider's `shouldSkipDefinitionCapture` so the structure-phase records
104
+ * and the scope-side bindings agree on what counts as a plain variable. */
105
+ /** A binding whose name the type system already owns: a container / import
106
+ * binding, or the top-level `@This()` self-alias of a file-struct. None of
107
+ * these may mint a Const beside the Struct / import they stand for. */
108
+ export function isZigTypeShadowingBinding(declNode) {
109
+ if (isZigContainerOrImportBinding(declNode))
110
+ return true;
111
+ return isZigFileThisAlias(declNode) && isZigFileStruct(declNode.parent);
112
+ }
113
+ export function isZigContainerOrImportBinding(declNode) {
114
+ const typeNode = declNode.childForFieldName('type');
115
+ for (let i = 0; i < declNode.namedChildCount; i++) {
116
+ const child = declNode.namedChild(i);
117
+ if (child === null)
118
+ continue;
119
+ if (ZIG_CONTAINER_TYPES.has(child.type))
120
+ return true;
121
+ // Only the VALUE binds an import: `var x: @import("foo.zig").T =
122
+ // undefined;` types a plain variable by an imported type, it imports
123
+ // nothing under `x` (see `isZigTypePositionImport`).
124
+ if (child.id === typeNode?.id)
125
+ continue;
126
+ if (zigImportRootOf(child) !== null)
127
+ return true;
128
+ }
129
+ return false;
130
+ }
131
+ /** Does the `@import(…)` a binding rule matched sit in the TYPE annotation of
132
+ * its declaration (`var x: @import("foo.zig").T = undefined;`) rather than
133
+ * in its value? `variable_declaration` is fieldless except for `type:`, so
134
+ * the query cannot tell the two positions apart; such a match must not bind
135
+ * `x` as an import — `x` is a variable, and the `@import` is only a file
136
+ * dependency (the `@import.inline` rule records it as a side-effect import
137
+ * once this match releases the source node). */
138
+ export function isZigTypePositionImport(stmt, source) {
139
+ const typeNode = stmt.childForFieldName('type');
140
+ return (typeNode !== null &&
141
+ typeNode.startIndex <= source.startIndex &&
142
+ source.endIndex <= typeNode.endIndex);
143
+ }
144
+ /** Does this `variable_declaration` carry a `const` / `var` keyword child?
145
+ * tree-sitter-zig 1.1.2 parses statement-position ASSIGNMENTS (`x = 5;`,
146
+ * `x += 1;`, `_ = expr;`) as `variable_declaration` too — the only thing
147
+ * that separates a real binding from a re-assignment is the keyword. Query
148
+ * rules match the keyword literally; this is the JS-side twin for the
149
+ * extractors and the phantom-local guard in `emitZigScopeCaptures`. */
150
+ export function isZigKeywordDeclaration(declNode) {
151
+ for (let i = 0; i < declNode.childCount; i++) {
152
+ const t = declNode.child(i)?.type;
153
+ if (t === 'const' || t === 'var')
154
+ return true;
155
+ }
156
+ return false;
157
+ }
158
+ /** Is `root` a FILE-STRUCT — a `.zig` file that IS a type? In Zig every file
159
+ * is a struct; the ones that matter as types are named after the file stem
160
+ * (`Page.zig` declares `Page`, `@typeName(Page)` is `"Page"`) and their
161
+ * top-level `fn`s taking a receiver are its methods. Two signals, either one
162
+ * suffices:
163
+ * - the top level declares at least one container field (an instantiable
164
+ * type with state); the MISSING-identifier placeholder tree-sitter-zig
165
+ * recovers for an empty body is not a field (see
166
+ * `zigFieldConfig.extractName`);
167
+ * - a top-level `fn` takes the file's OWN type as its first parameter —
168
+ * `self: *@This()`, or `self: *Self` with `const Self = @This();` at
169
+ * file level. A zero-sized file type (`Empty.zig`: no field, a `Self`
170
+ * alias and `pub fn ping(self: *Self)`) is still constructed (`Empty{}`)
171
+ * and dispatched on by importers; keyed on fields alone it lost its
172
+ * `Struct`, its `HAS_METHOD`s and every `e.ping()` edge (PR #1432
173
+ * review, 8.12).
174
+ * A file with neither is a namespace and stays a Module: its fns keep their
175
+ * `Function` ids, and a `const js = @This();` there stays a Const. Only the
176
+ * receiver TYPE decides, never the parameter name: `fn f(self: Foo)` in a
177
+ * utility file is a free function that happens to call its argument `self`. */
178
+ export function isZigFileStruct(root) {
179
+ if (root?.type !== 'source_file')
180
+ return false;
181
+ for (let i = 0; i < root.namedChildCount; i++) {
182
+ const child = root.namedChild(i);
183
+ if (child?.type !== 'container_field')
184
+ continue;
185
+ const name = child.childForFieldName('name');
186
+ if (name !== null && name.text.length > 0)
187
+ return true;
188
+ }
189
+ let aliases;
190
+ for (let i = 0; i < root.namedChildCount; i++) {
191
+ const fn = root.namedChild(i);
192
+ if (fn?.type !== 'function_declaration')
193
+ continue;
194
+ const first = fn.namedChildren
195
+ .find((c) => c?.type === 'parameters')
196
+ ?.namedChild(0);
197
+ const typeNode = first?.type === 'parameter' ? first.childForFieldName('type') : null;
198
+ if (typeNode === null || typeNode === undefined)
199
+ continue;
200
+ const nominal = zigParameterNominalType(typeNode.text);
201
+ if (nominal === '@This()')
202
+ return true;
203
+ aliases ??= zigThisAliasNamesIn(root);
204
+ if (aliases.has(nominal))
205
+ return true;
206
+ }
207
+ return false;
208
+ }
209
+ /** The type name a file-struct declares: the file stem (`src/browser/Page.zig`
210
+ * → `Page`). This is what importers write (`const Page = @import("Page.zig")`)
211
+ * and what `@typeName` reports; the in-file `const Page = @This();` alias is
212
+ * a second spelling of the same type (see `zigThisAliasTargets`). */
213
+ export function zigFileStructName(filePath) {
214
+ const base = filePath.replace(/\\/g, '/').split('/').pop() ?? filePath;
215
+ return base.endsWith('.zig') ? base.slice(0, -'.zig'.length) : base;
216
+ }
217
+ /** Is `declNode` a top-level `const X = @This();` — the idiomatic self-alias of
218
+ * a file-struct? Such a binding names the FILE's own type, so a file-struct
219
+ * must not mint a `Const` (graph node or scope binding) beside the `Struct`
220
+ * it aliases — the Const would shadow the type for every `x: *Page`. */
221
+ export function isZigFileThisAlias(declNode) {
222
+ if (declNode.type !== 'variable_declaration' || declNode.parent?.type !== 'source_file') {
223
+ return false;
224
+ }
225
+ if (!isZigKeywordDeclaration(declNode))
226
+ return false;
227
+ const named = declNode.namedChildren.filter((c) => c !== null);
228
+ if (named.length !== 2 || named[0].type !== 'identifier')
229
+ return false;
230
+ const value = named[1];
231
+ return (value.type === 'builtin_function' &&
232
+ value.namedChild(0)?.type === 'builtin_identifier' &&
233
+ value.namedChild(0)?.text === '@This');
234
+ }
235
+ /** Does this module-level import binding REPUBLISH the name? `pub const Arena
236
+ * = @import("Arena.zig");` at file scope makes `Arena` part of this file's
237
+ * public surface: a third file reaches it as `lp.Arena` (Lightpanda's
238
+ * `lightpanda.zig` is one long list of these). Same fact as a Python
239
+ * `__init__.py` `from .impl import X` — the shared contract's
240
+ * `reexportsName` flag. Only `pub` at container level publishes; a `const`
241
+ * inside a fn body binds locally. */
242
+ export function isZigPublishingImport(declNode) {
243
+ return declNode.parent?.type === 'source_file' && hasZigPubKeyword(declNode);
244
+ }
245
+ /** The BINDING name of a Zig container node — the spelling code uses to
246
+ * refer to it — or undefined for a truly anonymous one. Three shapes carry
247
+ * one:
248
+ * - a file-struct: the file stem (`Page.zig` → `Page`);
249
+ * - `const Point = struct {…}` — the first identifier of the wrapping
250
+ * `variable_declaration`;
251
+ * - `pub fn List(comptime T: type) type { return struct {…}; }` — the
252
+ * generic type constructor. Zig has no other spelling for a generic
253
+ * type, and every reader calls the returned container `List`, so the
254
+ * enclosing function's name IS the type name (`ArrayList(u8)`).
255
+ * This is what scope-side NAME BINDINGS use (`R.get()`, `self: *R`, a
256
+ * `const Self = @This();` rewrite). The graph IDENTITY of a container is
257
+ * `zigContainerName`, which equals the binding name except for the
258
+ * function-local and anonymous shapes (F8). */
259
+ export function zigContainerBindingName(containerNode, filePath) {
260
+ if (containerNode.type === 'source_file') {
261
+ return filePath !== undefined && isZigFileStruct(containerNode)
262
+ ? zigFileStructName(filePath)
263
+ : undefined;
264
+ }
265
+ const parent = containerNode.parent;
266
+ if (parent === null || parent === undefined)
267
+ return undefined;
268
+ if (parent.type === 'variable_declaration') {
269
+ for (let i = 0; i < parent.namedChildCount; i++) {
270
+ const child = parent.namedChild(i);
271
+ if (child?.type === 'identifier')
272
+ return child.text;
273
+ }
274
+ return undefined;
275
+ }
276
+ return zigTypeConstructorOf(containerNode)?.childForFieldName('name')?.text;
277
+ }
278
+ /** The nominal type spelled at a parameter with its sigils stripped:
279
+ * `*const Self` → `Self`, `?*T` → `T`, `Pool(Node)` → `Pool`; `@This()` is
280
+ * kept whole (a builtin, not a constructor name). */
281
+ function zigParameterNominalType(typeText) {
282
+ let t = typeText.trim();
283
+ let previous;
284
+ do {
285
+ previous = t;
286
+ t = t.replace(/^(?:\*|\?|const\s+|\s+)/, '');
287
+ } while (t !== previous);
288
+ if (!t.startsWith('@')) {
289
+ const paren = t.indexOf('(');
290
+ if (paren > 0)
291
+ t = t.slice(0, paren).trim();
292
+ }
293
+ return t;
294
+ }
295
+ /** The `const X = @This();` alias names declared DIRECTLY in `container` (a
296
+ * container node, or the root of a file-struct). */
297
+ function zigThisAliasNamesIn(container) {
298
+ const out = new Set();
299
+ for (let i = 0; i < container.namedChildCount; i++) {
300
+ const decl = container.namedChild(i);
301
+ if (decl === null || decl.type !== 'variable_declaration')
302
+ continue;
303
+ const named = decl.namedChildren.filter((c) => c !== null);
304
+ const value = named[named.length - 1];
305
+ if (named.length === 2 &&
306
+ named[0].type === 'identifier' &&
307
+ value?.type === 'builtin_function' &&
308
+ value.namedChild(0)?.text === '@This' &&
309
+ isZigKeywordDeclaration(decl)) {
310
+ out.add(named[0].text);
311
+ }
312
+ }
313
+ return out;
314
+ }
315
+ /** The RECEIVER parameter of a container method, or null for a static fn.
316
+ *
317
+ * Zig has no receiver keyword: `self` is a convention, not a rule, and real
318
+ * code names the receiver after its type as often as not — tigerbeetle
319
+ * (`replica: *Replica`, 777 of 1127 methods), mach (`pool: *@This()`, 764 of
320
+ * 833). Treating only `self` as the receiver labelled those methods static,
321
+ * counted the receiver in their arity (`Counter.incr#1`) and left the
322
+ * scope-side binding sourced as a plain parameter. The rule, applied to the
323
+ * FIRST parameter only:
324
+ * - named `self`, whatever its type; or
325
+ * - typed as the enclosing container: `@This()`, the container's binding
326
+ * name (`Counter`, the generic constructor `Pool` for `Pool(Node)`, the
327
+ * file stem for a file-struct when `filePath` is known), or a
328
+ * `const X = @This();` alias declared in that container (`Self`, `PRNG`
329
+ * in `prng.zig`) — pointers, `const` and optionals stripped.
330
+ * `fn` must be a direct child of a container (or the file root); a fn nested
331
+ * in a body is never a method. Single source for the method extractor
332
+ * (parameters / receiver type / `isStatic`) and `emitZigScopeCaptures`
333
+ * (`@type-binding.receiver`), so the structure and scope phases agree. */
334
+ export function zigReceiverParameter(fn, filePath) {
335
+ const container = fn.parent;
336
+ if (container === null || container === undefined)
337
+ return null;
338
+ const inContainer = ZIG_CONTAINER_TYPES.has(container.type);
339
+ const inFileStruct = container.type === 'source_file' && isZigFileStruct(container);
340
+ if (!inContainer && !inFileStruct)
341
+ return null;
342
+ const paramList = fn.namedChildren.find((child) => child?.type === 'parameters');
343
+ const first = paramList?.namedChild(0);
344
+ if (first === null || first === undefined || first.type !== 'parameter')
345
+ return null;
346
+ if (first.childForFieldName('name')?.text === 'self')
347
+ return first;
348
+ const typeNode = first.childForFieldName('type');
349
+ if (typeNode === null)
350
+ return null;
351
+ const nominal = zigParameterNominalType(typeNode.text);
352
+ if (nominal.length === 0)
353
+ return null;
354
+ if (nominal === '@This()')
355
+ return first;
356
+ if (zigThisAliasNamesIn(container).has(nominal))
357
+ return first;
358
+ const binding = zigContainerBindingName(container, filePath);
359
+ return binding !== undefined && nominal === binding ? first : null;
360
+ }
361
+ /** The graph IDENTITY of a Zig container node — the name its class-like
362
+ * node, its members' owner segment (`Method:<file>:<name>.<fn>`) and the
363
+ * scope-side def all carry. Single source for the class/field/method
364
+ * extractor configs, the provider's `resolveContainerTypeOwner` hook (the
365
+ * structure phase's owner walk) and `emitZigScopeCaptures`, so the two
366
+ * phases agree by construction.
367
+ *
368
+ * - file-struct / `const T = struct {…}` at file level / generic type
369
+ * constructor: the binding name (`zigContainerBindingName`).
370
+ * - CONTAINER-HOSTED named container — `pub const Item = struct {…}`
371
+ * inside `const A = struct {…}`: owner-qualified, `A.Item` (recursively:
372
+ * `A.B.Item`). By binding name alone `A.Item` and `B.Item` — two
373
+ * distinct types, each with its own `run` — shared ONE `Struct:<file>:
374
+ * Item` node and one `Item.run` (PR #1432 review, 8.5). The scope side
375
+ * still binds the lexical spelling `Item` inside `A` (the
376
+ * `@declaration.binding-name` split in `emitZigScopeCaptures`), and the
377
+ * shared `populateClassOwnedMembers` leaves a dotted qualified name
378
+ * alone, so the def and the graph node agree on `A.Item` and its members
379
+ * qualify as `A.Item.run` — the same shape Java's `Outer.Inner` takes.
380
+ * - FUNCTION-LOCAL named container (F8) — `const R = struct {…}` inside a
381
+ * `fn` or `test` body: `<enclosing callable>$<name>`, e.g. `string$R`,
382
+ * `Reflect.string$R`. Zig code declares such helper containers per
383
+ * builder function (Lightpanda's `reflection.zig` has ~20 `const R =
384
+ * struct { fn get… fn set… }`), and by binding name alone they all
385
+ * collapsed onto ONE `Struct:<file>:R` with one `R.get`. Java's local
386
+ * classes are keyed the same way (`Outer$1Local`); the `$` is what tells
387
+ * the shared `populateClassOwnedMembers` the name is already complete.
388
+ * Two same-named locals in sibling blocks of ONE callable still share
389
+ * an identity (Zig code does not do that; javac's ordinal would).
390
+ * - ANONYMOUS container (F8) — `struct { fn lessThan(…) … }.lessThan`
391
+ * passed to `std.sort.pdq`, `const cmp = struct {…}.lt;`, a field or
392
+ * parameter typed `struct { min: u32 }`: `<host>$<ordinal>` where the
393
+ * host is the enclosing callable (`build$1`), else the enclosing
394
+ * container (`Outer$1`), else the file stem (`util$1`), and the ordinal
395
+ * counts the anonymous containers of that host in source order —
396
+ * javac's `Outer$1` rule (`synthesizeJavaTypeIdentity`). Without an
397
+ * identity their fns were OWNERLESS Methods (`Method:<file>:lessThan`),
398
+ * and same-named ones in one file collided on a single node.
399
+ * Deterministic in the tree alone, so any two walks over one source agree. */
400
+ export function zigContainerName(containerNode, filePath) {
401
+ const binding = zigContainerBindingName(containerNode, filePath);
402
+ if (containerNode.type === 'source_file')
403
+ return binding;
404
+ if (!ZIG_CONTAINER_TYPES.has(containerNode.type))
405
+ return undefined;
406
+ const host = zigIdentityHost(containerNode);
407
+ if (binding !== undefined) {
408
+ if (containerNode.parent?.type !== 'variable_declaration' || host === null)
409
+ return binding;
410
+ if (isZigCallableNode(host))
411
+ return `${zigCallableQualifiedName(host, filePath)}$${binding}`;
412
+ if (ZIG_CONTAINER_TYPES.has(host.type)) {
413
+ const hostName = zigContainerName(host, filePath);
414
+ return hostName === undefined ? binding : `${hostName}.${binding}`;
415
+ }
416
+ return binding; // file level: the binding name is the identity
417
+ }
418
+ const ordinal = zigAnonymousContainerOrdinal(containerNode);
419
+ const prefix = host === null ? undefined : zigAnonymousHostPrefix(host, filePath);
420
+ return `${prefix ?? ''}$${ordinal}`;
421
+ }
422
+ /** The label the graph gives a container node (`opaque {}` is a fieldless
423
+ * container that may own methods — Struct, see ZIG_QUERIES). */
424
+ export function zigContainerLabel(containerNode) {
425
+ switch (containerNode.type) {
426
+ case 'struct_declaration':
427
+ case 'opaque_declaration':
428
+ return 'Struct';
429
+ case 'enum_declaration':
430
+ return 'Enum';
431
+ case 'union_declaration':
432
+ return 'Union';
433
+ default:
434
+ return undefined;
435
+ }
436
+ }
437
+ /** Which ZIG_QUERIES rule mints a container's graph node (F8):
438
+ * - 'wrapper': `const T = struct {…}` at FILE level — the
439
+ * `variable_declaration … @definition.struct` rule (name from `@name`);
440
+ * - 'constructor': the container a generic type constructor returns —
441
+ * the `fn … type { return struct {…}; }` rule;
442
+ * - 'container': everything the bare `(struct_declaration)
443
+ * @definition.struct` rule owns — FUNCTION-LOCAL and CONTAINER-HOSTED
444
+ * named containers (their identities `string$R` / `A.Item` are not
445
+ * captures, so the class extractor names them via `zigContainerName`)
446
+ * and ANONYMOUS containers.
447
+ * The provider's `shouldSkipClassCapture` drops the other rules' matches for
448
+ * the same node so each container is minted exactly once. */
449
+ export function zigContainerAnchor(containerNode) {
450
+ if (containerNode.type === 'source_file')
451
+ return 'file';
452
+ if (zigTypeConstructorOf(containerNode) !== null)
453
+ return 'constructor';
454
+ if (containerNode.parent?.type === 'variable_declaration' &&
455
+ zigContainerBindingName(containerNode) !== undefined) {
456
+ const host = zigIdentityHost(containerNode);
457
+ return host === null || host.type === 'source_file' ? 'wrapper' : 'container';
458
+ }
459
+ return 'container';
460
+ }
461
+ /** Is this `@definition.<container>` match of ZIG_QUERIES REDUNDANT — i.e.
462
+ * minted by another rule for the same container (F8)? `definitionNode` is
463
+ * the match anchor (the wrapper `variable_declaration`, or the container
464
+ * node for the type-constructor and bare-container rules), `nameNode` its
465
+ * `@name` capture (the type-constructor rule captures the fn's name, the
466
+ * bare rule captures none). Wired through the provider's
467
+ * `shouldSkipDefinitionCapture`, which the definition phase consults for
468
+ * EVERY label — `shouldSkipClassCapture` would not see `Union`. */
469
+ export function isZigRedundantContainerCapture(definitionNode, nameNode) {
470
+ let container;
471
+ if (ZIG_CONTAINER_TYPES.has(definitionNode.type)) {
472
+ container = definitionNode;
473
+ }
474
+ else if (definitionNode.type === 'variable_declaration') {
475
+ for (let i = 0; i < definitionNode.namedChildCount; i++) {
476
+ const child = definitionNode.namedChild(i);
477
+ if (child !== null && ZIG_CONTAINER_TYPES.has(child.type)) {
478
+ container = child;
479
+ break;
480
+ }
481
+ }
482
+ }
483
+ if (container === undefined)
484
+ return false;
485
+ const anchor = zigContainerAnchor(container);
486
+ if (definitionNode.type === 'variable_declaration')
487
+ return anchor !== 'wrapper';
488
+ return nameNode !== undefined ? anchor !== 'constructor' : anchor !== 'container';
489
+ }
490
+ /** A callable body in Zig: a `fn` or a `test` block. */
491
+ function isZigCallableNode(node) {
492
+ return node.type === 'function_declaration' || node.type === 'test_declaration';
493
+ }
494
+ /** The nearest ancestor that hosts identities — a callable, a container or
495
+ * the file — starting from `node`'s parent. */
496
+ function zigIdentityHost(node) {
497
+ let cur = node.parent;
498
+ while (cur !== null && cur !== undefined) {
499
+ if (isZigCallableNode(cur) || ZIG_CONTAINER_TYPES.has(cur.type) || cur.type === 'source_file') {
500
+ return cur;
501
+ }
502
+ cur = cur.parent;
503
+ }
504
+ return null;
505
+ }
506
+ /** A callable's qualified name as the structure phase spells it: its owner
507
+ * container's identity, a dot, its own name (`Reflect.string`, `Page.init`,
508
+ * `helper` in a namespace file). A `test` block is keyed by its LINE
509
+ * (`test@L<line>`, 1-based; a test is a top-level declaration, so the line
510
+ * identifies it), not by its string: the class extractor's
511
+ * `buildQualifiedName` normalizes a graph node's `qualifiedName`
512
+ * (whitespace stripped, `::` → `.`), so an identity carrying `"Function:
513
+ * requested termination …"` would key the node under one spelling and the
514
+ * scope def under another, and `State{}` in that test would resolve to
515
+ * nothing. No `:` either (`row:col` would do), so an id keeps a single
516
+ * `label:file:name` colon structure for anything that splits on it. */
517
+ export function zigCallableQualifiedName(fnNode, filePath) {
518
+ const own = fnNode.type === 'test_declaration'
519
+ ? `test@L${fnNode.startPosition.row + 1}`
520
+ : (fnNode.childForFieldName('name')?.text ?? `fn@L${fnNode.startPosition.row + 1}`);
521
+ const host = zigIdentityHost(fnNode);
522
+ if (host === null)
523
+ return own;
524
+ const hostName = ZIG_CONTAINER_TYPES.has(host.type)
525
+ ? zigContainerName(host, filePath)
526
+ : host.type === 'source_file'
527
+ ? zigContainerBindingName(host, filePath) // file-struct stem; nothing for a namespace file
528
+ : zigCallableQualifiedName(host, filePath);
529
+ return hostName === undefined ? own : `${hostName}.${own}`;
530
+ }
531
+ /** The `<host>` part of an anonymous container's identity. */
532
+ function zigAnonymousHostPrefix(host, filePath) {
533
+ if (isZigCallableNode(host))
534
+ return zigCallableQualifiedName(host, filePath);
535
+ if (ZIG_CONTAINER_TYPES.has(host.type))
536
+ return zigContainerName(host, filePath);
537
+ // File level: the file stem — a Zig file is itself a struct, so this is
538
+ // javac's `Outer$1` with the file as `Outer` (`Page$1`, `util$1`).
539
+ return filePath === undefined ? undefined : zigFileStructName(filePath);
540
+ }
541
+ /** 1-based ordinal of an anonymous container among the anonymous containers
542
+ * sharing its identity host, in source order. Computed once per tree and
543
+ * memoized on the tree object (a node without `.tree` recomputes from its
544
+ * root); position-derived, so two parses of one source agree. */
545
+ const zigAnonymousOrdinalMemo = new WeakMap();
546
+ function zigAnonymousContainerOrdinal(containerNode) {
547
+ const tree = containerNode.tree;
548
+ let table = tree === undefined ? undefined : zigAnonymousOrdinalMemo.get(tree);
549
+ if (table === undefined) {
550
+ let root = containerNode;
551
+ while (root.parent !== null && root.parent !== undefined)
552
+ root = root.parent;
553
+ const built = new Map();
554
+ const perHost = new Map();
555
+ const visit = (node) => {
556
+ if (ZIG_CONTAINER_TYPES.has(node.type) && zigContainerBindingName(node) === undefined) {
557
+ const host = zigIdentityHost(node);
558
+ const key = host === null ? -1 : host.id;
559
+ const next = (perHost.get(key) ?? 0) + 1;
560
+ perHost.set(key, next);
561
+ built.set(node.id, next);
562
+ }
563
+ for (let i = 0; i < node.namedChildCount; i++) {
564
+ const child = node.namedChild(i);
565
+ if (child !== null)
566
+ visit(child);
567
+ }
568
+ };
569
+ visit(root);
570
+ if (tree !== undefined)
571
+ zigAnonymousOrdinalMemo.set(tree, built);
572
+ table = built;
573
+ }
574
+ return table.get(containerNode.id) ?? 0;
575
+ }
576
+ /** For a container that is the direct `return` value of a function whose
577
+ * return type is `type` — `fn List(comptime T: type) type { return struct
578
+ * {…}; }` — the function_declaration; null for any other placement. Only the
579
+ * literal `return_expression → expression_statement → block → fn` chain
580
+ * counts: a container nested deeper (`return struct {…}.field`, a container
581
+ * inside an `if`) is not the type the function constructs. */
582
+ export function zigTypeConstructorOf(containerNode) {
583
+ if (!ZIG_CONTAINER_TYPES.has(containerNode.type))
584
+ return null;
585
+ const ret = containerNode.parent;
586
+ if (ret?.type !== 'return_expression')
587
+ return null;
588
+ const stmt = ret.parent;
589
+ if (stmt?.type !== 'expression_statement')
590
+ return null;
591
+ const block = stmt.parent;
592
+ if (block?.type !== 'block')
593
+ return null;
594
+ const fn = block.parent;
595
+ if (fn?.type !== 'function_declaration' || fn.childForFieldName('body')?.id !== block.id) {
596
+ return null;
597
+ }
598
+ return fn.childForFieldName('type')?.text === 'type' ? fn : null;
599
+ }
600
+ /** F7 — is `call` an instantiation of a generic type constructor —
601
+ * `List(u8)`, `util.List(u8)`, `js.Bridge(T)`, `std.AutoHashMapUnmanaged(K, V)`?
602
+ * Zig spells a generic type as a CALL, and the grammar cannot separate it
603
+ * from a value call; the one signal is the naming convention (types and
604
+ * type constructors are TitleCase, functions camelCase), so the callee's
605
+ * last identifier decides. Heuristic by nature — say so wherever it is used. */
606
+ export function isZigTypeConstructorCall(call) {
607
+ if (call.type !== 'call_expression')
608
+ return false;
609
+ let callee = call.childForFieldName('function');
610
+ if (callee?.type === 'field_expression')
611
+ callee = callee.childForFieldName('member');
612
+ if (callee?.type !== 'identifier')
613
+ return false;
614
+ return /^[A-Z]/.test(callee.text);
615
+ }
616
+ /** A `fn` nested in a struct/enum/union/opaque container is a method. Single
617
+ * predicate shared between the provider's `labelOverride` (worker
618
+ * structure phase) and the scope-capture relabel below, so the graph
619
+ * node label and the scope-side def label cannot drift apart. The loose
620
+ * parameter shape matches what `labelOverride` receives.
621
+ *
622
+ * Only a `function_declaration` can be a method: a named `test "…" {}`
623
+ * inside a container is also a Function definition, but the method
624
+ * extractor does not know test blocks (no parameters, no `self`), so
625
+ * relabelling it would mint a Method id the definition phase never
626
+ * builds. Tests stay Functions wherever they sit. */
627
+ export function isZigContainerMethod(captureNode) {
628
+ if (captureNode?.type !== undefined && captureNode.type !== 'function_declaration')
629
+ return false;
630
+ let ancestor = captureNode?.parent;
631
+ while (ancestor) {
632
+ if (ZIG_CONTAINER_TYPES.has(ancestor.type))
633
+ return true;
634
+ // A top-level fn of a FILE-STRUCT is a method of the file's type.
635
+ if (ancestor.type === 'source_file')
636
+ return isZigFileStruct(ancestor);
637
+ ancestor = ancestor.parent;
638
+ }
639
+ return false;
640
+ }
641
+ /** `{ '@import.reexports': 'true' }` when `stmt` is a publishing import
642
+ * (see `isZigPublishingImport`), else nothing — spread into an import group. */
643
+ function republishMarker(stmt) {
644
+ return isZigPublishingImport(stmt)
645
+ ? { '@import.reexports': syntheticCapture('@import.reexports', stmt, 'true') }
646
+ : {};
647
+ }
648
+ /** Every `const X = @This();` in the tree, keyed by the alias node id's owner:
649
+ * returns a map from the DECLARING container node id to `[aliasName,
650
+ * containerName]`. The file-level alias maps to the file-struct name (only
651
+ * when the file is a file-struct — a namespace-only file's `const js =
652
+ * @This();` stays a Const and is not a type to rewrite to). The container
653
+ * side is the name the SCOPE BINDS: the binding name (`R` for a function-
654
+ * local `const R = struct {…}`, whose identity `string$R` is not a binding),
655
+ * or the synthetic identity of an anonymous container, which is bound under
656
+ * that very name (F8). */
657
+ function collectZigThisAliases(root, fileStructName, filePath) {
658
+ const out = new Map();
659
+ const visit = (node) => {
660
+ if (node.type === 'variable_declaration') {
661
+ const named = node.namedChildren.filter((c) => c !== null);
662
+ const value = named[named.length - 1];
663
+ if (named.length === 2 &&
664
+ named[0].type === 'identifier' &&
665
+ value?.type === 'builtin_function' &&
666
+ value.namedChild(0)?.text === '@This' &&
667
+ isZigKeywordDeclaration(node)) {
668
+ const owner = node.parent;
669
+ if (owner?.type === 'source_file') {
670
+ if (fileStructName !== undefined) {
671
+ out.set(owner.id, { alias: named[0].text, container: fileStructName });
672
+ }
673
+ }
674
+ else if (owner !== null && ZIG_CONTAINER_TYPES.has(owner.type)) {
675
+ const containerName = zigContainerBindingName(owner) ?? zigContainerName(owner, filePath);
676
+ if (containerName !== undefined) {
677
+ out.set(owner.id, { alias: named[0].text, container: containerName });
678
+ }
679
+ }
680
+ }
681
+ }
682
+ for (let i = 0; i < node.namedChildCount; i++) {
683
+ const child = node.namedChild(i);
684
+ if (child !== null)
685
+ visit(child);
686
+ }
687
+ };
688
+ visit(root);
689
+ return out;
690
+ }
691
+ /** If the nominal part of the type written at `typeNode` is a `@This()` alias
692
+ * of an enclosing container, the same text with the alias replaced by the
693
+ * container's name; undefined when nothing applies. Innermost container wins
694
+ * (a nested `Self` shadows the file's). */
695
+ function rewriteZigThisAlias(typeNode, aliases,
696
+ // The text to rewrite: the node's own, unless an earlier step (F6's value
697
+ // inference) already replaced the capture text.
698
+ text = typeNode.text) {
699
+ const nominal = /[A-Za-z_@][\w.]*(?:\([^)]*\))?\s*$/.exec(text)?.[0]?.trim();
700
+ if (nominal === undefined || nominal.length === 0)
701
+ return undefined;
702
+ const bare = nominal.replace(/\(.*\)$/, '');
703
+ let ancestor = typeNode.parent;
704
+ while (ancestor !== null) {
705
+ const entry = aliases.get(ancestor.id);
706
+ if (entry !== undefined && entry.alias === bare) {
707
+ if (entry.container === bare)
708
+ return undefined;
709
+ const idx = text.lastIndexOf(bare);
710
+ return text.slice(0, idx) + entry.container + text.slice(idx + bare.length);
711
+ }
712
+ ancestor = ancestor.parent;
713
+ }
714
+ return undefined;
715
+ }
716
+ /**
717
+ * Callable-value-flow vocabulary for tree-sitter-zig 1.1.2 (verified by AST
718
+ * dump). Two grammar quirks drive the callbacks:
719
+ *
720
+ * - `variable_declaration` is FIELDLESS apart from `type:` — `const f = target;`
721
+ * is `(variable_declaration (identifier) (identifier))`, and a bare
722
+ * re-assignment statement `f = target;` parses to the SAME node shape. The
723
+ * shared `left`/`name`/`value` fallback decomposes nothing, so
724
+ * `extractAssignment` pairs first-identifier → last-child positionally.
725
+ * `assignment_expression` (`self.f = target`) carries real `left`/`right`
726
+ * fields and is left to the shared path.
727
+ * - `call_expression` has NO argument-list wrapper: `invoke(second)` is
728
+ * `(call_expression function: (identifier) (identifier))`. Arguments are
729
+ * every named child other than the `function` field, hence
730
+ * `extractCallArguments`.
731
+ *
732
+ * - Zig's receiver is an EXPLICIT first parameter named `self` (the same
733
+ * convention `interpretZigTypeBinding` keys receiver typing on), and a
734
+ * method has TWO call spellings: the implicit `r.run(target)` and the
735
+ * explicit `Runner.run(&r, target)`. The shared harness joins actuals to
736
+ * formals by index, and formals are numbered once per function — so the
737
+ * receiver must be re-added on the CALL side, never dropped on the formal
738
+ * side (slicing `self` off the formals fixed the implicit spelling and lost
739
+ * the explicit one — PR #1432 review). `extractCallArguments` therefore
740
+ * prepends the receiver as actual 0 when the callee is a member call on a
741
+ * VALUE receiver (`r.run(target)`, `self.slot.release()`, `node.child()`),
742
+ * so both spellings yield `target@1` and join formal `cb@1`. A namespace
743
+ * or type receiver (`Runner.init(cb)`, `std.sort.pdq(…)`, `List(u8).init`,
744
+ * `@import("x.zig").f(cb)`) passes nothing implicitly and gets no prepend.
745
+ * Value-vs-type is F6's rule: the chain head is a fn-local name (param /
746
+ * local / payload) that is not TitleCase, or a MODULE-level value — a
747
+ * file- or container-level `var` / `const` whose declaration shape says
748
+ * "value" (`zigHostValueNames`: annotated, a struct literal, a non-type
749
+ * call, a literal), so `global_runner.run(target)` prepends `global_runner`
750
+ * exactly like `r.run(target)` does and `target` joins formal `cb@1`
751
+ * instead of `self@0` (PR #1432 review, 8.3).
752
+ *
753
+ * `builtin_function` (`@import`, `@sizeOf`, …) is deliberately not a call node:
754
+ * builtins never take user callables as flow arguments.
755
+ */
756
+ /** Per file: the callable-flow options close over the file's fn-local and
757
+ * host-value name caches (shared with F6's `zigCallReturnTypeOf`). */
758
+ function zigCallableCaptureOptions(fnLocalNames, hostValueNames) {
759
+ return {
760
+ functionNodeTypes: new Set(['function_declaration']),
761
+ callNodeTypes: new Set(['call_expression']),
762
+ parameterListNodeTypes: new Set(['parameters']),
763
+ parameterNodeTypes: new Set(['parameter']),
764
+ bindingNodeTypes: new Set(['variable_declaration']),
765
+ assignmentNodeTypes: new Set(['assignment_expression']),
766
+ identifierNodeTypes: new Set(['identifier']),
767
+ extractAssignment: (node) => {
768
+ if (node.type !== 'variable_declaration')
769
+ return undefined;
770
+ const named = node.namedChildren.filter((child) => child !== null);
771
+ if (named.length < 2 || named[0].type !== 'identifier')
772
+ return undefined;
773
+ const source = named[named.length - 1];
774
+ // `const x: T;` (extern) — the trailing child is the type, not a value.
775
+ if (source.id === node.childForFieldName('type')?.id)
776
+ return undefined;
777
+ return { destination: named[0], source };
778
+ },
779
+ extractFunctionParameters: (fn) => {
780
+ // `parameters` is a fieldless child of `function_declaration`. The
781
+ // leading `self` is kept: it is formal 0, matched by the receiver that
782
+ // `extractCallArguments` prepends (implicit spelling) or the explicit
783
+ // first actual (`Runner.run(&r, target)`).
784
+ const list = fn.namedChildren.find((child) => child?.type === 'parameters');
785
+ if (list === undefined || list === null)
786
+ return undefined;
787
+ return list.namedChildren.filter((child) => child !== null && child.type === 'parameter');
788
+ },
789
+ extractCallArguments: (call) => {
790
+ const callee = call.childForFieldName('function');
791
+ const explicit = call.namedChildren.filter((child) => child !== null && child.id !== callee?.id && child.type !== 'comment');
792
+ const receiver = zigImplicitReceiver(call, fnLocalNames, hostValueNames);
793
+ return receiver === undefined ? explicit : [receiver, ...explicit];
794
+ },
795
+ };
796
+ }
797
+ /** The value receiver `x.f(…)` passes implicitly as `self`, or undefined for
798
+ * a free call, a decl literal (`.init(…)`), or a namespace / type receiver
799
+ * (`Runner.init(…)`, `std.mem.eql(…)`, `List(u8).init(…)`, `@import(…).f(…)`).
800
+ * Same value-vs-type rule as `zigCallReturnTypeOf` (F6): the chain head is a
801
+ * name declared in the enclosing fn and is not TitleCase (a fn-local
802
+ * TitleCase name is a type alias, F7), or a module-level value
803
+ * (`zigIsHostValueName`). */
804
+ function zigImplicitReceiver(call, fnLocalNames, hostValueNames) {
805
+ const callee = call.childForFieldName('function');
806
+ if (callee === null || callee.type !== 'field_expression')
807
+ return undefined;
808
+ const object = callee.childForFieldName('object');
809
+ if (object === null)
810
+ return undefined; // `.init(…)` decl literal
811
+ const head = zigChainHead(object);
812
+ if (head === null || isZigTitleCase(head.text))
813
+ return undefined;
814
+ return zigIsValueName(call, head.text, fnLocalNames, hostValueNames) ? object : undefined;
815
+ }
816
+ /** Is `name`, read at `at`, a VALUE — a fn-local (param / local / payload) of
817
+ * the enclosing fn, or a module-level value declared by the file or by a
818
+ * container enclosing `at`? Zig forbids shadowing, so the first declaration
819
+ * found walking outwards is the only one. Namespaces (`std`, an `@import`
820
+ * handle, `const mem = std.mem`) and types are not values. */
821
+ function zigIsValueName(at, name, fnLocalNames, hostValueNames) {
822
+ const fn = zigEnclosingFunction(at);
823
+ if (fn !== null && zigFunctionLocalNames(fn, fnLocalNames).has(name))
824
+ return true;
825
+ let host = zigIdentityHost(at);
826
+ while (host !== null) {
827
+ if (!isZigCallableNode(host) && zigHostValueNames(host, hostValueNames).has(name))
828
+ return true;
829
+ host = zigIdentityHost(host);
830
+ }
831
+ return false;
832
+ }
833
+ /** The names a HOST (the file root or a container node) declares DIRECTLY as
834
+ * values — module-level state such as `var global_runner = Runner{};`,
835
+ * `var pool: Pool = undefined;`, `const default_config = Config.load();`,
836
+ * `const max = 16;`. Decided from the declaration's shape, the only evidence
837
+ * available before finalization:
838
+ * - annotated (`var x: T = …`, `const x: T;`) or initialized with a struct
839
+ * literal, a literal, or a call that is not a generic type instantiation
840
+ * (`isZigTypeConstructorCall`) → value;
841
+ * - container / `@import` bindings, TitleCase names (types, F7), and
842
+ * values that merely ALIAS another name (`const mem = std.mem;`,
843
+ * `var cur = orig;`) → not a value here: an alias is whatever it aliases,
844
+ * and a namespace alias prepended as a receiver would shift every
845
+ * callback index the other way.
846
+ * Lazily computed once per host node. */
847
+ function zigHostValueNames(host, cache) {
848
+ const cached = cache.get(host.id);
849
+ if (cached !== undefined)
850
+ return cached;
851
+ const names = new Set();
852
+ for (let i = 0; i < host.namedChildCount; i++) {
853
+ const decl = host.namedChild(i);
854
+ if (decl === null || decl.type !== 'variable_declaration' || !isZigKeywordDeclaration(decl)) {
855
+ continue;
856
+ }
857
+ const named = decl.namedChildren.filter((c) => c !== null);
858
+ const name = named[0];
859
+ if (name === undefined || name.type !== 'identifier' || isZigTitleCase(name.text))
860
+ continue;
861
+ if (named.length < 2 || isZigContainerOrImportBinding(decl))
862
+ continue;
863
+ const last = named[named.length - 1];
864
+ if (last.id !== decl.childForFieldName('type')?.id) {
865
+ const value = zigUnwrapValue(last);
866
+ if (value.type === 'identifier' ||
867
+ value.type === 'field_expression' ||
868
+ value.type === 'builtin_function' ||
869
+ (value.type === 'call_expression' && isZigTypeConstructorCall(value))) {
870
+ continue;
871
+ }
872
+ }
873
+ names.add(name.text);
874
+ }
875
+ cache.set(host.id, names);
876
+ return names;
877
+ }
878
+ // ─── F6: value-inferred and return-type bindings ─────────────────────────────
879
+ /** Strip the value wrappers Zig puts around a call — `try f()`, `f() catch
880
+ * …`, `f() orelse …`, `(f())` — down to the wrapped expression. Rust's
881
+ * `.await` unwrapping, one level richer. */
882
+ export function zigUnwrapValue(value) {
883
+ let cur = value;
884
+ for (;;) {
885
+ if (cur.type === 'try_expression' || cur.type === 'parenthesized_expression') {
886
+ const inner = cur.namedChildren.find((c) => c !== null && c.type !== 'comment');
887
+ if (inner === undefined)
888
+ return cur;
889
+ cur = inner;
890
+ }
891
+ else if (cur.type === 'catch_expression') {
892
+ // `<operand> catch [|err|] <handler>` — the operand is the first child.
893
+ const inner = cur.namedChild(0);
894
+ if (inner === null)
895
+ return cur;
896
+ cur = inner;
897
+ }
898
+ else if (cur.type === 'binary_expression') {
899
+ if (cur.childForFieldName('operator')?.type !== 'orelse')
900
+ return cur;
901
+ const left = cur.childForFieldName('left');
902
+ if (left === null)
903
+ return cur;
904
+ cur = left;
905
+ }
906
+ else {
907
+ return cur;
908
+ }
909
+ }
910
+ }
911
+ /** Zig style: types are TitleCase, functions camelCase, namespaces
912
+ * snake_case. A TitleCase callee (`List(u8)`, `std.ArrayList(T)`) is a type
913
+ * constructor — its call yields a TYPE, not a value, and is a type alias
914
+ * (F7), not a call-return binding. */
915
+ function isZigTitleCase(name) {
916
+ return /^[A-Z]/.test(name);
917
+ }
918
+ /** The leftmost identifier of a `field_expression` chain (`self.parser` →
919
+ * `self`), the identifier itself, or null when the chain roots in anything
920
+ * else (a call, an index, a builtin). */
921
+ function zigChainHead(node) {
922
+ let cur = node;
923
+ while (cur !== null && cur.type === 'field_expression')
924
+ cur = cur.childForFieldName('object');
925
+ return cur !== null && cur.type === 'identifier' ? cur : null;
926
+ }
927
+ /** Names bound INSIDE `fn` (parameters, `const`/`var` locals, payload
928
+ * captures), lazily computed once per function node. Zig forbids shadowing,
929
+ * so a name declared anywhere in the function is a value local everywhere in
930
+ * it, and a name NOT declared in it is a module-level (or imported) name.
931
+ * That is the whole distinction `zigCallReturnTypeOf` needs: a call on a
932
+ * local receiver (`node.asElement()`) yields the METHOD's return type, a
933
+ * call on a module-level receiver (`Counter.init()`, `mod.Counter.init()`)
934
+ * names the receiver's type. */
935
+ function zigFunctionLocalNames(fn, cache) {
936
+ const cached = cache.get(fn.id);
937
+ if (cached !== undefined)
938
+ return cached;
939
+ const names = new Set();
940
+ const visit = (node) => {
941
+ if (node.type === 'parameter') {
942
+ const name = node.childForFieldName('name');
943
+ if (name !== null)
944
+ names.add(name.text);
945
+ }
946
+ else if (node.type === 'variable_declaration' && isZigKeywordDeclaration(node)) {
947
+ const first = node.namedChild(0);
948
+ if (first?.type === 'identifier')
949
+ names.add(first.text);
950
+ }
951
+ else if (node.type === 'payload') {
952
+ for (let i = 0; i < node.namedChildCount; i++) {
953
+ const c = node.namedChild(i);
954
+ if (c?.type === 'identifier')
955
+ names.add(c.text);
956
+ }
957
+ }
958
+ for (let i = 0; i < node.namedChildCount; i++) {
959
+ const c = node.namedChild(i);
960
+ if (c !== null)
961
+ visit(c);
962
+ }
963
+ };
964
+ visit(fn);
965
+ cache.set(fn.id, names);
966
+ return names;
967
+ }
968
+ /** The nearest enclosing `function_declaration` / `test_declaration`, or
969
+ * null at module (container) level. */
970
+ function zigEnclosingFunction(node) {
971
+ let cur = node.parent;
972
+ while (cur !== null && cur !== undefined) {
973
+ if (cur.type === 'function_declaration' || cur.type === 'test_declaration')
974
+ return cur;
975
+ cur = cur.parent;
976
+ }
977
+ return null;
978
+ }
979
+ /** For `const t = <value>;`, the type source the value names, or undefined
980
+ * when the value types nothing on its own:
981
+ * - `Counter.init()` / `mod.Counter.init()` / `List(u8).init()` (receiver
982
+ * is a module-level name) → `{ type: 'Counter' }` — the receiver names
983
+ * the type (Rust `Foo::new()`); a value receiver simply finds no
984
+ * container later and declines.
985
+ * - `node.asElement()` / `self.parser.next()` (receiver head is a fn-local
986
+ * param / local / payload) → `{ type: 'node.asElement()', memberCall:
987
+ * true }` — the compound shape the shared resolver walks through the
988
+ * receiver's class scope to the method's `@type-binding.return`.
989
+ * - `makeThing()` (free call) → `{ type: 'makeThing' }` — chains to the
990
+ * fn's return binding.
991
+ * - a `struct_initializer` under a wrapper (`try Thing{…}`) → its type,
992
+ * `structLiteral: true` (the direct shape has its own query rules).
993
+ * - `.init()` decl literals, TitleCase callees (type constructors —
994
+ * aliases, F7), identifiers, field chains, literals → undefined.
995
+ * Wrappers (`try`, `catch`, `orelse`, parens) are unwrapped first. */
996
+ export function zigCallReturnTypeOf(value, localNamesCache, hostValueNames = new Map()) {
997
+ const inner = zigUnwrapValue(value);
998
+ if (inner.type === 'struct_initializer') {
999
+ if (inner.id === value.id)
1000
+ return undefined; // direct: the constructor rules own it
1001
+ const typeNode = inner.namedChild(0);
1002
+ if (typeNode === null ||
1003
+ !(typeNode.type === 'identifier' ||
1004
+ typeNode.type === 'field_expression' ||
1005
+ typeNode.type === 'call_expression')) {
1006
+ return undefined;
1007
+ }
1008
+ return { type: typeNode.text, structLiteral: true };
1009
+ }
1010
+ if (inner.type !== 'call_expression')
1011
+ return undefined;
1012
+ const callee = inner.childForFieldName('function');
1013
+ if (callee === null)
1014
+ return undefined;
1015
+ if (callee.type === 'identifier') {
1016
+ return isZigTitleCase(callee.text) ? undefined : { type: callee.text };
1017
+ }
1018
+ if (callee.type !== 'field_expression')
1019
+ return undefined;
1020
+ const object = callee.childForFieldName('object');
1021
+ const member = callee.childForFieldName('member');
1022
+ if (object === null || member === null)
1023
+ return undefined; // `.init()` decl literal
1024
+ if (isZigTitleCase(member.text))
1025
+ return undefined;
1026
+ const head = zigChainHead(object);
1027
+ if (head !== null) {
1028
+ // A fn-local or module-level value name is a VALUE receiver — unless it
1029
+ // is TitleCase: `const R = generic.List(u8); var l = R.init();` binds a
1030
+ // type alias inside the fn (F7), and `R.init()` names the type `R`
1031
+ // exactly like `Counter.init()` does at module level. Zig's naming
1032
+ // convention (types TitleCase, values snake_case) is the same signal
1033
+ // `isZigTypeConstructorCall` relies on.
1034
+ if (!isZigTitleCase(head.text) &&
1035
+ zigIsValueName(value, head.text, localNamesCache, hostValueNames)) {
1036
+ return { type: `${object.text}.${member.text}()`, memberCall: true };
1037
+ }
1038
+ }
1039
+ return { type: object.text };
1040
+ }
1041
+ /** Return-type node → the nominal type node under its sigils (`!*Thing` →
1042
+ * `Thing`), following error-union `ok`, pointer / nullable / slice / array
1043
+ * wrappers. Undefined when it bottoms out in nothing nominal. */
1044
+ function zigNominalTypeNode(typeNode) {
1045
+ let cur = typeNode;
1046
+ for (let guard = 0; cur !== null && cur !== undefined && guard < 16; guard++) {
1047
+ switch (cur.type) {
1048
+ case 'error_union_type':
1049
+ cur = cur.childForFieldName('ok');
1050
+ continue;
1051
+ case 'pointer_type':
1052
+ case 'nullable_type':
1053
+ case 'slice_type':
1054
+ case 'array_type':
1055
+ // The element type is the LAST named child (arrays carry the length
1056
+ // first, pointers/slices may carry a sentinel or `const`).
1057
+ cur = cur.namedChildren
1058
+ .filter((c) => c !== null && c.type !== 'comment')
1059
+ .pop();
1060
+ continue;
1061
+ default:
1062
+ return cur;
1063
+ }
1064
+ }
1065
+ return undefined;
1066
+ }
1067
+ /** Should `fn name() <type>` bind `name ↦ <type>`? Only when the return
1068
+ * type is nominal: a container name, a qualified name, a generic
1069
+ * instantiation, or `@This()`. Builtins (`void`, `u8`, `bool`, `anyerror`),
1070
+ * `type` (a generic type constructor — its container binding is the type),
1071
+ * and `@TypeOf(…)` bind nothing: `List ↦ type` would hijack the
1072
+ * `List(u8){}` constructor chain, and `f ↦ void` is noise. */
1073
+ export function zigReturnTypeIsNominal(typeNode) {
1074
+ const nominal = zigNominalTypeNode(typeNode);
1075
+ if (nominal === undefined)
1076
+ return false;
1077
+ switch (nominal.type) {
1078
+ case 'identifier':
1079
+ // `fn is(self: *Node, comptime T: type) ?*T` — `T` is the CALLER's
1080
+ // choice, not a type; binding `is ↦ T` would type every `node.is(X)`
1081
+ // as whatever class happens to be named `T`.
1082
+ return !zigIsComptimeTypeParameter(typeNode, nominal.text);
1083
+ case 'field_expression':
1084
+ case 'call_expression':
1085
+ return true;
1086
+ case 'builtin_function':
1087
+ return nominal.namedChild(0)?.text === '@This';
1088
+ default:
1089
+ return false;
1090
+ }
1091
+ }
1092
+ /** Is `name` a `comptime <name>: type` parameter of the function whose
1093
+ * return type `returnTypeNode` is, or of any function enclosing it (the
1094
+ * `T` of a generic type constructor is visible to every method of the
1095
+ * returned container: `fn get(self) T`)? */
1096
+ function zigIsComptimeTypeParameter(returnTypeNode, name) {
1097
+ let fn = returnTypeNode.parent;
1098
+ while (fn !== null && fn !== undefined) {
1099
+ if (fn.type === 'function_declaration') {
1100
+ const params = fn.namedChildren.find((c) => c?.type === 'parameters');
1101
+ for (let i = 0; params !== undefined && params !== null && i < params.namedChildCount; i++) {
1102
+ const p = params.namedChild(i);
1103
+ if (p?.type !== 'parameter')
1104
+ continue;
1105
+ if (p.childForFieldName('name')?.text === name &&
1106
+ p.childForFieldName('type')?.text === 'type') {
1107
+ return true;
1108
+ }
1109
+ }
1110
+ }
1111
+ fn = fn.parent;
1112
+ }
1113
+ return false;
1114
+ }
1115
+ /** The container a `@This()` in `typeNode`'s position names: the innermost
1116
+ * enclosing container's binding name, or the file-struct's name at file
1117
+ * level. Undefined outside any named container. */
1118
+ function zigThisTargetFor(typeNode, fileStructName) {
1119
+ let cur = typeNode.parent;
1120
+ while (cur !== null && cur !== undefined) {
1121
+ if (ZIG_CONTAINER_TYPES.has(cur.type))
1122
+ return zigContainerName(cur);
1123
+ if (cur.type === 'source_file')
1124
+ return fileStructName;
1125
+ cur = cur.parent;
1126
+ }
1127
+ return undefined;
1128
+ }
1129
+ export function emitZigScopeCaptures(sourceText, _filePath, cachedTree) {
1130
+ let tree = cachedTree;
1131
+ if (tree === undefined) {
1132
+ tree = parseSourceSafe(getZigParser(), sourceText, undefined, {
1133
+ bufferSize: getTreeSitterBufferSize(sourceText),
1134
+ });
1135
+ }
1136
+ const rawMatches = getZigScopeQuery().matches(tree.rootNode);
1137
+ const out = [];
1138
+ // Pre-pass: this file's `@import` bindings (`const counter =
1139
+ // @import("counter.zig")` → counter ↦ the string node), so a member alias
1140
+ // `const Counter = counter.Counter;` can be promoted to a named import of
1141
+ // `Counter` from `counter.zig` below. Aliases are collected in the same
1142
+ // pass so their plain-variable group is dropped (a local Const binding
1143
+ // would outrank the import binding it stands for).
1144
+ const importSources = new Map();
1145
+ const aliasDeclIds = new Set();
1146
+ // The `@import(…)` string nodes a BINDING rule (or the keyword-less
1147
+ // side-effect rule) matched, by node id, plus their texts. The catch-all
1148
+ // `@import.inline` rule matches those same builtins again; the id set
1149
+ // keeps a bound import from being doubled, the text set keeps a second
1150
+ // spelling of an already-imported file from adding a redundant edge.
1151
+ const claimedImportSourceIds = new Set();
1152
+ const importedSourceTexts = new Set();
1153
+ for (const m of rawMatches) {
1154
+ const byName = new Map(m.captures.map((c) => [c.name, c.node]));
1155
+ const importName = byName.get('import.name');
1156
+ const importSource = byName.get('import.source');
1157
+ const importStmt = byName.get('import.statement');
1158
+ // A binding-rule match whose `@import` is the declaration's TYPE
1159
+ // annotation binds nothing: leave the source unclaimed so the
1160
+ // `@import.inline` rule keeps the file edge.
1161
+ if (importSource !== undefined &&
1162
+ importStmt !== undefined &&
1163
+ isZigTypePositionImport(importStmt, importSource)) {
1164
+ continue;
1165
+ }
1166
+ if (importSource !== undefined &&
1167
+ (importStmt !== undefined ||
1168
+ byName.get('import.side-effect') !== undefined ||
1169
+ byName.get('import.wildcard') !== undefined)) {
1170
+ claimedImportSourceIds.add(importSource.id);
1171
+ importedSourceTexts.add(importSource.text);
1172
+ }
1173
+ if (importName !== undefined &&
1174
+ importSource !== undefined &&
1175
+ byName.get('import.imported') === undefined &&
1176
+ importStmt !== undefined &&
1177
+ importStmt.parent?.type === 'source_file' &&
1178
+ isZigKeywordDeclaration(importStmt)) {
1179
+ importSources.set(importName.text, importSource);
1180
+ }
1181
+ }
1182
+ for (const m of rawMatches) {
1183
+ const byName = new Map(m.captures.map((c) => [c.name, c.node]));
1184
+ const stmt = byName.get('alias.statement');
1185
+ const ns = byName.get('alias.namespace');
1186
+ // Only a ONE-level member (`const Counter = counter.Counter;`) is the
1187
+ // named-import fact. A deeper chain (`const chosen = lib.B.work;`) names a
1188
+ // member OF a member: promoting it to a named import of `work` from
1189
+ // `lib.zig` discarded the written owner `B`, and `findExportedDef` then
1190
+ // answered with the first `work` in the file — `A.work` (PR #1432 review,
1191
+ // 8.4). Those stay Consts and are rewritten at their use sites instead
1192
+ // (`collectZigDeepAliases`).
1193
+ if (stmt !== undefined &&
1194
+ ns !== undefined &&
1195
+ importSources.has(ns.text) &&
1196
+ zigMemberChainOf(stmt)?.members.length === 1) {
1197
+ aliasDeclIds.add(stmt.id);
1198
+ }
1199
+ }
1200
+ // Function-local `@import` bindings, keyed per enclosing callable (PR #1432
1201
+ // review, 8.9). Finalization flattens every import of a file onto its
1202
+ // Module scope, so two sibling fns each binding `const m = @import(…)` to a
1203
+ // different file became ONE `m → [a.zig, b.zig]` namespace bucket and both
1204
+ // `m.Thing{}` sites took the first target. The binding and every use of the
1205
+ // name inside that fn are rewritten to `m$<fn>` — a spelling no Zig
1206
+ // identifier can take — so each fn's handle is its own bucket and resolves
1207
+ // through its own lexical import (`rewriteZigFunctionLocalImportNames`).
1208
+ const fnLocalImports = new Map();
1209
+ for (const m of rawMatches) {
1210
+ const byName = new Map(m.captures.map((c) => [c.name, c.node]));
1211
+ const importName = byName.get('import.name');
1212
+ const importStmt = byName.get('import.statement');
1213
+ const importSource = byName.get('import.source');
1214
+ if (importName === undefined || importStmt === undefined || importSource === undefined) {
1215
+ continue;
1216
+ }
1217
+ if (!isZigKeywordDeclaration(importStmt) || isZigTypePositionImport(importStmt, importSource)) {
1218
+ continue;
1219
+ }
1220
+ const fn = zigEnclosingFunction(importStmt);
1221
+ if (fn === null)
1222
+ continue;
1223
+ let entry = fnLocalImports.get(fn.id);
1224
+ if (entry === undefined) {
1225
+ entry = { fn, names: new Map() };
1226
+ fnLocalImports.set(fn.id, entry);
1227
+ }
1228
+ if (!entry.names.has(importName.text)) {
1229
+ entry.names.set(importName.text, zigFunctionLocalImportKey(importName.text, fn, _filePath));
1230
+ }
1231
+ }
1232
+ // Deep member aliases — `const chosen = @import("lib.zig").B.work;`,
1233
+ // `const chosen2 = lib.B.work;`, `const Inner = nested.Outer.Inner;` (PR
1234
+ // #1432 review, 8.4): the owner path is kept and the alias's use sites are
1235
+ // rewritten to qualified references (`chosen()` → `lib.B` . `work`), which
1236
+ // the namespace chain walk resolves segment by segment.
1237
+ const deepAliases = collectZigDeepAliases(tree.rootNode, importSources, fnLocalImports);
1238
+ // File-struct (top-level fields): the file IS a type named after the file.
1239
+ // Emit a Class scope over the whole file (nested under the Module scope —
1240
+ // the equal-range Module/Class pair is the C# `namespace`-only-file shape
1241
+ // `canParentScope` admits) and a Struct def anchored on the same node, so
1242
+ // top-level fns/fields become its owned members (`populateClassOwnedMembers`)
1243
+ // and `self: *Page` / `page: *Page` resolve to a class-like def. Name
1244
+ // bindings of those members are hoisted back to the Module scope by
1245
+ // `zigBindingScopeFor` so `Page.init()` (namespace member) keeps working.
1246
+ const root = tree.rootNode;
1247
+ const fileStruct = isZigFileStruct(root);
1248
+ const fileStructName = fileStruct ? zigFileStructName(_filePath) : undefined;
1249
+ if (fileStruct && fileStructName !== undefined) {
1250
+ out.push({ '@scope.class': nodeToCapture('@scope.class', root) });
1251
+ out.push({
1252
+ '@declaration.struct': nodeToCapture('@declaration.struct', root),
1253
+ '@declaration.name': syntheticCapture('@declaration.name', root, fileStructName),
1254
+ });
1255
+ }
1256
+ // `@This()` aliases: alias name ↦ the container it names (file stem for the
1257
+ // file-struct, binding name for `const Self = @This();` inside a container).
1258
+ const thisAliases = collectZigThisAliases(root, fileStructName, _filePath);
1259
+ // F6: fn-local names per function node, for `zigCallReturnTypeOf`; module-
1260
+ // level value names per host node (file / container), for the value-vs-
1261
+ // namespace receiver rule (`zigHostValueNames`).
1262
+ const fnLocalNames = new Map();
1263
+ const hostValueNames = new Map();
1264
+ for (const m of rawMatches) {
1265
+ const grouped = {};
1266
+ const nodeMap = {};
1267
+ for (const c of m.captures) {
1268
+ const tag = '@' + c.name;
1269
+ if (tag.startsWith('@_'))
1270
+ continue; // skip anonymous predicate captures
1271
+ grouped[tag] = nodeToCapture(tag, c.node);
1272
+ nodeMap[tag] = c.node;
1273
+ }
1274
+ if (Object.keys(grouped).length === 0)
1275
+ continue;
1276
+ // `_ = @import("x.zig");` and any other keyword-less `<ident> =
1277
+ // @import(…)`: a statement (tree-sitter-zig reuses variable_declaration
1278
+ // for assignments), not a declaration. It references the file without
1279
+ // binding a name → side-effect import (file edge only). The query rule
1280
+ // cannot exclude the keyword-bearing shapes, so drop those here — they
1281
+ // are the binding rules' matches. A keyword-less shape never enters
1282
+ // `importSources`, so it cannot promote aliases.
1283
+ const sideEffectStmt = nodeMap['@import.side-effect'];
1284
+ if (sideEffectStmt !== undefined) {
1285
+ if (isZigKeywordDeclaration(sideEffectStmt))
1286
+ continue;
1287
+ out.push({
1288
+ '@import.side-effect': grouped['@import.side-effect'],
1289
+ '@import.source': grouped['@import.source'],
1290
+ });
1291
+ continue;
1292
+ }
1293
+ // `@import("…")` in expression position — a tuple element, a call
1294
+ // argument, a comparison operand, a member-call receiver, a chain deeper
1295
+ // than the binding rules follow. Skip the builtins a binding rule (or the
1296
+ // keyword-less side-effect rule) already owns; the rest are file
1297
+ // dependencies without a name. The receiver of a member call
1298
+ // (`try @import("dump.zig").root(...)`) is more: it is a namespace used
1299
+ // in place. Bind it as a namespace import whose local name IS the
1300
+ // builtin's own text — `@reference.receiver` on that call carries the
1301
+ // same text, so the shared Case-1 namespace-receiver lookup resolves
1302
+ // `root` in dump.zig exactly as it would for `const dump =
1303
+ // @import("dump.zig"); dump.root(...)`. Anything else is a side-effect
1304
+ // import (file edge only), emitted once per distinct source per file.
1305
+ const inlineImport = nodeMap['@import.inline'];
1306
+ if (inlineImport !== undefined) {
1307
+ const source = nodeMap['@import.source'];
1308
+ if (claimedImportSourceIds.has(source.id))
1309
+ continue;
1310
+ const sourceCapture = grouped['@import.source'];
1311
+ if (isZigInlineImportReceiver(inlineImport) || deepAliases.inlineRoots.has(inlineImport.id)) {
1312
+ const key = `receiver:${source.text}`;
1313
+ if (importedSourceTexts.has(key))
1314
+ continue;
1315
+ importedSourceTexts.add(key);
1316
+ out.push({
1317
+ '@import.statement': nodeToCapture('@import.statement', inlineImport),
1318
+ '@import.name': nodeToCapture('@import.name', inlineImport),
1319
+ '@import.source': sourceCapture,
1320
+ });
1321
+ continue;
1322
+ }
1323
+ if (importedSourceTexts.has(source.text))
1324
+ continue;
1325
+ importedSourceTexts.add(source.text);
1326
+ out.push({
1327
+ '@import.side-effect': nodeToCapture('@import.side-effect', inlineImport),
1328
+ '@import.source': sourceCapture,
1329
+ });
1330
+ continue;
1331
+ }
1332
+ // `const chosen = @import("lib.zig").B.work;` — the two-member import
1333
+ // rule matched a DEEP chain. Binding `chosen` as a named import of the
1334
+ // innermost member `work` lost the owner `B` (8.4); bind the module under
1335
+ // the builtin's own text instead — the same namespace binding a member-
1336
+ // call receiver gets — and let the rewritten use sites walk `B.work`.
1337
+ const deepImportStmt = nodeMap['@import.statement'];
1338
+ if (deepImportStmt !== undefined &&
1339
+ nodeMap['@import.imported'] !== undefined &&
1340
+ deepAliases.declIds.has(deepImportStmt.id)) {
1341
+ const importRoot = zigImportRootOf(deepImportStmt.namedChildren.filter((c) => c !== null).pop() ?? null);
1342
+ const source = nodeMap['@import.source'];
1343
+ if (importRoot !== null && source !== undefined) {
1344
+ const key = `receiver:${source.text}`;
1345
+ if (!importedSourceTexts.has(key)) {
1346
+ importedSourceTexts.add(key);
1347
+ out.push({
1348
+ '@import.statement': nodeToCapture('@import.statement', importRoot),
1349
+ '@import.name': nodeToCapture('@import.name', importRoot),
1350
+ '@import.source': grouped['@import.source'],
1351
+ });
1352
+ }
1353
+ }
1354
+ continue;
1355
+ }
1356
+ // Member aliases: promote to a named import when the object is one of
1357
+ // this file's @import bindings; otherwise the group is inert (the same
1358
+ // node is also matched by the plain-variable rule).
1359
+ const aliasStmt = nodeMap['@alias.statement'];
1360
+ if (aliasStmt !== undefined) {
1361
+ if (!aliasDeclIds.has(aliasStmt.id))
1362
+ continue;
1363
+ const source = importSources.get(nodeMap['@alias.namespace'].text);
1364
+ out.push({
1365
+ '@import.statement': nodeToCapture('@import.statement', aliasStmt),
1366
+ '@import.name': nodeToCapture('@import.name', nodeMap['@alias.name']),
1367
+ '@import.imported': nodeToCapture('@import.imported', nodeMap['@alias.member']),
1368
+ '@import.source': nodeToCapture('@import.source', source),
1369
+ ...republishMarker(aliasStmt),
1370
+ });
1371
+ continue;
1372
+ }
1373
+ // F7 — type aliases (`@type-binding.alias`, see the query): keep the group
1374
+ // only for a value that is a TYPE expression. An identifier / member chain
1375
+ // is kept as written (a value alias such as `const log = lp.log` binds a
1376
+ // type name that resolves to nothing, exactly like Rust's `let x = y`); a
1377
+ // call is kept only when its callee's last identifier is TitleCase —
1378
+ // Zig's naming convention for types, hence for type constructors
1379
+ // (`util.List(u8)`, `js.Bridge(T)`, `GenericIterator(K, V)`). That is a
1380
+ // heuristic and the only one here: the grammar cannot tell a type
1381
+ // constructor call from a value call, and a value call (`const t =
1382
+ // util.makeThing()`) is typed by the call-return rules — a second binding
1383
+ // for the same name would race on match order. A promoted namespace-
1384
+ // member alias (`const Counter = counter.Counter;`) is skipped: it is a
1385
+ // named import already, and the import binding carries the type.
1386
+ const aliasAnchor = nodeMap['@type-binding.alias'];
1387
+ if (aliasAnchor !== undefined) {
1388
+ // An @import binding (`const Stack = @import("counter.zig").Stack;`) is
1389
+ // an import, and a promoted member alias a named import: both already
1390
+ // carry the type through the import binding.
1391
+ // A deep alias off an inline import (`const Inner =
1392
+ // @import("n.zig").Outer.Inner;`) keeps its type binding: the written
1393
+ // path is the type (8.4).
1394
+ if (aliasDeclIds.has(aliasAnchor.id) ||
1395
+ (isZigContainerOrImportBinding(aliasAnchor) && !deepAliases.declIds.has(aliasAnchor.id))) {
1396
+ continue;
1397
+ }
1398
+ const value = nodeMap['@type-binding.type'];
1399
+ if (value === undefined)
1400
+ continue;
1401
+ // `var b: Counter = undefined;` — `undefined` / `null` are anonymous
1402
+ // nodes, so the last NAMED child the query anchored on is the `type:`
1403
+ // annotation, not a value. The annotation rule owns that binding.
1404
+ if (value.id === aliasAnchor.childForFieldName('type')?.id)
1405
+ continue;
1406
+ if (value.type === 'call_expression' && !isZigTypeConstructorCall(value))
1407
+ continue;
1408
+ // `.foo` / `.init` (enum / decl literal) parses as a field_expression
1409
+ // without an object — a value, never a type name.
1410
+ if (value.type === 'field_expression' && value.childForFieldName('object') === null)
1411
+ continue;
1412
+ }
1413
+ // …and the converse: a call-return binding for `const B = util.List(u8)`
1414
+ // would type `B` as `util` (the call's receiver) and, at equal strength,
1415
+ // race the alias binding above on match order. A type-constructor
1416
+ // instantiation is the alias rule's; drop the call-return group for it.
1417
+ const callReturnAnchor = nodeMap['@type-binding.call-return'];
1418
+ if (callReturnAnchor !== undefined) {
1419
+ const named = callReturnAnchor.namedChildren.filter((c) => c !== null);
1420
+ const value = named[named.length - 1];
1421
+ if (value?.type === 'call_expression' && isZigTypeConstructorCall(value))
1422
+ continue;
1423
+ }
1424
+ // `var x: @import("foo.zig").T = undefined;` — the binding rules match
1425
+ // the type-position `@import` too; that group binds nothing (the
1426
+ // variable group for `x` and the inline file edge cover it).
1427
+ if (nodeMap['@import.statement'] !== undefined &&
1428
+ nodeMap['@import.source'] !== undefined &&
1429
+ isZigTypePositionImport(nodeMap['@import.statement'], nodeMap['@import.source'])) {
1430
+ continue;
1431
+ }
1432
+ // Drop the plain-variable group for container/import bindings — their
1433
+ // dedicated rules already bind the name (as Struct/Enum/Union or import).
1434
+ // The query already requires a `const`/`var` keyword, so statement
1435
+ // assignments (`x = 5;`, `_ = expr;` — same node type, no keyword) never
1436
+ // mint phantom locals; `isZigKeywordDeclaration` is the belt to that
1437
+ // brace should the rule ever be loosened.
1438
+ const variableAnchor = nodeMap['@declaration.variable'];
1439
+ if (variableAnchor !== undefined &&
1440
+ (isZigContainerOrImportBinding(variableAnchor) ||
1441
+ aliasDeclIds.has(variableAnchor.id) ||
1442
+ !isZigKeywordDeclaration(variableAnchor) ||
1443
+ // `const Page = @This();` in a file-struct names the file's own type,
1444
+ // which the synthetic `@declaration.struct` below already binds.
1445
+ (fileStruct && isZigFileThisAlias(variableAnchor)))) {
1446
+ continue;
1447
+ }
1448
+ // F5 — `const page = self.page;` value aliases (`@type-binding.alias`)
1449
+ // share the query shape with `const Counter = counter.Counter;`, which is
1450
+ // a NAMED IMPORT of `Counter` (promoted above) — a type binding beside it
1451
+ // would make `Counter` look like an instance of the class it names. Drop
1452
+ // the alias binding for those declarations.
1453
+ const aliasBinding = nodeMap['@type-binding.alias'];
1454
+ if (aliasBinding !== undefined && aliasDeclIds.has(aliasBinding.id))
1455
+ continue;
1456
+ // F6 — value-inferred bindings: `const t = [try] <call>` (see
1457
+ // `zigCallReturnTypeOf`). The query pins every keyword declaration; here
1458
+ // the value decides whether it types anything, and how. The value capture
1459
+ // is scaffolding and never leaves this function.
1460
+ const valueNode = nodeMap['@type-binding.value'];
1461
+ if (valueNode !== undefined && nodeMap['@type-binding.call-return'] !== undefined) {
1462
+ delete grouped['@type-binding.value'];
1463
+ // `const x: T;` (extern) — the trailing child is the type, not a value.
1464
+ if (valueNode.id === nodeMap['@type-binding.call-return'].childForFieldName('type')?.id) {
1465
+ continue;
1466
+ }
1467
+ const inferred = zigCallReturnTypeOf(valueNode, fnLocalNames, hostValueNames);
1468
+ if (inferred === undefined)
1469
+ continue;
1470
+ grouped['@type-binding.type'] = syntheticCapture('@type-binding.type', valueNode, inferred.type);
1471
+ nodeMap['@type-binding.type'] = valueNode;
1472
+ if (inferred.structLiteral === true) {
1473
+ const anchor = grouped['@type-binding.call-return'];
1474
+ delete grouped['@type-binding.call-return'];
1475
+ grouped['@type-binding.constructor'] = { ...anchor, name: '@type-binding.constructor' };
1476
+ }
1477
+ else if (inferred.memberCall === true) {
1478
+ grouped['@type-binding.member-call-return'] = syntheticCapture('@type-binding.member-call-return', nodeMap['@type-binding.name'], 'true');
1479
+ }
1480
+ }
1481
+ // F6 — return-type bindings: `fn make() !*Thing` binds `make ↦ Thing`.
1482
+ // Only nominal returns bind (see `zigReturnTypeIsNominal`); a bare
1483
+ // `@This()` return names the enclosing container by name (the alias
1484
+ // rewrite below handles the `Self` spelling of the same thing).
1485
+ const returnAnchor = nodeMap['@type-binding.return'];
1486
+ if (returnAnchor !== undefined) {
1487
+ const retType = nodeMap['@type-binding.type'];
1488
+ if (retType === undefined || !zigReturnTypeIsNominal(retType))
1489
+ continue;
1490
+ if (retType.text.includes('@This()')) {
1491
+ const target = zigThisTargetFor(retType, fileStructName);
1492
+ if (target === undefined)
1493
+ continue;
1494
+ grouped['@type-binding.type'] = syntheticCapture('@type-binding.type', retType, retType.text.replace('@This()', target));
1495
+ }
1496
+ }
1497
+ // `@This()` aliases in type position — `self: *Self` in a container that
1498
+ // declares `const Self = @This();`, or `self: *SigHandler` in
1499
+ // `Sighandler.zig` (`const SigHandler = @This();`) — name the enclosing
1500
+ // container. Rewrite the type text to that container's name so the
1501
+ // receiver binding resolves to the Struct (Rust does the same for `Self`,
1502
+ // rust/captures.ts). Only the nominal part is rewritten; sigils stay.
1503
+ const typeNode = nodeMap['@type-binding.type'];
1504
+ if (typeNode !== undefined && thisAliases.size > 0) {
1505
+ const rewritten = rewriteZigThisAlias(typeNode, thisAliases, grouped['@type-binding.type']?.text);
1506
+ if (rewritten !== undefined) {
1507
+ grouped['@type-binding.type'] = syntheticCapture('@type-binding.type', typeNode, rewritten);
1508
+ }
1509
+ }
1510
+ // The receiver is the FIRST parameter when it is named `self` or typed as
1511
+ // the enclosing container — `zigReceiverParameter` is the single rule,
1512
+ // shared with the method extractor so the two phases agree. Tag it so
1513
+ // `interpretZigTypeBinding` sources the binding as `self`; a later `self`
1514
+ // parameter (legal Zig) is an ordinary parameter, not a receiver. The
1515
+ // synthetic captures sit on the name node (smaller than the `parameter`
1516
+ // anchor), so they never displace the anchor.
1517
+ const paramAnchor = nodeMap['@type-binding.parameter'];
1518
+ const paramName = nodeMap['@type-binding.name'];
1519
+ if (paramAnchor !== undefined &&
1520
+ paramName !== undefined &&
1521
+ paramAnchor.previousNamedSibling === null) {
1522
+ grouped['@type-binding.first-parameter'] = syntheticCapture('@type-binding.first-parameter', paramName, 'true');
1523
+ const fnNode = paramAnchor.parent?.parent;
1524
+ if (fnNode !== null &&
1525
+ fnNode !== undefined &&
1526
+ zigReceiverParameter(fnNode, _filePath)?.id === paramAnchor.id) {
1527
+ grouped['@type-binding.receiver'] = syntheticCapture('@type-binding.receiver', paramName, 'true');
1528
+ }
1529
+ }
1530
+ // Mark containers returned by a generic type constructor so
1531
+ // `zigBindingScopeFor` hoists their name to the module scope (beside the
1532
+ // Function def of the same name) instead of the fn body it sits in.
1533
+ for (const kind of ['@declaration.struct', '@declaration.union', '@declaration.enum']) {
1534
+ const containerAnchor = nodeMap[kind];
1535
+ const nameNode = nodeMap['@declaration.name'];
1536
+ if (containerAnchor !== undefined &&
1537
+ nameNode !== undefined &&
1538
+ zigTypeConstructorOf(containerAnchor) !== null) {
1539
+ grouped['@declaration.type-constructor'] = syntheticCapture('@declaration.type-constructor', nameNode, 'true');
1540
+ }
1541
+ // F8 — FUNCTION-LOCAL named container (`const R = struct {…}` inside a
1542
+ // fn body) and (8.5) CONTAINER-HOSTED named container (`pub const Item
1543
+ // = struct {…}` inside `A`): the def's qualified name is its identity
1544
+ // (`string$R`, `A.Item` — matching the graph node the structure phase
1545
+ // mints via `zigContainerName`), while the scope still binds the
1546
+ // spelling code uses (`R.get()`, `self: *R`, `Item{}`) — the Java
1547
+ // local / nested class split (`@declaration.binding-name`,
1548
+ // java/captures.ts).
1549
+ if (containerAnchor !== undefined && nameNode !== undefined) {
1550
+ const identity = zigContainerName(containerAnchor, _filePath);
1551
+ if (identity !== undefined && identity !== nameNode.text) {
1552
+ grouped['@declaration.binding-name'] = grouped['@declaration.name'];
1553
+ grouped['@declaration.name'] = syntheticCapture('@declaration.name', nameNode, identity);
1554
+ }
1555
+ }
1556
+ }
1557
+ // Relabel container-nested fns Function → Method (provider labelOverride
1558
+ // parity). The anchor capture name carries the kind, so rebuild it.
1559
+ const fnAnchor = nodeMap['@declaration.function'];
1560
+ if (fnAnchor !== undefined && isZigContainerMethod(fnAnchor)) {
1561
+ const fnCapture = grouped['@declaration.function'];
1562
+ delete grouped['@declaration.function'];
1563
+ grouped['@declaration.method'] = { ...fnCapture, name: '@declaration.method' };
1564
+ }
1565
+ // `pub const X = @import("x.zig").X;` at file scope republishes `X`.
1566
+ if (nodeMap['@import.statement'] !== undefined &&
1567
+ nodeMap['@import.imported'] !== undefined &&
1568
+ isZigPublishingImport(nodeMap['@import.statement'])) {
1569
+ Object.assign(grouped, republishMarker(nodeMap['@import.statement']));
1570
+ }
1571
+ out.push(grouped);
1572
+ // F5 — field types. `session: *Session,` declares the TYPE of the member
1573
+ // `session`; the compound resolver reads member types from the owning
1574
+ // container's Class scope (`typeOfMemberOnClass` →
1575
+ // `classScope.typeBindings.get('session')`), so without a binding there
1576
+ // `self.session.name()` / `self.counter.incr()` never resolve (Lightpanda:
1577
+ // 9 of 2803 such calls, 0.3 %). Synthesize a `@type-binding.field` group
1578
+ // per typed field (Go does the same in go/captures.ts; Rust/C++ capture it
1579
+ // in the query). Enum variants carry no type and get no binding. The
1580
+ // group's anchor is the type node — inside the container, never equal to
1581
+ // its range — so the extractor hosts it in the container's Class scope
1582
+ // (the file's Class scope for a file-struct); `zigBindingScopeFor` hoists
1583
+ // only declaration NAMES, never `@type-binding.*`. `?*Self` /
1584
+ // `?*Page` (a `@This()` alias) is rewritten to the container name exactly
1585
+ // like a parameter type; `normalizeZigTypeName` reduces `?*Session`,
1586
+ // `[]const Counter` to the nominal name and the extractor keeps the
1587
+ // written spelling as `declaredSpelling`.
1588
+ const fieldAnchor = nodeMap['@declaration.field'];
1589
+ const fieldType = nodeMap['@declaration.field-type'];
1590
+ const fieldName = nodeMap['@declaration.name'];
1591
+ if (fieldAnchor !== undefined &&
1592
+ fieldType !== undefined &&
1593
+ fieldName !== undefined &&
1594
+ // An inline anonymous container (`opts: struct { a: u32 },`) names no
1595
+ // type; its own Class scope already owns its members.
1596
+ !ZIG_CONTAINER_TYPES.has(fieldType.type)) {
1597
+ const rewritten = thisAliases.size > 0 ? rewriteZigThisAlias(fieldType, thisAliases) : undefined;
1598
+ out.push({
1599
+ '@type-binding.field': nodeToCapture('@type-binding.field', fieldType),
1600
+ '@type-binding.name': nodeToCapture('@type-binding.name', fieldName),
1601
+ '@type-binding.type': rewritten === undefined
1602
+ ? nodeToCapture('@type-binding.type', fieldType)
1603
+ : syntheticCapture('@type-binding.type', fieldType, rewritten),
1604
+ });
1605
+ }
1606
+ else if (fieldAnchor !== undefined &&
1607
+ fieldType === undefined &&
1608
+ fieldName !== undefined &&
1609
+ fieldName.text !== '_' &&
1610
+ fieldAnchor.parent?.type === 'enum_declaration') {
1611
+ // An enum VARIANT has no written type, but it has one: the enum itself.
1612
+ // `Operation.create_accounts.event_max()` — a method called on a variant
1613
+ // reached through the type — needs `Operation.create_accounts` typed as
1614
+ // `Operation` for the compound resolver to walk the field like
1615
+ // `self.session.name()`. tigerbeetle writes this shape 147 times
1616
+ // (`Operation.<variant>.event_max(…)`, `TestOperation.create.event_size(`).
1617
+ const enumName = zigContainerBindingName(fieldAnchor.parent, _filePath) ??
1618
+ zigContainerName(fieldAnchor.parent, _filePath);
1619
+ if (enumName !== undefined) {
1620
+ out.push({
1621
+ '@type-binding.field': nodeToCapture('@type-binding.field', fieldName),
1622
+ '@type-binding.name': nodeToCapture('@type-binding.name', fieldName),
1623
+ '@type-binding.type': syntheticCapture('@type-binding.type', fieldName, enumName),
1624
+ });
1625
+ }
1626
+ }
1627
+ // `const Page = @import("Page.zig")` binds BOTH the module (namespace:
1628
+ // `Page.init()`) and, when the target is a file-struct, the type it
1629
+ // declares (`page: *Page`, `var p: Page`). The type is exported from the
1630
+ // target under its file stem, so emit a second, NAMED import of that stem
1631
+ // beside the namespace import. For a target that is only a namespace the
1632
+ // named import finds no such export and binds nothing.
1633
+ const importStmt = nodeMap['@import.statement'];
1634
+ if (importStmt !== undefined &&
1635
+ isZigKeywordDeclaration(importStmt) &&
1636
+ nodeMap['@import.imported'] === undefined &&
1637
+ nodeMap['@import.name'] !== undefined &&
1638
+ nodeMap['@import.source'] !== undefined) {
1639
+ const source = nodeMap['@import.source'].text.replace(/^["']|["']$/g, '');
1640
+ if (source.endsWith('.zig')) {
1641
+ out.push({
1642
+ '@import.statement': grouped['@import.statement'],
1643
+ '@import.name': grouped['@import.name'],
1644
+ '@import.source': grouped['@import.source'],
1645
+ '@import.imported': syntheticCapture('@import.imported', nodeMap['@import.name'], zigFileStructName(source)),
1646
+ // `pub const Arena = @import("Arena.zig");` republishes the TYPE too:
1647
+ // `lp.Arena` in a third file is the Struct `Arena.zig` declares.
1648
+ ...republishMarker(importStmt),
1649
+ });
1650
+ }
1651
+ }
1652
+ }
1653
+ // A generic type constructor yields two module-scope defs of one name: the
1654
+ // Function `Stack` and the Struct `Stack` it returns. Import materialization
1655
+ // keeps the FIRST def of a name (finalize `indexExportsByName`), and query
1656
+ // order puts the fn (outer node) first — so `const Stack =
1657
+ // @import("x.zig").Stack;` bound the Function and `Stack(u8){}` typed
1658
+ // nothing. Emit the container def ahead of its constructor: the type is what
1659
+ // an importer instantiates, and the free call `Stack(u8)` still resolves
1660
+ // (a Struct is a valid call target — the constructor-reference path).
1661
+ for (let i = 0; i < out.length; i++) {
1662
+ const group = out[i];
1663
+ if (group['@declaration.type-constructor'] === undefined)
1664
+ continue;
1665
+ const anchor = group['@declaration.struct'] ?? group['@declaration.union'] ?? group['@declaration.enum'];
1666
+ if (anchor === undefined)
1667
+ continue;
1668
+ for (let j = 0; j < i; j++) {
1669
+ const fn = out[j]['@declaration.function'];
1670
+ if (fn === undefined)
1671
+ continue;
1672
+ if (fn.range.startLine < anchor.range.startLine ||
1673
+ (fn.range.startLine === anchor.range.startLine &&
1674
+ fn.range.startCol <= anchor.range.startCol)) {
1675
+ if (fn.range.endLine > anchor.range.endLine ||
1676
+ (fn.range.endLine === anchor.range.endLine && fn.range.endCol >= anchor.range.endCol)) {
1677
+ out.splice(j, 0, ...out.splice(i, 1));
1678
+ break;
1679
+ }
1680
+ }
1681
+ }
1682
+ }
1683
+ // F8 — ANONYMOUS containers (`std.sort.pdq(…, struct { fn lessThan … }
1684
+ // .lessThan)`, `const cmp = struct { fn lt … }.lt;`, `clamp: ?struct { min:
1685
+ // u32 }`): no query rule binds them, so their Class scope owned no def and
1686
+ // their fns were ownerless, colliding Methods. Synthesize the def the
1687
+ // structure phase mints (`zigContainerName` → `build$1`), anchored on the
1688
+ // container node so it shares the `@scope.class` range: the def joins that
1689
+ // scope's ownedDefs (`populateClassOwnedMembers` then qualifies its members
1690
+ // `build$1.lessThan` and stamps their ownerId) and the name auto-hoists to
1691
+ // the enclosing scope, where a `const Self = @This();` rewrite can find it.
1692
+ out.push(...synthesizeZigAnonymousContainerDeclarations(root, _filePath));
1693
+ // Result-location sites — `const a: Counter = .init(1);`, `return .init(3);`,
1694
+ // `const b: Counter = .{ .n = 2 };` (8.6): the call / construction the query
1695
+ // cannot see because the type is written on the LEFT.
1696
+ out.push(...synthesizeZigResultLocationReferences(root, thisAliases, fileStructName));
1697
+ out.push(...synthesizeCallableFlowCaptures(tree.rootNode, zigCallableCaptureOptions(fnLocalNames, hostValueNames)));
1698
+ // Use-site rewrites, in this order: a deep alias's receiver may itself name
1699
+ // a fn-local import (`const m = @import(…); const w = m.B.work;`), and the
1700
+ // second pass rewrites that name inside the receiver text it just minted.
1701
+ rewriteZigDeepAliasReferences(out, deepAliases.aliases);
1702
+ rewriteZigFunctionLocalImportNames(out, fnLocalImports);
1703
+ return stampZigStaticGating(out, tree.rootNode);
1704
+ }
1705
+ // ─── Use-site rewrites (8.4 / 8.9) and result-location sites (8.6) ────────────
1706
+ /** The unique spelling a function-local import binding gets: `m$<callable>`
1707
+ * — `m$f_sib_a`, `m$Reflect$string`, `m$test$L12`. `$` cannot appear in a
1708
+ * Zig identifier, so the key collides with nothing the source declares;
1709
+ * every non-word character of the callable's qualified name becomes `$` so
1710
+ * the key stays a single receiver segment (a `.` would split it). */
1711
+ function zigFunctionLocalImportKey(name, fn, filePath) {
1712
+ return `${name}$${zigCallableQualifiedName(fn, filePath).replace(/[^\w]/g, '$')}`;
1713
+ }
1714
+ /** The capture-side range of `node` (1-based lines, as `nodeToCapture`). */
1715
+ function zigNodeRange(node) {
1716
+ return {
1717
+ startLine: node.startPosition.row + 1,
1718
+ startCol: node.startPosition.column,
1719
+ endLine: node.endPosition.row + 1,
1720
+ endCol: node.endPosition.column,
1721
+ };
1722
+ }
1723
+ function zigRangeWithin(inner, outer) {
1724
+ const startsAfter = inner.startLine > outer.startLine ||
1725
+ (inner.startLine === outer.startLine && inner.startCol >= outer.startCol);
1726
+ const endsBefore = inner.endLine < outer.endLine ||
1727
+ (inner.endLine === outer.endLine && inner.endCol <= outer.endCol);
1728
+ return startsAfter && endsBefore;
1729
+ }
1730
+ /** Replace every bare identifier token `name` in `text` — outside string
1731
+ * literals, and not the member of a `.name` access or the tail of a
1732
+ * `@builtin` — with `replacement`. Zig forbids shadowing, so inside the
1733
+ * region a rewrite applies to, every such token is the same binding. */
1734
+ function zigReplaceIdentifier(text, name, replacement) {
1735
+ let out = '';
1736
+ let i = 0;
1737
+ let inString = false;
1738
+ while (i < text.length) {
1739
+ const ch = text[i];
1740
+ if (inString) {
1741
+ out += ch;
1742
+ if (ch === '\\') {
1743
+ out += text[i + 1] ?? '';
1744
+ i += 2;
1745
+ continue;
1746
+ }
1747
+ if (ch === '"')
1748
+ inString = false;
1749
+ i++;
1750
+ continue;
1751
+ }
1752
+ if (ch === '"') {
1753
+ inString = true;
1754
+ out += ch;
1755
+ i++;
1756
+ continue;
1757
+ }
1758
+ if (/[A-Za-z_]/.test(ch)) {
1759
+ let j = i;
1760
+ while (j < text.length && /\w/.test(text[j]))
1761
+ j++;
1762
+ const word = text.slice(i, j);
1763
+ const prev = i > 0 ? text[i - 1] : '';
1764
+ out += word === name && prev !== '.' && prev !== '@' ? replacement : word;
1765
+ i = j;
1766
+ continue;
1767
+ }
1768
+ out += ch;
1769
+ i++;
1770
+ }
1771
+ return out;
1772
+ }
1773
+ /** Every capture whose text can spell a receiver, a type, a bound name or a
1774
+ * callable-flow cell — the ones a fn-local import name can appear in. */
1775
+ const ZIG_NAME_BEARING_TAGS = [
1776
+ '@import.name',
1777
+ '@reference.receiver',
1778
+ '@reference.name',
1779
+ '@type-binding.type',
1780
+ '@callable-flow.target-name',
1781
+ '@callable-flow.target-qualified-name',
1782
+ '@callable-flow.receiver',
1783
+ '@callable-flow.source',
1784
+ '@callable-flow.destination',
1785
+ '@callable-flow.callee',
1786
+ '@callable-flow.direct-callee-name',
1787
+ ];
1788
+ /** 8.9 — rewrite a fn-local import's binding and its uses to its unique key
1789
+ * (`zigFunctionLocalImportKey`), within that fn's range only. */
1790
+ function rewriteZigFunctionLocalImportNames(out, fnLocalImports) {
1791
+ if (fnLocalImports.size === 0)
1792
+ return;
1793
+ for (const { fn, names } of fnLocalImports.values()) {
1794
+ const fnRange = zigNodeRange(fn);
1795
+ for (let i = 0; i < out.length; i++) {
1796
+ const group = out[i];
1797
+ let next;
1798
+ for (const tag of ZIG_NAME_BEARING_TAGS) {
1799
+ const cap = group[tag];
1800
+ if (cap === undefined || !zigRangeWithin(cap.range, fnRange))
1801
+ continue;
1802
+ let text = cap.text;
1803
+ for (const [name, key] of names)
1804
+ text = zigReplaceIdentifier(text, name, key);
1805
+ if (text === cap.text)
1806
+ continue;
1807
+ next ??= { ...group };
1808
+ next[tag] = { ...cap, text };
1809
+ }
1810
+ if (next !== undefined)
1811
+ out[i] = next;
1812
+ }
1813
+ }
1814
+ }
1815
+ /** The member chain a declaration's value spells — `lib.B.work` →
1816
+ * `{ root: lib, members: [B, work] }`; undefined for anything but a
1817
+ * `field_expression` chain rooted in an identifier or an `@import(…)`. */
1818
+ function zigMemberChainOf(decl) {
1819
+ const named = decl.namedChildren.filter((c) => c !== null);
1820
+ if (named.length !== 2 || named[0].type !== 'identifier')
1821
+ return undefined;
1822
+ let cur = named[1];
1823
+ const members = [];
1824
+ while (cur !== null && cur.type === 'field_expression') {
1825
+ const member = cur.childForFieldName('member');
1826
+ const object = cur.childForFieldName('object');
1827
+ if (member === null || object === null)
1828
+ return undefined; // `.init` literal
1829
+ members.unshift(member);
1830
+ cur = object;
1831
+ }
1832
+ if (cur === null || members.length === 0)
1833
+ return undefined;
1834
+ if (cur.type !== 'identifier' && !isZigImportBuiltin(cur))
1835
+ return undefined;
1836
+ return { root: cur, members };
1837
+ }
1838
+ /** 8.4 — every deep member alias in the tree whose root is a module handle:
1839
+ * a file-level `@import` binding of this file, a fn-local one (its key is
1840
+ * applied by the later rewrite), or an inline `@import(…)`. Returns the
1841
+ * aliases, the declaring nodes (so their import / alias groups are handled
1842
+ * as deep aliases) and the inline-import roots (bound as namespaces). */
1843
+ function collectZigDeepAliases(root, importSources, fnLocalImports) {
1844
+ const aliases = [];
1845
+ const declIds = new Set();
1846
+ const inlineRoots = new Set();
1847
+ const visit = (node) => {
1848
+ if (node.type === 'variable_declaration' && isZigKeywordDeclaration(node)) {
1849
+ const chain = zigMemberChainOf(node);
1850
+ if (chain !== undefined && chain.members.length >= 2) {
1851
+ const fn = zigEnclosingFunction(node);
1852
+ const isHandle = isZigImportBuiltin(chain.root) ||
1853
+ importSources.has(chain.root.text) ||
1854
+ (fn !== null && fnLocalImports.get(fn.id)?.names.has(chain.root.text) === true);
1855
+ if (isHandle) {
1856
+ const host = zigIdentityHost(node);
1857
+ const receiver = [chain.root.text, ...chain.members.slice(0, -1).map((m) => m.text)].join('.');
1858
+ aliases.push({
1859
+ name: node.namedChild(0).text,
1860
+ receiver,
1861
+ member: chain.members[chain.members.length - 1].text,
1862
+ range: host === null || host.type === 'source_file' ? null : zigNodeRange(host),
1863
+ });
1864
+ declIds.add(node.id);
1865
+ if (isZigImportBuiltin(chain.root))
1866
+ inlineRoots.add(chain.root.id);
1867
+ }
1868
+ }
1869
+ }
1870
+ for (let i = 0; i < node.namedChildCount; i++) {
1871
+ const child = node.namedChild(i);
1872
+ if (child !== null)
1873
+ visit(child);
1874
+ }
1875
+ };
1876
+ visit(root);
1877
+ return { aliases, declIds, inlineRoots };
1878
+ }
1879
+ /** 8.4 — turn a free call / bare construction of a deep alias (`chosen()`,
1880
+ * `Inner{}`) into the qualified reference it stands for (receiver `lib.B`,
1881
+ * member `work`), so it resolves through the alias's written owner path and
1882
+ * never through a same-named member elsewhere in the module. */
1883
+ function rewriteZigDeepAliasReferences(out, aliases) {
1884
+ if (aliases.length === 0)
1885
+ return;
1886
+ for (let i = 0; i < out.length; i++) {
1887
+ const group = out[i];
1888
+ const free = group['@reference.call.free'];
1889
+ const ctor = group['@reference.call.constructor'];
1890
+ if ((free === undefined && ctor === undefined) || group['@reference.receiver'] !== undefined) {
1891
+ continue;
1892
+ }
1893
+ const nameCap = group['@reference.name'];
1894
+ if (nameCap === undefined)
1895
+ continue;
1896
+ const alias = aliases.find((a) => a.name === nameCap.text && (a.range === null || zigRangeWithin(nameCap.range, a.range)));
1897
+ if (alias === undefined)
1898
+ continue;
1899
+ const next = { ...group };
1900
+ if (free !== undefined) {
1901
+ delete next['@reference.call.free'];
1902
+ next['@reference.call.member'] = { ...free, name: '@reference.call.member' };
1903
+ }
1904
+ next['@reference.receiver'] = {
1905
+ name: '@reference.receiver',
1906
+ range: nameCap.range,
1907
+ text: alias.receiver,
1908
+ };
1909
+ next['@reference.name'] = { ...nameCap, text: alias.member };
1910
+ out[i] = next;
1911
+ }
1912
+ }
1913
+ /** 8.6 — reference sites for RESULT-LOCATION expressions: a decl literal
1914
+ * `.init(…)` or an anonymous literal `.{…}` whose type comes from where the
1915
+ * value lands — a declared variable (`const a: Counter = .init(1);`), a
1916
+ * function's return (`fn make() Counter { return .init(3); }`), a container
1917
+ * field's default (`n: Counter = .init(0),`). The query cannot see these:
1918
+ * its member-call rule needs an object and its constructor rule a type
1919
+ * identifier, and the annotation only typed the VARIABLE — the `init` call
1920
+ * and the `Counter{…}` construction event were absent from the graph. Each
1921
+ * site is emitted with the expected type as its receiver (`Counter`,
1922
+ * `stdx.Thing`, `@This()` and `Self` rewritten to the container), so it
1923
+ * resolves exactly as `Counter.init(1)` / `Counter{…}` would — through the
1924
+ * class binding or the namespace chain, never by simple name workspace-wide.
1925
+ * Arguments (`f(.init(1))`) are out: the expected type is the callee's
1926
+ * parameter, which needs the resolved callee. */
1927
+ function synthesizeZigResultLocationReferences(root, thisAliases, fileStructName) {
1928
+ const out = [];
1929
+ const emit = (expected, value) => {
1930
+ const inner = zigUnwrapValue(value);
1931
+ let isDeclLiteral = false;
1932
+ let member = null;
1933
+ if (inner.type === 'call_expression') {
1934
+ const callee = inner.childForFieldName('function');
1935
+ if (callee?.type !== 'field_expression' || callee.childForFieldName('object') !== null)
1936
+ return;
1937
+ member = callee.childForFieldName('member');
1938
+ if (member === null)
1939
+ return;
1940
+ isDeclLiteral = true;
1941
+ }
1942
+ else if (inner.type !== 'anonymous_struct_initializer') {
1943
+ return;
1944
+ }
1945
+ let text = expected.text;
1946
+ if (text.includes('@This()')) {
1947
+ const target = zigThisTargetFor(expected, fileStructName);
1948
+ if (target === undefined)
1949
+ return;
1950
+ text = text.replace('@This()', target);
1951
+ }
1952
+ else {
1953
+ text = rewriteZigThisAlias(expected, thisAliases) ?? text;
1954
+ }
1955
+ const nominal = normalizeZigTypeName(text);
1956
+ // A builtin / primitive (`u32`, `void`, `anyerror`) constructs nothing.
1957
+ if (!/^[A-Z@]/.test(nominal) && !nominal.includes('.'))
1958
+ return;
1959
+ if (isDeclLiteral) {
1960
+ out.push({
1961
+ '@reference.call.member': nodeToCapture('@reference.call.member', inner),
1962
+ '@reference.receiver': syntheticCapture('@reference.receiver', inner, nominal),
1963
+ '@reference.name': nodeToCapture('@reference.name', member),
1964
+ });
1965
+ return;
1966
+ }
1967
+ const dot = zigLastTopLevelDot(nominal);
1968
+ out.push(dot === -1
1969
+ ? {
1970
+ '@reference.call.constructor': nodeToCapture('@reference.call.constructor', inner),
1971
+ '@reference.name': syntheticCapture('@reference.name', inner, nominal),
1972
+ }
1973
+ : {
1974
+ '@reference.call.constructor': nodeToCapture('@reference.call.constructor', inner),
1975
+ '@reference.receiver': syntheticCapture('@reference.receiver', inner, nominal.slice(0, dot)),
1976
+ '@reference.name': syntheticCapture('@reference.name', inner, nominal.slice(dot + 1)),
1977
+ });
1978
+ };
1979
+ const visit = (node) => {
1980
+ if (node.type === 'variable_declaration' && isZigKeywordDeclaration(node)) {
1981
+ const typeNode = node.childForFieldName('type');
1982
+ const named = node.namedChildren.filter((c) => c !== null);
1983
+ const last = named[named.length - 1];
1984
+ if (typeNode !== null && last !== undefined && last.id !== typeNode.id)
1985
+ emit(typeNode, last);
1986
+ }
1987
+ else if (node.type === 'return_expression') {
1988
+ const fn = zigEnclosingFunction(node);
1989
+ const ret = fn?.type === 'function_declaration' ? fn.childForFieldName('type') : null;
1990
+ const value = node.namedChild(0);
1991
+ if (ret !== null && ret !== undefined && value !== null && zigReturnTypeIsNominal(ret)) {
1992
+ emit(ret, value);
1993
+ }
1994
+ }
1995
+ else if (node.type === 'container_field') {
1996
+ const typeNode = node.childForFieldName('type');
1997
+ const nameNode = node.childForFieldName('name');
1998
+ const named = node.namedChildren.filter((c) => c !== null);
1999
+ const last = named[named.length - 1];
2000
+ if (typeNode !== null &&
2001
+ last !== undefined &&
2002
+ last.id !== typeNode.id &&
2003
+ last.id !== nameNode?.id) {
2004
+ emit(typeNode, last);
2005
+ }
2006
+ }
2007
+ for (let i = 0; i < node.namedChildCount; i++) {
2008
+ const child = node.namedChild(i);
2009
+ if (child !== null)
2010
+ visit(child);
2011
+ }
2012
+ };
2013
+ visit(root);
2014
+ return out;
2015
+ }
2016
+ /** Index of the last `.` at nesting depth 0 and outside string literals —
2017
+ * the one that separates `@import("a.zig").Thing` or `stdx.List(u8).Node`
2018
+ * into receiver and member. -1 when there is none. */
2019
+ function zigLastTopLevelDot(text) {
2020
+ let depth = 0;
2021
+ let inString = false;
2022
+ let last = -1;
2023
+ for (let i = 0; i < text.length; i++) {
2024
+ const ch = text[i];
2025
+ if (inString) {
2026
+ if (ch === '\\')
2027
+ i++;
2028
+ else if (ch === '"')
2029
+ inString = false;
2030
+ continue;
2031
+ }
2032
+ if (ch === '"')
2033
+ inString = true;
2034
+ else if (ch === '(' || ch === '[')
2035
+ depth++;
2036
+ else if (ch === ')' || ch === ']')
2037
+ depth--;
2038
+ else if (ch === '.' && depth === 0)
2039
+ last = i;
2040
+ }
2041
+ return last;
2042
+ }
2043
+ /** One `@declaration.<kind>` group per anonymous container in the tree —
2044
+ * see the call site in `emitZigScopeCaptures` (F8). Named after
2045
+ * `synthesizeJavaAnonymousClassDeclarations`, which does the same for
2046
+ * `new Runnable() {…}` bodies. */
2047
+ function synthesizeZigAnonymousContainerDeclarations(root, filePath) {
2048
+ const out = [];
2049
+ const visit = (node) => {
2050
+ if (ZIG_CONTAINER_TYPES.has(node.type) && zigContainerBindingName(node) === undefined) {
2051
+ const identity = zigContainerName(node, filePath);
2052
+ const label = zigContainerLabel(node);
2053
+ if (identity !== undefined && label !== undefined) {
2054
+ const tag = `@declaration.${label.toLowerCase()}`;
2055
+ out.push({
2056
+ [tag]: nodeToCapture(tag, node),
2057
+ '@declaration.name': syntheticCapture('@declaration.name', node, identity),
2058
+ '@declaration.is-synthetic': syntheticCapture('@declaration.is-synthetic', node, 'true'),
2059
+ });
2060
+ }
2061
+ }
2062
+ for (let i = 0; i < node.namedChildCount; i++) {
2063
+ const child = node.namedChild(i);
2064
+ if (child !== null)
2065
+ visit(child);
2066
+ }
2067
+ };
2068
+ visit(root);
2069
+ return out;
2070
+ }