@quolu/lattice 0.70.0 → 0.71.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (603) hide show
  1. package/bin/lattice.mjs +21 -0
  2. package/package.json +1 -1
  3. package/sensor/dist/bin/lattice-sensor.js +746 -323
  4. package/sensor/dist/bin/viewer-gate.d.ts +17 -0
  5. package/sensor/dist/bin/viewer-gate.d.ts.map +1 -0
  6. package/sensor/dist/bin/viewer-gate.js +31 -0
  7. package/sensor/dist/bin/viewer-gate.js.map +1 -0
  8. package/sensor/dist/context/formatter.d.ts +7 -1
  9. package/sensor/dist/context/formatter.d.ts.map +1 -1
  10. package/sensor/dist/context/formatter.js +12 -6
  11. package/sensor/dist/context/formatter.js.map +1 -1
  12. package/sensor/dist/context/index.d.ts +12 -0
  13. package/sensor/dist/context/index.d.ts.map +1 -1
  14. package/sensor/dist/context/index.js +103 -16
  15. package/sensor/dist/context/index.js.map +1 -1
  16. package/sensor/dist/db/index.d.ts +23 -2
  17. package/sensor/dist/db/index.d.ts.map +1 -1
  18. package/sensor/dist/db/index.js +109 -31
  19. package/sensor/dist/db/index.js.map +1 -1
  20. package/sensor/dist/db/migrations.d.ts +1 -1
  21. package/sensor/dist/db/migrations.d.ts.map +1 -1
  22. package/sensor/dist/db/migrations.js +68 -1
  23. package/sensor/dist/db/migrations.js.map +1 -1
  24. package/sensor/dist/db/queries.d.ts +583 -6
  25. package/sensor/dist/db/queries.d.ts.map +1 -1
  26. package/sensor/dist/db/queries.js +1425 -57
  27. package/sensor/dist/db/queries.js.map +1 -1
  28. package/sensor/dist/db/schema.sql +27 -4
  29. package/sensor/dist/db/sqlite-adapter.d.ts +2 -0
  30. package/sensor/dist/db/sqlite-adapter.d.ts.map +1 -1
  31. package/sensor/dist/db/sqlite-adapter.js +56 -7
  32. package/sensor/dist/db/sqlite-adapter.js.map +1 -1
  33. package/sensor/dist/db/synthesis-stage.d.ts +12 -0
  34. package/sensor/dist/db/synthesis-stage.d.ts.map +1 -0
  35. package/sensor/dist/db/synthesis-stage.js +93 -0
  36. package/sensor/dist/db/synthesis-stage.js.map +1 -0
  37. package/sensor/dist/db/wal-valve.d.ts +32 -12
  38. package/sensor/dist/db/wal-valve.d.ts.map +1 -1
  39. package/sensor/dist/db/wal-valve.js +65 -22
  40. package/sensor/dist/db/wal-valve.js.map +1 -1
  41. package/sensor/dist/db/wsl-shared-index.d.ts +53 -0
  42. package/sensor/dist/db/wsl-shared-index.d.ts.map +1 -0
  43. package/sensor/dist/db/wsl-shared-index.js +121 -0
  44. package/sensor/dist/db/wsl-shared-index.js.map +1 -0
  45. package/sensor/dist/directory.d.ts +126 -2
  46. package/sensor/dist/directory.d.ts.map +1 -1
  47. package/sensor/dist/directory.js +285 -16
  48. package/sensor/dist/directory.js.map +1 -1
  49. package/sensor/dist/errors.d.ts +14 -0
  50. package/sensor/dist/errors.d.ts.map +1 -1
  51. package/sensor/dist/errors.js +16 -1
  52. package/sensor/dist/errors.js.map +1 -1
  53. package/sensor/dist/extraction/cfml-extractor.d.ts +1 -0
  54. package/sensor/dist/extraction/cfml-extractor.d.ts.map +1 -1
  55. package/sensor/dist/extraction/cfml-extractor.js +15 -3
  56. package/sensor/dist/extraction/cfml-extractor.js.map +1 -1
  57. package/sensor/dist/extraction/dfm-extractor.d.ts.map +1 -1
  58. package/sensor/dist/extraction/dfm-extractor.js +14 -10
  59. package/sensor/dist/extraction/dfm-extractor.js.map +1 -1
  60. package/sensor/dist/extraction/extraction-version.d.ts +1 -1
  61. package/sensor/dist/extraction/extraction-version.d.ts.map +1 -1
  62. package/sensor/dist/extraction/extraction-version.js +3 -1
  63. package/sensor/dist/extraction/extraction-version.js.map +1 -1
  64. package/sensor/dist/extraction/function-ref.d.ts +6 -4
  65. package/sensor/dist/extraction/function-ref.d.ts.map +1 -1
  66. package/sensor/dist/extraction/function-ref.js +35 -14
  67. package/sensor/dist/extraction/function-ref.js.map +1 -1
  68. package/sensor/dist/extraction/generated-detection.d.ts +48 -12
  69. package/sensor/dist/extraction/generated-detection.d.ts.map +1 -1
  70. package/sensor/dist/extraction/generated-detection.js +176 -11
  71. package/sensor/dist/extraction/generated-detection.js.map +1 -1
  72. package/sensor/dist/extraction/grammars.d.ts +38 -0
  73. package/sensor/dist/extraction/grammars.d.ts.map +1 -1
  74. package/sensor/dist/extraction/grammars.js +115 -3
  75. package/sensor/dist/extraction/grammars.js.map +1 -1
  76. package/sensor/dist/extraction/index.d.ts +218 -14
  77. package/sensor/dist/extraction/index.d.ts.map +1 -1
  78. package/sensor/dist/extraction/index.js +887 -175
  79. package/sensor/dist/extraction/index.js.map +1 -1
  80. package/sensor/dist/extraction/languages/c-cpp.d.ts +58 -0
  81. package/sensor/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  82. package/sensor/dist/extraction/languages/c-cpp.js +310 -19
  83. package/sensor/dist/extraction/languages/c-cpp.js.map +1 -1
  84. package/sensor/dist/extraction/languages/dart.d.ts.map +1 -1
  85. package/sensor/dist/extraction/languages/dart.js +8 -3
  86. package/sensor/dist/extraction/languages/dart.js.map +1 -1
  87. package/sensor/dist/extraction/languages/erlang.d.ts.map +1 -1
  88. package/sensor/dist/extraction/languages/erlang.js +58 -22
  89. package/sensor/dist/extraction/languages/erlang.js.map +1 -1
  90. package/sensor/dist/extraction/languages/javascript.js +1 -1
  91. package/sensor/dist/extraction/languages/javascript.js.map +1 -1
  92. package/sensor/dist/extraction/languages/kotlin.d.ts.map +1 -1
  93. package/sensor/dist/extraction/languages/kotlin.js +169 -36
  94. package/sensor/dist/extraction/languages/kotlin.js.map +1 -1
  95. package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
  96. package/sensor/dist/extraction/languages/lua.js +4 -1
  97. package/sensor/dist/extraction/languages/lua.js.map +1 -1
  98. package/sensor/dist/extraction/languages/objc.d.ts.map +1 -1
  99. package/sensor/dist/extraction/languages/objc.js +4 -0
  100. package/sensor/dist/extraction/languages/objc.js.map +1 -1
  101. package/sensor/dist/extraction/languages/python.d.ts.map +1 -1
  102. package/sensor/dist/extraction/languages/python.js +71 -0
  103. package/sensor/dist/extraction/languages/python.js.map +1 -1
  104. package/sensor/dist/extraction/languages/rust.d.ts +20 -0
  105. package/sensor/dist/extraction/languages/rust.d.ts.map +1 -1
  106. package/sensor/dist/extraction/languages/rust.js +50 -20
  107. package/sensor/dist/extraction/languages/rust.js.map +1 -1
  108. package/sensor/dist/extraction/languages/scala.d.ts.map +1 -1
  109. package/sensor/dist/extraction/languages/scala.js +18 -3
  110. package/sensor/dist/extraction/languages/scala.js.map +1 -1
  111. package/sensor/dist/extraction/languages/swift.d.ts +15 -0
  112. package/sensor/dist/extraction/languages/swift.d.ts.map +1 -1
  113. package/sensor/dist/extraction/languages/swift.js +50 -0
  114. package/sensor/dist/extraction/languages/swift.js.map +1 -1
  115. package/sensor/dist/extraction/languages/typescript.d.ts.map +1 -1
  116. package/sensor/dist/extraction/languages/typescript.js +9 -2
  117. package/sensor/dist/extraction/languages/typescript.js.map +1 -1
  118. package/sensor/dist/extraction/liquid-extractor.d.ts +18 -0
  119. package/sensor/dist/extraction/liquid-extractor.d.ts.map +1 -1
  120. package/sensor/dist/extraction/liquid-extractor.js +98 -22
  121. package/sensor/dist/extraction/liquid-extractor.js.map +1 -1
  122. package/sensor/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  123. package/sensor/dist/extraction/mybatis-extractor.js +6 -4
  124. package/sensor/dist/extraction/mybatis-extractor.js.map +1 -1
  125. package/sensor/dist/extraction/parse-pool.d.ts +7 -0
  126. package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
  127. package/sensor/dist/extraction/parse-pool.js +20 -1
  128. package/sensor/dist/extraction/parse-pool.js.map +1 -1
  129. package/sensor/dist/extraction/syntax-tokens.d.ts +103 -0
  130. package/sensor/dist/extraction/syntax-tokens.d.ts.map +1 -0
  131. package/sensor/dist/extraction/syntax-tokens.js +381 -0
  132. package/sensor/dist/extraction/syntax-tokens.js.map +1 -0
  133. package/sensor/dist/extraction/tree-sitter-helpers.d.ts +5 -0
  134. package/sensor/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  135. package/sensor/dist/extraction/tree-sitter-helpers.js +17 -0
  136. package/sensor/dist/extraction/tree-sitter-helpers.js.map +1 -1
  137. package/sensor/dist/extraction/tree-sitter-types.d.ts +31 -3
  138. package/sensor/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  139. package/sensor/dist/extraction/tree-sitter.d.ts +101 -18
  140. package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
  141. package/sensor/dist/extraction/tree-sitter.js +928 -134
  142. package/sensor/dist/extraction/tree-sitter.js.map +1 -1
  143. package/sensor/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  144. package/sensor/dist/file-limits.d.ts +40 -0
  145. package/sensor/dist/file-limits.d.ts.map +1 -0
  146. package/sensor/dist/file-limits.js +147 -0
  147. package/sensor/dist/file-limits.js.map +1 -0
  148. package/sensor/dist/graph/branch-guards.d.ts +265 -0
  149. package/sensor/dist/graph/branch-guards.d.ts.map +1 -0
  150. package/sensor/dist/graph/branch-guards.js +2325 -0
  151. package/sensor/dist/graph/branch-guards.js.map +1 -0
  152. package/sensor/dist/graph/dead-code.d.ts +251 -0
  153. package/sensor/dist/graph/dead-code.d.ts.map +1 -0
  154. package/sensor/dist/graph/dead-code.js +776 -0
  155. package/sensor/dist/graph/dead-code.js.map +1 -0
  156. package/sensor/dist/graph/dynamic-boundary-report.d.ts +121 -0
  157. package/sensor/dist/graph/dynamic-boundary-report.d.ts.map +1 -0
  158. package/sensor/dist/graph/dynamic-boundary-report.js +283 -0
  159. package/sensor/dist/graph/dynamic-boundary-report.js.map +1 -0
  160. package/sensor/dist/graph/index.d.ts +4 -0
  161. package/sensor/dist/graph/index.d.ts.map +1 -1
  162. package/sensor/dist/graph/index.js +16 -1
  163. package/sensor/dist/graph/index.js.map +1 -1
  164. package/sensor/dist/graph/named-symbol-flow.d.ts +125 -0
  165. package/sensor/dist/graph/named-symbol-flow.d.ts.map +1 -0
  166. package/sensor/dist/graph/named-symbol-flow.js +552 -0
  167. package/sensor/dist/graph/named-symbol-flow.js.map +1 -0
  168. package/sensor/dist/graph/queries.d.ts.map +1 -1
  169. package/sensor/dist/graph/queries.js +2 -0
  170. package/sensor/dist/graph/queries.js.map +1 -1
  171. package/sensor/dist/graph/symbol-lookup.d.ts +83 -0
  172. package/sensor/dist/graph/symbol-lookup.d.ts.map +1 -0
  173. package/sensor/dist/graph/symbol-lookup.js +181 -0
  174. package/sensor/dist/graph/symbol-lookup.js.map +1 -0
  175. package/sensor/dist/graph/traversal.d.ts +9 -0
  176. package/sensor/dist/graph/traversal.d.ts.map +1 -1
  177. package/sensor/dist/graph/traversal.js +62 -36
  178. package/sensor/dist/graph/traversal.js.map +1 -1
  179. package/sensor/dist/graph/type-hierarchy.d.ts +155 -0
  180. package/sensor/dist/graph/type-hierarchy.d.ts.map +1 -0
  181. package/sensor/dist/graph/type-hierarchy.js +382 -0
  182. package/sensor/dist/graph/type-hierarchy.js.map +1 -0
  183. package/sensor/dist/index.d.ts +272 -10
  184. package/sensor/dist/index.d.ts.map +1 -1
  185. package/sensor/dist/index.js +510 -34
  186. package/sensor/dist/index.js.map +1 -1
  187. package/sensor/dist/mcp/answer-freshness.d.ts +10 -0
  188. package/sensor/dist/mcp/answer-freshness.d.ts.map +1 -0
  189. package/sensor/dist/mcp/answer-freshness.js +68 -0
  190. package/sensor/dist/mcp/answer-freshness.js.map +1 -0
  191. package/sensor/dist/mcp/daemon-manager.d.ts +1 -1
  192. package/sensor/dist/mcp/daemon-manager.d.ts.map +1 -1
  193. package/sensor/dist/mcp/daemon-manager.js +17 -1
  194. package/sensor/dist/mcp/daemon-manager.js.map +1 -1
  195. package/sensor/dist/mcp/daemon-paths.d.ts +8 -0
  196. package/sensor/dist/mcp/daemon-paths.d.ts.map +1 -1
  197. package/sensor/dist/mcp/daemon-paths.js +65 -2
  198. package/sensor/dist/mcp/daemon-paths.js.map +1 -1
  199. package/sensor/dist/mcp/daemon-registry.d.ts +17 -4
  200. package/sensor/dist/mcp/daemon-registry.d.ts.map +1 -1
  201. package/sensor/dist/mcp/daemon-registry.js +133 -23
  202. package/sensor/dist/mcp/daemon-registry.js.map +1 -1
  203. package/sensor/dist/mcp/daemon.d.ts +26 -13
  204. package/sensor/dist/mcp/daemon.d.ts.map +1 -1
  205. package/sensor/dist/mcp/daemon.js +126 -32
  206. package/sensor/dist/mcp/daemon.js.map +1 -1
  207. package/sensor/dist/mcp/dynamic-boundaries.d.ts +1 -10
  208. package/sensor/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  209. package/sensor/dist/mcp/dynamic-boundaries.js +46 -42
  210. package/sensor/dist/mcp/dynamic-boundaries.js.map +1 -1
  211. package/sensor/dist/mcp/engine.d.ts +31 -9
  212. package/sensor/dist/mcp/engine.d.ts.map +1 -1
  213. package/sensor/dist/mcp/engine.js +190 -79
  214. package/sensor/dist/mcp/engine.js.map +1 -1
  215. package/sensor/dist/mcp/explore-dedup.d.ts +140 -0
  216. package/sensor/dist/mcp/explore-dedup.d.ts.map +1 -0
  217. package/sensor/dist/mcp/explore-dedup.js +239 -0
  218. package/sensor/dist/mcp/explore-dedup.js.map +1 -0
  219. package/sensor/dist/mcp/explore-diagnostics.d.ts +289 -0
  220. package/sensor/dist/mcp/explore-diagnostics.d.ts.map +1 -0
  221. package/sensor/dist/mcp/explore-diagnostics.js +558 -0
  222. package/sensor/dist/mcp/explore-diagnostics.js.map +1 -0
  223. package/sensor/dist/mcp/explore-session-state.d.ts +217 -0
  224. package/sensor/dist/mcp/explore-session-state.d.ts.map +1 -0
  225. package/sensor/dist/mcp/explore-session-state.js +322 -0
  226. package/sensor/dist/mcp/explore-session-state.js.map +1 -0
  227. package/sensor/dist/mcp/index-freshness-worker.d.ts +2 -0
  228. package/sensor/dist/mcp/index-freshness-worker.d.ts.map +1 -0
  229. package/sensor/dist/mcp/index-freshness-worker.js +30 -0
  230. package/sensor/dist/mcp/index-freshness-worker.js.map +1 -0
  231. package/sensor/dist/mcp/index-freshness.d.ts +7 -0
  232. package/sensor/dist/mcp/index-freshness.d.ts.map +1 -0
  233. package/sensor/dist/mcp/index-freshness.js +94 -0
  234. package/sensor/dist/mcp/index-freshness.js.map +1 -0
  235. package/sensor/dist/mcp/index.d.ts +3 -1
  236. package/sensor/dist/mcp/index.d.ts.map +1 -1
  237. package/sensor/dist/mcp/index.js +109 -12
  238. package/sensor/dist/mcp/index.js.map +1 -1
  239. package/sensor/dist/mcp/liveness-watchdog.d.ts +4 -1
  240. package/sensor/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  241. package/sensor/dist/mcp/liveness-watchdog.js +11 -2
  242. package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
  243. package/sensor/dist/mcp/project-lifecycle.d.ts +9 -0
  244. package/sensor/dist/mcp/project-lifecycle.d.ts.map +1 -0
  245. package/sensor/dist/mcp/project-lifecycle.js +182 -0
  246. package/sensor/dist/mcp/project-lifecycle.js.map +1 -0
  247. package/sensor/dist/mcp/proxy.d.ts.map +1 -1
  248. package/sensor/dist/mcp/proxy.js +15 -1
  249. package/sensor/dist/mcp/proxy.js.map +1 -1
  250. package/sensor/dist/mcp/query-pool.d.ts +2 -2
  251. package/sensor/dist/mcp/query-pool.d.ts.map +1 -1
  252. package/sensor/dist/mcp/query-pool.js +9 -7
  253. package/sensor/dist/mcp/query-pool.js.map +1 -1
  254. package/sensor/dist/mcp/query-worker.js +2 -2
  255. package/sensor/dist/mcp/query-worker.js.map +1 -1
  256. package/sensor/dist/mcp/server-instructions.d.ts +2 -2
  257. package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
  258. package/sensor/dist/mcp/server-instructions.js +13 -6
  259. package/sensor/dist/mcp/server-instructions.js.map +1 -1
  260. package/sensor/dist/mcp/session.d.ts +16 -0
  261. package/sensor/dist/mcp/session.d.ts.map +1 -1
  262. package/sensor/dist/mcp/session.js +36 -13
  263. package/sensor/dist/mcp/session.js.map +1 -1
  264. package/sensor/dist/mcp/startup-handshake.d.ts +3 -1
  265. package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
  266. package/sensor/dist/mcp/startup-handshake.js +10 -2
  267. package/sensor/dist/mcp/startup-handshake.js.map +1 -1
  268. package/sensor/dist/mcp/tools.d.ts +496 -38
  269. package/sensor/dist/mcp/tools.d.ts.map +1 -1
  270. package/sensor/dist/mcp/tools.js +4021 -772
  271. package/sensor/dist/mcp/tools.js.map +1 -1
  272. package/sensor/dist/mcp/writer-lock.d.ts +61 -0
  273. package/sensor/dist/mcp/writer-lock.d.ts.map +1 -0
  274. package/sensor/dist/mcp/writer-lock.js +260 -0
  275. package/sensor/dist/mcp/writer-lock.js.map +1 -0
  276. package/sensor/dist/project-config.d.ts +23 -0
  277. package/sensor/dist/project-config.d.ts.map +1 -1
  278. package/sensor/dist/project-config.js +39 -2
  279. package/sensor/dist/project-config.js.map +1 -1
  280. package/sensor/dist/resolution/alias-binding.d.ts +56 -0
  281. package/sensor/dist/resolution/alias-binding.d.ts.map +1 -0
  282. package/sensor/dist/resolution/alias-binding.js +127 -0
  283. package/sensor/dist/resolution/alias-binding.js.map +1 -0
  284. package/sensor/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  285. package/sensor/dist/resolution/c-fnptr-synthesizer.js +21 -19
  286. package/sensor/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  287. package/sensor/dist/resolution/callback-synthesizer.d.ts +23 -1
  288. package/sensor/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  289. package/sensor/dist/resolution/callback-synthesizer.js +356 -155
  290. package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
  291. package/sensor/dist/resolution/cpp-constructor.d.ts +4 -0
  292. package/sensor/dist/resolution/cpp-constructor.d.ts.map +1 -0
  293. package/sensor/dist/resolution/cpp-constructor.js +123 -0
  294. package/sensor/dist/resolution/cpp-constructor.js.map +1 -0
  295. package/sensor/dist/resolution/cpp-macro-visibility.d.ts +12 -0
  296. package/sensor/dist/resolution/cpp-macro-visibility.d.ts.map +1 -0
  297. package/sensor/dist/resolution/cpp-macro-visibility.js +342 -0
  298. package/sensor/dist/resolution/cpp-macro-visibility.js.map +1 -0
  299. package/sensor/dist/resolution/expo-router-synthesizer.d.ts +32 -0
  300. package/sensor/dist/resolution/expo-router-synthesizer.d.ts.map +1 -0
  301. package/sensor/dist/resolution/expo-router-synthesizer.js +189 -0
  302. package/sensor/dist/resolution/expo-router-synthesizer.js.map +1 -0
  303. package/sensor/dist/resolution/frameworks/csharp.d.ts.map +1 -1
  304. package/sensor/dist/resolution/frameworks/csharp.js +96 -2
  305. package/sensor/dist/resolution/frameworks/csharp.js.map +1 -1
  306. package/sensor/dist/resolution/frameworks/expo-router.d.ts +189 -0
  307. package/sensor/dist/resolution/frameworks/expo-router.d.ts.map +1 -0
  308. package/sensor/dist/resolution/frameworks/expo-router.js +785 -0
  309. package/sensor/dist/resolution/frameworks/expo-router.js.map +1 -0
  310. package/sensor/dist/resolution/frameworks/express.d.ts.map +1 -1
  311. package/sensor/dist/resolution/frameworks/express.js +251 -16
  312. package/sensor/dist/resolution/frameworks/express.js.map +1 -1
  313. package/sensor/dist/resolution/frameworks/index.d.ts +5 -0
  314. package/sensor/dist/resolution/frameworks/index.d.ts.map +1 -1
  315. package/sensor/dist/resolution/frameworks/index.js +29 -1
  316. package/sensor/dist/resolution/frameworks/index.js.map +1 -1
  317. package/sensor/dist/resolution/frameworks/java.d.ts.map +1 -1
  318. package/sensor/dist/resolution/frameworks/java.js +79 -47
  319. package/sensor/dist/resolution/frameworks/java.js.map +1 -1
  320. package/sensor/dist/resolution/frameworks/laravel.d.ts.map +1 -1
  321. package/sensor/dist/resolution/frameworks/laravel.js +294 -4
  322. package/sensor/dist/resolution/frameworks/laravel.js.map +1 -1
  323. package/sensor/dist/resolution/frameworks/nestjs.d.ts.map +1 -1
  324. package/sensor/dist/resolution/frameworks/nestjs.js +6 -14
  325. package/sensor/dist/resolution/frameworks/nestjs.js.map +1 -1
  326. package/sensor/dist/resolution/frameworks/nextjs.d.ts +76 -0
  327. package/sensor/dist/resolution/frameworks/nextjs.d.ts.map +1 -0
  328. package/sensor/dist/resolution/frameworks/nextjs.js +328 -0
  329. package/sensor/dist/resolution/frameworks/nextjs.js.map +1 -0
  330. package/sensor/dist/resolution/frameworks/object-literal.d.ts +32 -0
  331. package/sensor/dist/resolution/frameworks/object-literal.d.ts.map +1 -0
  332. package/sensor/dist/resolution/frameworks/object-literal.js +139 -0
  333. package/sensor/dist/resolution/frameworks/object-literal.js.map +1 -0
  334. package/sensor/dist/resolution/frameworks/package-deps.d.ts +14 -0
  335. package/sensor/dist/resolution/frameworks/package-deps.d.ts.map +1 -0
  336. package/sensor/dist/resolution/frameworks/package-deps.js +78 -0
  337. package/sensor/dist/resolution/frameworks/package-deps.js.map +1 -0
  338. package/sensor/dist/resolution/frameworks/python.d.ts.map +1 -1
  339. package/sensor/dist/resolution/frameworks/python.js +166 -0
  340. package/sensor/dist/resolution/frameworks/python.js.map +1 -1
  341. package/sensor/dist/resolution/frameworks/react-native.d.ts +26 -0
  342. package/sensor/dist/resolution/frameworks/react-native.d.ts.map +1 -1
  343. package/sensor/dist/resolution/frameworks/react-native.js +153 -14
  344. package/sensor/dist/resolution/frameworks/react-native.js.map +1 -1
  345. package/sensor/dist/resolution/frameworks/react-router.d.ts +46 -0
  346. package/sensor/dist/resolution/frameworks/react-router.d.ts.map +1 -0
  347. package/sensor/dist/resolution/frameworks/react-router.js +187 -0
  348. package/sensor/dist/resolution/frameworks/react-router.js.map +1 -0
  349. package/sensor/dist/resolution/frameworks/react.d.ts +2 -1
  350. package/sensor/dist/resolution/frameworks/react.d.ts.map +1 -1
  351. package/sensor/dist/resolution/frameworks/react.js +188 -139
  352. package/sensor/dist/resolution/frameworks/react.js.map +1 -1
  353. package/sensor/dist/resolution/frameworks/svelte.d.ts.map +1 -1
  354. package/sensor/dist/resolution/frameworks/svelte.js +6 -1
  355. package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
  356. package/sensor/dist/resolution/frameworks/sveltekit-router.d.ts +36 -0
  357. package/sensor/dist/resolution/frameworks/sveltekit-router.d.ts.map +1 -0
  358. package/sensor/dist/resolution/frameworks/sveltekit-router.js +156 -0
  359. package/sensor/dist/resolution/frameworks/sveltekit-router.js.map +1 -0
  360. package/sensor/dist/resolution/frameworks/swift.d.ts.map +1 -1
  361. package/sensor/dist/resolution/frameworks/swift.js +140 -99
  362. package/sensor/dist/resolution/frameworks/swift.js.map +1 -1
  363. package/sensor/dist/resolution/frameworks/tanstack-router.d.ts +84 -0
  364. package/sensor/dist/resolution/frameworks/tanstack-router.d.ts.map +1 -0
  365. package/sensor/dist/resolution/frameworks/tanstack-router.js +437 -0
  366. package/sensor/dist/resolution/frameworks/tanstack-router.js.map +1 -0
  367. package/sensor/dist/resolution/frameworks/vue-router.d.ts +82 -0
  368. package/sensor/dist/resolution/frameworks/vue-router.d.ts.map +1 -0
  369. package/sensor/dist/resolution/frameworks/vue-router.js +377 -0
  370. package/sensor/dist/resolution/frameworks/vue-router.js.map +1 -0
  371. package/sensor/dist/resolution/import-resolver.d.ts +38 -0
  372. package/sensor/dist/resolution/import-resolver.d.ts.map +1 -1
  373. package/sensor/dist/resolution/import-resolver.js +394 -28
  374. package/sensor/dist/resolution/import-resolver.js.map +1 -1
  375. package/sensor/dist/resolution/index.d.ts +89 -25
  376. package/sensor/dist/resolution/index.d.ts.map +1 -1
  377. package/sensor/dist/resolution/index.js +573 -148
  378. package/sensor/dist/resolution/index.js.map +1 -1
  379. package/sensor/dist/resolution/js-builtins.d.ts +13 -0
  380. package/sensor/dist/resolution/js-builtins.d.ts.map +1 -0
  381. package/sensor/dist/resolution/js-builtins.js +46 -0
  382. package/sensor/dist/resolution/js-builtins.js.map +1 -0
  383. package/sensor/dist/resolution/name-matcher.d.ts +99 -22
  384. package/sensor/dist/resolution/name-matcher.d.ts.map +1 -1
  385. package/sensor/dist/resolution/name-matcher.js +2220 -86
  386. package/sensor/dist/resolution/name-matcher.js.map +1 -1
  387. package/sensor/dist/resolution/next-router-synthesizer.d.ts +27 -0
  388. package/sensor/dist/resolution/next-router-synthesizer.d.ts.map +1 -0
  389. package/sensor/dist/resolution/next-router-synthesizer.js +113 -0
  390. package/sensor/dist/resolution/next-router-synthesizer.js.map +1 -0
  391. package/sensor/dist/resolution/path-aliases.d.ts +6 -4
  392. package/sensor/dist/resolution/path-aliases.d.ts.map +1 -1
  393. package/sensor/dist/resolution/path-aliases.js +125 -17
  394. package/sensor/dist/resolution/path-aliases.js.map +1 -1
  395. package/sensor/dist/resolution/react-router-synthesizer.d.ts +33 -0
  396. package/sensor/dist/resolution/react-router-synthesizer.d.ts.map +1 -0
  397. package/sensor/dist/resolution/react-router-synthesizer.js +126 -0
  398. package/sensor/dist/resolution/react-router-synthesizer.js.map +1 -0
  399. package/sensor/dist/resolution/resolver-pool.d.ts +2 -0
  400. package/sensor/dist/resolution/resolver-pool.d.ts.map +1 -1
  401. package/sensor/dist/resolution/resolver-pool.js +4 -0
  402. package/sensor/dist/resolution/resolver-pool.js.map +1 -1
  403. package/sensor/dist/resolution/strip-comments.d.ts +6 -0
  404. package/sensor/dist/resolution/strip-comments.d.ts.map +1 -1
  405. package/sensor/dist/resolution/strip-comments.js +58 -0
  406. package/sensor/dist/resolution/strip-comments.js.map +1 -1
  407. package/sensor/dist/resolution/sveltekit-synthesizer.d.ts +58 -0
  408. package/sensor/dist/resolution/sveltekit-synthesizer.d.ts.map +1 -0
  409. package/sensor/dist/resolution/sveltekit-synthesizer.js +173 -0
  410. package/sensor/dist/resolution/sveltekit-synthesizer.js.map +1 -0
  411. package/sensor/dist/resolution/swift-type-visibility.d.ts +52 -0
  412. package/sensor/dist/resolution/swift-type-visibility.d.ts.map +1 -0
  413. package/sensor/dist/resolution/swift-type-visibility.js +328 -0
  414. package/sensor/dist/resolution/swift-type-visibility.js.map +1 -0
  415. package/sensor/dist/resolution/synth-utils.d.ts +26 -0
  416. package/sensor/dist/resolution/synth-utils.d.ts.map +1 -0
  417. package/sensor/dist/resolution/synth-utils.js +75 -0
  418. package/sensor/dist/resolution/synth-utils.js.map +1 -0
  419. package/sensor/dist/resolution/tanstack-router-synthesizer.d.ts +31 -0
  420. package/sensor/dist/resolution/tanstack-router-synthesizer.d.ts.map +1 -0
  421. package/sensor/dist/resolution/tanstack-router-synthesizer.js +114 -0
  422. package/sensor/dist/resolution/tanstack-router-synthesizer.js.map +1 -0
  423. package/sensor/dist/resolution/tier-synthesizer.d.ts +54 -0
  424. package/sensor/dist/resolution/tier-synthesizer.d.ts.map +1 -0
  425. package/sensor/dist/resolution/tier-synthesizer.js +1294 -0
  426. package/sensor/dist/resolution/tier-synthesizer.js.map +1 -0
  427. package/sensor/dist/resolution/types.d.ts +78 -2
  428. package/sensor/dist/resolution/types.d.ts.map +1 -1
  429. package/sensor/dist/resolution/types.js +64 -0
  430. package/sensor/dist/resolution/types.js.map +1 -1
  431. package/sensor/dist/resolution/vue-router-synthesizer.d.ts +30 -0
  432. package/sensor/dist/resolution/vue-router-synthesizer.d.ts.map +1 -0
  433. package/sensor/dist/resolution/vue-router-synthesizer.js +115 -0
  434. package/sensor/dist/resolution/vue-router-synthesizer.js.map +1 -0
  435. package/sensor/dist/search/identifier-segments.d.ts +10 -0
  436. package/sensor/dist/search/identifier-segments.d.ts.map +1 -1
  437. package/sensor/dist/search/identifier-segments.js +29 -0
  438. package/sensor/dist/search/identifier-segments.js.map +1 -1
  439. package/sensor/dist/search/query-paths.d.ts +106 -0
  440. package/sensor/dist/search/query-paths.d.ts.map +1 -0
  441. package/sensor/dist/search/query-paths.js +513 -0
  442. package/sensor/dist/search/query-paths.js.map +1 -0
  443. package/sensor/dist/search/query-utils.d.ts +22 -2
  444. package/sensor/dist/search/query-utils.d.ts.map +1 -1
  445. package/sensor/dist/search/query-utils.js +56 -16
  446. package/sensor/dist/search/query-utils.js.map +1 -1
  447. package/sensor/dist/sync/watch-policy.d.ts +14 -0
  448. package/sensor/dist/sync/watch-policy.d.ts.map +1 -1
  449. package/sensor/dist/sync/watch-policy.js +19 -0
  450. package/sensor/dist/sync/watch-policy.js.map +1 -1
  451. package/sensor/dist/sync/watcher.d.ts +74 -35
  452. package/sensor/dist/sync/watcher.d.ts.map +1 -1
  453. package/sensor/dist/sync/watcher.js +281 -99
  454. package/sensor/dist/sync/watcher.js.map +1 -1
  455. package/sensor/dist/telemetry/index.d.ts +4 -2
  456. package/sensor/dist/telemetry/index.d.ts.map +1 -1
  457. package/sensor/dist/telemetry/index.js +92 -41
  458. package/sensor/dist/telemetry/index.js.map +1 -1
  459. package/sensor/dist/types.d.ts +18 -2
  460. package/sensor/dist/types.d.ts.map +1 -1
  461. package/sensor/dist/types.js +2 -0
  462. package/sensor/dist/types.js.map +1 -1
  463. package/sensor/dist/ui-server/api/deadcode.d.ts +84 -0
  464. package/sensor/dist/ui-server/api/deadcode.d.ts.map +1 -0
  465. package/sensor/dist/ui-server/api/deadcode.js +136 -0
  466. package/sensor/dist/ui-server/api/deadcode.js.map +1 -0
  467. package/sensor/dist/ui-server/api/effects.d.ts +94 -0
  468. package/sensor/dist/ui-server/api/effects.d.ts.map +1 -0
  469. package/sensor/dist/ui-server/api/effects.js +527 -0
  470. package/sensor/dist/ui-server/api/effects.js.map +1 -0
  471. package/sensor/dist/ui-server/api/entrypoints.d.ts +92 -0
  472. package/sensor/dist/ui-server/api/entrypoints.d.ts.map +1 -0
  473. package/sensor/dist/ui-server/api/entrypoints.js +361 -0
  474. package/sensor/dist/ui-server/api/entrypoints.js.map +1 -0
  475. package/sensor/dist/ui-server/api/events.d.ts +183 -0
  476. package/sensor/dist/ui-server/api/events.d.ts.map +1 -0
  477. package/sensor/dist/ui-server/api/events.js +430 -0
  478. package/sensor/dist/ui-server/api/events.js.map +1 -0
  479. package/sensor/dist/ui-server/api/file.d.ts +66 -0
  480. package/sensor/dist/ui-server/api/file.d.ts.map +1 -0
  481. package/sensor/dist/ui-server/api/file.js +235 -0
  482. package/sensor/dist/ui-server/api/file.js.map +1 -0
  483. package/sensor/dist/ui-server/api/filecode.d.ts +126 -0
  484. package/sensor/dist/ui-server/api/filecode.d.ts.map +1 -0
  485. package/sensor/dist/ui-server/api/filecode.js +205 -0
  486. package/sensor/dist/ui-server/api/filecode.js.map +1 -0
  487. package/sensor/dist/ui-server/api/flow.d.ts +226 -0
  488. package/sensor/dist/ui-server/api/flow.d.ts.map +1 -0
  489. package/sensor/dist/ui-server/api/flow.js +612 -0
  490. package/sensor/dist/ui-server/api/flow.js.map +1 -0
  491. package/sensor/dist/ui-server/api/hierarchy.d.ts +81 -0
  492. package/sensor/dist/ui-server/api/hierarchy.d.ts.map +1 -0
  493. package/sensor/dist/ui-server/api/hierarchy.js +92 -0
  494. package/sensor/dist/ui-server/api/hierarchy.js.map +1 -0
  495. package/sensor/dist/ui-server/api/index.d.ts +90 -0
  496. package/sensor/dist/ui-server/api/index.d.ts.map +1 -0
  497. package/sensor/dist/ui-server/api/index.js +332 -0
  498. package/sensor/dist/ui-server/api/index.js.map +1 -0
  499. package/sensor/dist/ui-server/api/map.d.ts +256 -0
  500. package/sensor/dist/ui-server/api/map.d.ts.map +1 -0
  501. package/sensor/dist/ui-server/api/map.js +760 -0
  502. package/sensor/dist/ui-server/api/map.js.map +1 -0
  503. package/sensor/dist/ui-server/api/node.d.ts +81 -0
  504. package/sensor/dist/ui-server/api/node.d.ts.map +1 -0
  505. package/sensor/dist/ui-server/api/node.js +388 -0
  506. package/sensor/dist/ui-server/api/node.js.map +1 -0
  507. package/sensor/dist/ui-server/api/nodes.d.ts +25 -0
  508. package/sensor/dist/ui-server/api/nodes.d.ts.map +1 -0
  509. package/sensor/dist/ui-server/api/nodes.js +45 -0
  510. package/sensor/dist/ui-server/api/nodes.js.map +1 -0
  511. package/sensor/dist/ui-server/api/program.d.ts +139 -0
  512. package/sensor/dist/ui-server/api/program.d.ts.map +1 -0
  513. package/sensor/dist/ui-server/api/program.js +315 -0
  514. package/sensor/dist/ui-server/api/program.js.map +1 -0
  515. package/sensor/dist/ui-server/api/respond.d.ts +76 -0
  516. package/sensor/dist/ui-server/api/respond.d.ts.map +1 -0
  517. package/sensor/dist/ui-server/api/respond.js +186 -0
  518. package/sensor/dist/ui-server/api/respond.js.map +1 -0
  519. package/sensor/dist/ui-server/api/route-roots.d.ts +39 -0
  520. package/sensor/dist/ui-server/api/route-roots.d.ts.map +1 -0
  521. package/sensor/dist/ui-server/api/route-roots.js +96 -0
  522. package/sensor/dist/ui-server/api/route-roots.js.map +1 -0
  523. package/sensor/dist/ui-server/api/routes.d.ts +59 -0
  524. package/sensor/dist/ui-server/api/routes.d.ts.map +1 -0
  525. package/sensor/dist/ui-server/api/routes.js +108 -0
  526. package/sensor/dist/ui-server/api/routes.js.map +1 -0
  527. package/sensor/dist/ui-server/api/screens.d.ts +117 -0
  528. package/sensor/dist/ui-server/api/screens.d.ts.map +1 -0
  529. package/sensor/dist/ui-server/api/screens.js +611 -0
  530. package/sensor/dist/ui-server/api/screens.js.map +1 -0
  531. package/sensor/dist/ui-server/api/search.d.ts +29 -0
  532. package/sensor/dist/ui-server/api/search.d.ts.map +1 -0
  533. package/sensor/dist/ui-server/api/search.js +232 -0
  534. package/sensor/dist/ui-server/api/search.js.map +1 -0
  535. package/sensor/dist/ui-server/api/session.d.ts +52 -0
  536. package/sensor/dist/ui-server/api/session.d.ts.map +1 -0
  537. package/sensor/dist/ui-server/api/session.js +196 -0
  538. package/sensor/dist/ui-server/api/session.js.map +1 -0
  539. package/sensor/dist/ui-server/api/source.d.ts +200 -0
  540. package/sensor/dist/ui-server/api/source.d.ts.map +1 -0
  541. package/sensor/dist/ui-server/api/source.js +422 -0
  542. package/sensor/dist/ui-server/api/source.js.map +1 -0
  543. package/sensor/dist/ui-server/api/stats.d.ts +27 -0
  544. package/sensor/dist/ui-server/api/stats.d.ts.map +1 -0
  545. package/sensor/dist/ui-server/api/stats.js +164 -0
  546. package/sensor/dist/ui-server/api/stats.js.map +1 -0
  547. package/sensor/dist/ui-server/api/steps.d.ts +274 -0
  548. package/sensor/dist/ui-server/api/steps.d.ts.map +1 -0
  549. package/sensor/dist/ui-server/api/steps.js +1544 -0
  550. package/sensor/dist/ui-server/api/steps.js.map +1 -0
  551. package/sensor/dist/ui-server/api/trail-store.d.ts +142 -0
  552. package/sensor/dist/ui-server/api/trail-store.d.ts.map +1 -0
  553. package/sensor/dist/ui-server/api/trail-store.js +325 -0
  554. package/sensor/dist/ui-server/api/trail-store.js.map +1 -0
  555. package/sensor/dist/ui-server/api/trails.d.ts +163 -0
  556. package/sensor/dist/ui-server/api/trails.d.ts.map +1 -0
  557. package/sensor/dist/ui-server/api/trails.js +369 -0
  558. package/sensor/dist/ui-server/api/trails.js.map +1 -0
  559. package/sensor/dist/ui-server/api/when.d.ts +133 -0
  560. package/sensor/dist/ui-server/api/when.d.ts.map +1 -0
  561. package/sensor/dist/ui-server/api/when.js +275 -0
  562. package/sensor/dist/ui-server/api/when.js.map +1 -0
  563. package/sensor/dist/ui-server/api/wire.d.ts +179 -0
  564. package/sensor/dist/ui-server/api/wire.d.ts.map +1 -0
  565. package/sensor/dist/ui-server/api/wire.js +231 -0
  566. package/sensor/dist/ui-server/api/wire.js.map +1 -0
  567. package/sensor/dist/ui-server/assets.d.ts +40 -0
  568. package/sensor/dist/ui-server/assets.d.ts.map +1 -0
  569. package/sensor/dist/ui-server/assets.js +110 -0
  570. package/sensor/dist/ui-server/assets.js.map +1 -0
  571. package/sensor/dist/ui-server/constants.d.ts +32 -0
  572. package/sensor/dist/ui-server/constants.d.ts.map +1 -0
  573. package/sensor/dist/ui-server/constants.js +35 -0
  574. package/sensor/dist/ui-server/constants.js.map +1 -0
  575. package/sensor/dist/ui-server/highlight/index.d.ts +113 -0
  576. package/sensor/dist/ui-server/highlight/index.d.ts.map +1 -0
  577. package/sensor/dist/ui-server/highlight/index.js +307 -0
  578. package/sensor/dist/ui-server/highlight/index.js.map +1 -0
  579. package/sensor/dist/ui-server/highlight/languages.d.ts +36 -0
  580. package/sensor/dist/ui-server/highlight/languages.d.ts.map +1 -0
  581. package/sensor/dist/ui-server/highlight/languages.js +50 -0
  582. package/sensor/dist/ui-server/highlight/languages.js.map +1 -0
  583. package/sensor/dist/ui-server/index.d.ts +92 -0
  584. package/sensor/dist/ui-server/index.d.ts.map +1 -0
  585. package/sensor/dist/ui-server/index.js +399 -0
  586. package/sensor/dist/ui-server/index.js.map +1 -0
  587. package/sensor/dist/ui-server/open-browser.d.ts +31 -0
  588. package/sensor/dist/ui-server/open-browser.d.ts.map +1 -0
  589. package/sensor/dist/ui-server/open-browser.js +79 -0
  590. package/sensor/dist/ui-server/open-browser.js.map +1 -0
  591. package/sensor/dist/ui-server/security.d.ts +127 -0
  592. package/sensor/dist/ui-server/security.d.ts.map +1 -0
  593. package/sensor/dist/ui-server/security.js +332 -0
  594. package/sensor/dist/ui-server/security.js.map +1 -0
  595. package/sensor/dist/ui-server/static.d.ts +45 -0
  596. package/sensor/dist/ui-server/static.d.ts.map +1 -0
  597. package/sensor/dist/ui-server/static.js +165 -0
  598. package/sensor/dist/ui-server/static.js.map +1 -0
  599. package/sensor/dist/utils.d.ts +14 -2
  600. package/sensor/dist/utils.d.ts.map +1 -1
  601. package/sensor/dist/utils.js +25 -10
  602. package/sensor/dist/utils.js.map +1 -1
  603. package/src/sensor-adapter.mjs +5 -1
@@ -4,15 +4,52 @@
4
4
  *
5
5
  * Handles symbol name matching for reference resolution.
6
6
  */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
7
40
  Object.defineProperty(exports, "__esModule", { value: true });
8
41
  exports.matchByFilePath = matchByFilePath;
9
42
  exports.sameLanguageFamily = sameLanguageFamily;
10
- exports.isKnownLanguageFamily = isKnownLanguageFamily;
11
- exports.crossesKnownFamily = crossesKnownFamily;
43
+ exports.crossesCodeBoundary = crossesCodeBoundary;
44
+ exports.gateLanguageMatch = gateLanguageMatch;
12
45
  exports.matchFunctionRef = matchFunctionRef;
46
+ exports.isVisibleAcrossFiles = isVisibleAcrossFiles;
13
47
  exports.matchByExactName = matchByExactName;
14
48
  exports.matchByQualifiedName = matchByQualifiedName;
15
49
  exports.preferCallSiteFile = preferCallSiteFile;
50
+ exports.resolveObjectLiteralMember = resolveObjectLiteralMember;
51
+ exports.objectLiteralMemberBinding = objectLiteralMemberBinding;
52
+ exports.resolveObjectLiteralBinding = resolveObjectLiteralBinding;
16
53
  exports.resolveMethodOnType = resolveMethodOnType;
17
54
  exports.matchCppCallChain = matchCppCallChain;
18
55
  exports.matchScopedCallChain = matchScopedCallChain;
@@ -21,9 +58,16 @@ exports.normalizeInferredTypeName = normalizeInferredTypeName;
21
58
  exports.clearNameMatcherMemos = clearNameMatcherMemos;
22
59
  exports.localReceiverTypePatterns = localReceiverTypePatterns;
23
60
  exports.matchMethodCall = matchMethodCall;
61
+ exports.rustFieldTypeName = rustFieldTypeName;
62
+ exports.matchJsStoreBindingCall = matchJsStoreBindingCall;
63
+ exports.isUnresolvedJsMemberCall = isUnresolvedJsMemberCall;
24
64
  exports.matchFuzzy = matchFuzzy;
25
65
  exports.dumpNameMatcherProfile = dumpNameMatcherProfile;
26
66
  exports.matchReference = matchReference;
67
+ const path = __importStar(require("path"));
68
+ const types_1 = require("./types");
69
+ const strip_comments_1 = require("./strip-comments");
70
+ const js_builtins_1 = require("./js-builtins");
27
71
  /**
28
72
  * Ceiling on how many same-named definitions a FUZZY name-match strategy will
29
73
  * score. A name defined more times than this is "ubiquitous" — a method/symbol
@@ -143,14 +187,16 @@ function pickClosestFileNode(candidates, ref) {
143
187
  */
144
188
  const LANGUAGE_FAMILY = {
145
189
  java: 'jvm', kotlin: 'jvm', scala: 'jvm',
146
- swift: 'apple', objc: 'apple',
190
+ swift: 'native', objc: 'native',
147
191
  // ArkTS is a TS superset — every HarmonyOS project mixes `.ets` UI with
148
192
  // `.ts` logic modules, so refs must cross freely between them.
149
193
  typescript: 'web', tsx: 'web', javascript: 'web', jsx: 'web', arkts: 'web',
150
- c: 'c', cpp: 'c',
194
+ c: 'native', cpp: 'native',
151
195
  // Razor/Blazor markup names C# types — same family so `@model Foo` /
152
196
  // `<MyComponent/>` resolve to their `.cs` class through the cross-family gate.
153
- csharp: 'dotnet', razor: 'dotnet',
197
+ csharp: 'dotnet', razor: 'dotnet', vbnet: 'dotnet',
198
+ svelte: 'web', vue: 'web', astro: 'web',
199
+ cfml: 'cfml', cfscript: 'cfml',
154
200
  };
155
201
  function sameLanguageFamily(a, b) {
156
202
  if (a === b)
@@ -158,62 +204,324 @@ function sameLanguageFamily(a, b) {
158
204
  const fa = LANGUAGE_FAMILY[a];
159
205
  return fa !== undefined && fa === LANGUAGE_FAMILY[b];
160
206
  }
161
- /**
162
- * True when `lang` belongs to a known multi-language family (jvm/apple/web/c).
163
- * Languages not listed (php, python, go, ruby, rust, dart, …) and config
164
- * formats (yaml/xml/blade) form their own singleton families and return
165
- * `false` — used to leave config↔code framework bridges (whose config side is
166
- * never a known programming-language family) out of the cross-family gate.
167
- */
168
- function isKnownLanguageFamily(lang) {
169
- return LANGUAGE_FAMILY[lang] !== undefined;
207
+ /** Config/markup transitions stay open; every other code language has a family. */
208
+ const CODE_FAMILY = {
209
+ ...LANGUAGE_FAMILY,
210
+ python: 'python', go: 'go', rust: 'rust', php: 'php', ruby: 'ruby', dart: 'dart',
211
+ lua: 'lua', luau: 'lua', r: 'r', erlang: 'erlang', pascal: 'pascal', solidity: 'solidity',
212
+ nix: 'nix', cobol: 'cobol',
213
+ };
214
+ function crossesCodeBoundary(a, b) {
215
+ return CODE_FAMILY[a] !== undefined && CODE_FAMILY[b] !== undefined &&
216
+ CODE_FAMILY[a] !== CODE_FAMILY[b];
170
217
  }
171
218
  /**
172
- * True when `a` and `b` are two DIFFERENT *known* language families — the
173
- * signature of a coincidental cross-language name collision (a TS `import
174
- * React` matching a Swift `import React`, a C++ `#include "X.h"` matching a
175
- * same-named ObjC header on another platform). The both-*known* test is
176
- * deliberately weaker than {@link sameLanguageFamily}'s negation: a
177
- * single-file-component language that carries its own tag (`vue`/`svelte`)
178
- * importing a `.ts` module, or any singleton-family language (php/go/ruby/…),
179
- * returns `false` here and is left alone.
219
+ * Cross-family name matches need a framework export or an actual ABI boundary,
220
+ * not merely a native caller. ABI evidence is scoped to the named free function.
180
221
  */
181
- function crossesKnownFamily(a, b) {
182
- return isKnownLanguageFamily(a) && isKnownLanguageFamily(b) && !sameLanguageFamily(a, b);
222
+ function hasBridgeEvidence(candidate, ref, context) {
223
+ if (ref.referenceKind !== 'calls')
224
+ return false;
225
+ // Expo's extractor creates explicit JS exports, resolved by the ordinary
226
+ // name matcher rather than a framework resolve() branch.
227
+ if (CODE_FAMILY[ref.language] === 'web' && candidate.id.startsWith('expo-module:') &&
228
+ candidate.isExported && (candidate.language === 'swift' || candidate.language === 'kotlin'))
229
+ return true;
230
+ if (candidate.kind !== 'function')
231
+ return false;
232
+ const name = candidate.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
233
+ if (CODE_FAMILY[ref.language] === 'native') {
234
+ const source = context.readFile(candidate.filePath);
235
+ if (!source)
236
+ return false;
237
+ if (candidate.language === 'go') {
238
+ const declaration = source.split('\n').slice(Math.max(0, candidate.startLine - 2), candidate.endLine).join('\n');
239
+ return /\bimport\s+(?:\(\s*)?"C"/.test((0, strip_comments_1.stripCommentsForRegex)(source, 'go')) &&
240
+ new RegExp('^//export ' + name + '\\r?\\nfunc ' + name + '\\s*\\(', 'm').test(declaration);
241
+ }
242
+ if (candidate.language === 'rust') {
243
+ const declaration = source.split('\n').slice(candidate.startLine - 1, candidate.endLine).join('\n');
244
+ return new RegExp('\\bpub\\s+extern\\s+"C"\\s+fn\\s+' + name + '\\b')
245
+ .test((0, strip_comments_1.stripCommentsForRegex)(declaration, 'rust'));
246
+ }
247
+ }
248
+ if (candidate.language === 'c' || candidate.language === 'cpp') {
249
+ const source = context.readFile(ref.filePath);
250
+ if (!source)
251
+ return false;
252
+ if (ref.language === 'go') {
253
+ return /\bimport\s+(?:\(\s*)?"C"/.test((0, strip_comments_1.stripCommentsForRegex)(source, 'go')) &&
254
+ ref.referenceName === 'C.' + candidate.name;
255
+ }
256
+ if (ref.language === 'rust') {
257
+ return new RegExp('extern\\s+"C"\\s*\\{[^}]*\\bfn\\s+' + name + '\\s*\\(')
258
+ .test((0, strip_comments_1.stripCommentsForRegex)(source, 'rust'));
259
+ }
260
+ }
261
+ return false;
183
262
  }
184
263
  /**
185
- * Drop cross-language candidates from a name lookup. Two regimes:
186
- * - `references` (type-usage): a type named in language X resolves to a
187
- * SAME-family type, never a coincidentally same-named symbol in another
188
- * language (the Android `BatteryManager` system class vs a JS one). Strict
189
- * same-family filter — cross-language communication is `calls`, not refs.
190
- * - `imports` (import binding): an `import`/`#include` never crosses two
191
- * KNOWN families (TS `import React` ↮ Swift `import React`). Weaker
192
- * both-known filter so `.vue`/`.svelte` (own tag) importing `.ts` survives.
264
+ * Per-context memo: node id → its language, for gateLanguageMatch. Matches
265
+ * land on ~5 refs per target on vscode, and each check otherwise fetched the
266
+ * whole node (a point read + row decode past the query layer's small cache)
267
+ * only to read one field. Nodes are fixed within a resolution pass; the memo
268
+ * drops with clearNameMatcherMemos.
193
269
  */
194
- function applyLanguageGate(candidates, ref) {
195
- if (ref.referenceKind === 'references' || ref.referenceKind === 'function_ref') {
196
- return candidates.filter((c) => sameLanguageFamily(c.language, ref.language));
270
+ const TARGET_LANGUAGE = new WeakMap();
271
+ /** Reject the chosen result without shrinking a pool or trying a replacement. */
272
+ function gateLanguageMatch(result, ref, context) {
273
+ if (!result)
274
+ return result;
275
+ // No code family on the reference's side: no target can cross a boundary.
276
+ if (CODE_FAMILY[ref.language] === undefined)
277
+ return result;
278
+ if (context.getNodeById) {
279
+ let languages = TARGET_LANGUAGE.get(context);
280
+ if (!languages) {
281
+ languages = new Map();
282
+ TARGET_LANGUAGE.set(context, languages);
283
+ }
284
+ let language = languages.get(result.targetNodeId);
285
+ if (language === undefined) {
286
+ const node = context.getNodeById(result.targetNodeId);
287
+ if (node) {
288
+ language = node.language;
289
+ if (languages.size >= 400_000)
290
+ languages.clear();
291
+ languages.set(result.targetNodeId, language);
292
+ }
293
+ }
294
+ if (language !== undefined) {
295
+ if (!crossesCodeBoundary(ref.language, language))
296
+ return result;
297
+ const target = context.getNodeById(result.targetNodeId);
298
+ return target && !hasBridgeEvidence(target, ref, context) ? null : result;
299
+ }
300
+ }
301
+ const target = context.getNodeById?.(result.targetNodeId) ??
302
+ context.getNodesByName(ref.referenceName).find((n) => n.id === result.targetNodeId);
303
+ if (target && crossesCodeBoundary(ref.language, target.language) &&
304
+ !hasBridgeEvidence(target, ref, context))
305
+ return null;
306
+ return result;
307
+ }
308
+ /** Member values retain their receiver; never break ties by file order (#1820). */
309
+ function matchMemberFunctionRef(ref, context) {
310
+ const dot = ref.referenceName.lastIndexOf('.');
311
+ const receiver = ref.referenceName.slice(0, dot);
312
+ const member = ref.referenceName.slice(dot + 1);
313
+ const result = (nodes, confidence = 0.9) => {
314
+ const pool = nodes.filter(n => sameLanguageFamily(n.language, ref.language));
315
+ const target = pool.length === 1 ? pool[0] : undefined;
316
+ return target && (target.kind === 'function' || target.kind === 'method') &&
317
+ target.id !== ref.fromNodeId && !isPythonProperty(target, context)
318
+ ? { original: ref, targetNodeId: target.id, confidence, resolvedBy: 'function-ref' }
319
+ : null;
320
+ };
321
+ const imports = context.getImportMappings(ref.filePath, ref.language);
322
+ // An import is authoritative even when it points outside the project.
323
+ if (imports.some(i => i.localName === receiver.split('.')[0])) {
324
+ if (ref.language === 'python') {
325
+ const cls = pythonRefClass(receiver, ref, context);
326
+ if (cls)
327
+ return result(pythonMembers(cls, member, ref, context));
328
+ }
329
+ const imported = context.resolveImport?.(ref);
330
+ const node = imported && context.getNodeById?.(imported.targetNodeId);
331
+ return node ? result(context.getNodesByQualifiedName(node.qualifiedName).filter(n => n.filePath === node.filePath)) : null;
332
+ }
333
+ if (ref.language === 'go') {
334
+ if (receiver.includes('.'))
335
+ return matchGoFieldChainCall(receiver, member, ref, context);
336
+ const type = inferLocalReceiverType(receiver, ref, context);
337
+ if (type)
338
+ return resolveMethodOnType(type, member, ref, context, 0.9, 'function-ref');
339
+ const types = context.getNodesByName(receiver).filter(n => n.language === 'go' && (n.kind === 'struct' || n.kind === 'interface'));
340
+ if (types.length)
341
+ return types.length === 1 ? resolveMethodOnType(receiver, member, ref, context, 0.9, 'function-ref') : null;
197
342
  }
198
- if (ref.referenceKind === 'imports') {
199
- return candidates.filter((c) => !crossesKnownFamily(c.language, ref.language));
343
+ else {
344
+ const owner = context.getNodesInFile(ref.filePath).filter(n => n.kind === 'class' && n.startLine <= ref.line && n.endLine >= ref.line)
345
+ .sort((a, b) => b.startLine - a.startLine)[0];
346
+ let type = null;
347
+ if (receiver === 'self' || receiver === 'cls') {
348
+ return owner ? result(pythonMembers(owner, member, ref, context)) : null;
349
+ }
350
+ if (/^(self|cls)\.\w+$/.test(receiver)) {
351
+ if (!owner)
352
+ return null;
353
+ type = pythonFieldType(receiver, owner, ref, context);
354
+ }
355
+ else {
356
+ type = pythonLocalType(receiver, ref, context);
357
+ }
358
+ // A type name used directly (`Store.fetch`) is scoped just like an annotation.
359
+ if (!type && /^[A-Z]\w*$/.test(receiver))
360
+ type = receiver;
361
+ if (type && type !== 'object' && type !== 'Any') {
362
+ const cls = pythonRefClass(type, ref, context);
363
+ if (!cls)
364
+ return null;
365
+ const members = pythonMembers(cls, member, ref, context);
366
+ if (members.length)
367
+ return result(members);
368
+ // A base-typed field can hold a subclass-only method (the reported case).
369
+ // Keep only descendants of THAT base; unrelated same-name methods cannot win.
370
+ const candidates = context.getNodesByName(member).filter(n => n.kind === 'method' && n.language === 'python');
371
+ const descendants = candidates.filter(n => {
372
+ const parent = context.getNodesInFile(n.filePath).find(c => c.kind === 'class' && n.qualifiedName === `${c.qualifiedName}::${member}`);
373
+ return parent && pythonDerivesFrom(parent, cls, ref, context);
374
+ });
375
+ return result(descendants, 0.8);
376
+ }
377
+ }
378
+ // Unknown receivers retain the old unique-or-drop discipline, across ALL
379
+ // files. Tests and abstract-looking bodies are candidates too.
380
+ return result(context.getNodesByName(member), 0.8);
381
+ }
382
+ function pythonRefClass(name, ref, context) {
383
+ const imports = context.getImportMappings(ref.filePath, 'python');
384
+ if (imports.some(i => i.localName === name.split('.')[0])) {
385
+ const hit = context.resolveImport?.({ ...ref, referenceName: name, referenceKind: 'references' });
386
+ const node = hit && context.getNodeById?.(hit.targetNodeId);
387
+ return node?.kind === 'class' && context.getNodesByQualifiedName(node.qualifiedName)
388
+ .filter(n => n.kind === 'class' && n.filePath === node.filePath).length === 1 ? node : null;
200
389
  }
201
- return candidates;
390
+ const classes = context.getNodesByName(name).filter(n => n.kind === 'class' && n.filePath === ref.filePath);
391
+ return classes.length === 1 ? classes[0] : null;
392
+ }
393
+ function pythonBases(cls, ref, context) {
394
+ const line = context.getFileLines?.(cls.filePath)?.[cls.startLine - 1]
395
+ ?? context.readFile(cls.filePath)?.split('\n')[cls.startLine - 1] ?? '';
396
+ const bases = line.match(/^\s*class\s+\w+\s*\(([^)]*)\)/)?.[1];
397
+ return (bases?.split(',') ?? []).flatMap(name => {
398
+ const base = pythonRefClass(name.trim(), { ...ref, filePath: cls.filePath }, context);
399
+ return base ? [base] : [];
400
+ });
401
+ }
402
+ function pythonDerivesFrom(cls, base, ref, context, seen = new Set()) {
403
+ if (seen.has(cls.id) || seen.size >= 16)
404
+ return false;
405
+ seen.add(cls.id);
406
+ return pythonBases(cls, ref, context).some(p => p.id === base.id || pythonDerivesFrom(p, base, ref, context, seen));
407
+ }
408
+ function pythonMembers(cls, member, ref, context, seen = new Set()) {
409
+ if (seen.has(cls.id) || seen.size >= 16)
410
+ return [];
411
+ seen.add(cls.id);
412
+ // Instance assignments also shadow methods, even though they are not nodes.
413
+ const body = pythonMemberLines(cls.filePath, context).slice(cls.startLine - 1, cls.endLine).join('\n');
414
+ if (new RegExp(`^\\s*(?:(?:self|cls)\\.)?${member}\\s*(?:=|:)`, 'm').test(body))
415
+ return [cls];
416
+ const own = context.getNodesByQualifiedName(`${cls.qualifiedName}::${member}`).filter(n => n.filePath === cls.filePath);
417
+ if (own.length)
418
+ return own;
419
+ return [...new Map(pythonBases(cls, ref, context).flatMap(p => pythonMembers(p, member, ref, context, seen)).map(n => [n.id, n])).values()];
420
+ }
421
+ function isPythonProperty(node, context) {
422
+ if (node.language !== 'python' || node.kind !== 'method')
423
+ return false;
424
+ const lines = context.getFileLines?.(node.filePath) ?? context.readFile(node.filePath)?.split('\n') ?? [];
425
+ for (let i = node.startLine - 2; i >= 0 && lines[i].trim().startsWith('@'); i--) {
426
+ if (/^\s*@(?:property|(?:functools\.)?cached_property)\s*$/.test(lines[i]))
427
+ return true;
428
+ }
429
+ return false;
430
+ }
431
+ const PYTHON_MEMBER_LINES = new WeakMap();
432
+ function pythonMemberLines(filePath, context) {
433
+ let files = PYTHON_MEMBER_LINES.get(context);
434
+ if (!files) {
435
+ files = new Map();
436
+ PYTHON_MEMBER_LINES.set(context, files);
437
+ }
438
+ let lines = files.get(filePath);
439
+ if (!lines) {
440
+ lines = (0, strip_comments_1.stripCommentsForRegex)(context.readFile(filePath) ?? '', 'python').split('\n');
441
+ files.set(filePath, lines);
442
+ }
443
+ return lines;
444
+ }
445
+ function pythonLocalType(receiver, ref, context) {
446
+ if (!/^\w+$/.test(receiver))
447
+ return null;
448
+ const caller = context.getNodeById?.(ref.fromNodeId);
449
+ const lines = pythonMemberLines(ref.filePath, context);
450
+ const declaration = new RegExp(`^\\s*${receiver}\\s*(?::\\s*["']?([\\w.]+)["']?)?\\s*=\\s*(.*)$`);
451
+ const annotation = new RegExp(`^\\s*${receiver}\\s*:\\s*["']?([\\w.]+)`);
452
+ for (let i = ref.line - 1; i >= (caller?.startLine ?? 1) - 1; i--) {
453
+ const line = lines[i] ?? '';
454
+ const assigned = line.match(declaration);
455
+ if (assigned)
456
+ return assigned[1] ?? assigned[2].match(/^([A-Z][\w.]*)\s*\(/)?.[1] ?? '<unknown>';
457
+ const declared = line.match(annotation)?.[1];
458
+ if (declared)
459
+ return declared;
460
+ }
461
+ return caller?.signature?.match(new RegExp(`\\b${receiver}\\s*:\\s*["']?([\\w.]+)`))?.[1] ?? null;
462
+ }
463
+ /** Read a field's own annotation/initializer, or a constructor parameter assigned to it. */
464
+ function pythonFieldType(receiver, owner, ref, context) {
465
+ const lines = pythonMemberLines(ref.filePath, context);
466
+ const field = receiver.split('.')[1];
467
+ const assignment = new RegExp(`^\\s*(?:self|cls)\\.${field}\\s*(?::\\s*["']?([\\w.]+)["']?)?\\s*=\\s*(.*)$`);
468
+ const annotation = new RegExp(`^\\s*(?:(?:self|cls)\\.)?${field}\\s*:\\s*["']?([\\w.]+)`);
469
+ const methods = context.getNodesInFile(ref.filePath).filter(n => n.kind === 'method' &&
470
+ n.qualifiedName.startsWith(`${owner.qualifiedName}::`));
471
+ const types = new Set();
472
+ for (let i = owner.startLine; i < owner.endLine; i++) {
473
+ const method = methods.find(n => n.startLine <= i + 1 && n.endLine >= i + 1);
474
+ if (method && method.name !== '__init__' && method.id !== ref.fromNodeId)
475
+ continue;
476
+ if (method?.id === ref.fromNodeId && i + 1 > ref.line)
477
+ continue;
478
+ const line = lines[i] ?? '';
479
+ const declared = !method || /^\s*(?:self|cls)\./.test(line) ? line.match(annotation)?.[1] : undefined;
480
+ if (declared)
481
+ types.add(declared);
482
+ const assigned = line.match(assignment);
483
+ if (!assigned)
484
+ continue;
485
+ if (assigned[1]) {
486
+ types.add(assigned[1]);
487
+ continue;
488
+ }
489
+ const constructor = assigned[2].match(/^([A-Z][\w.]*)\s*\(/)?.[1];
490
+ if (constructor) {
491
+ types.add(constructor);
492
+ continue;
493
+ }
494
+ const param = assigned[2].trim();
495
+ if (method && /^\w+$/.test(param)) {
496
+ const signature = method.signature ?? '';
497
+ const type = signature.match(new RegExp(`\\b${param}\\s*:\\s*["']?([\\w.]+)`))?.[1];
498
+ if (type)
499
+ types.add(type);
500
+ }
501
+ else {
502
+ types.add('<unknown>');
503
+ }
504
+ }
505
+ // Conflicting assignments are known-but-ambiguous, never a name-only fallback.
506
+ return types.size === 1 ? [...types][0] : types.size > 1 ? '<ambiguous>' : null;
202
507
  }
203
508
  /**
204
509
  * Resolve a function-as-value reference (#756) — a function name used as a
205
510
  * callback/function-pointer value (`register(handler)`, `o->cb = handler`,
206
511
  * `{ .cb = handler }`, `signal(SIGINT, handler)`). The ONLY strategy allowed
207
512
  * for `function_ref` refs: exact name, function/method targets only, same
208
- * language family, same-file first, and cross-file only when the match is
209
- * UNIQUE. No fuzzy fallback, no qualified-name walking — a wrong callback
210
- * edge is worse than none.
513
+ * language family, same-file first for bare names, and unique-only cross-file.
514
+ * Member values use receiver/type/import scope before a unique-name fallback.
515
+ * A wrong callback edge is worse than none.
211
516
  */
212
517
  function matchFunctionRef(ref, context) {
213
518
  // `this.<member>` refs are resolved ONLY by the class-scoped resolver in
214
519
  // resolveOne (resolveThisMemberFnRef) — never by name matching here.
215
520
  if (ref.referenceName.startsWith('this.'))
216
521
  return null;
522
+ if ((ref.language === 'python' || ref.language === 'go') && ref.referenceName.includes('.')) {
523
+ return matchMemberFunctionRef(ref, context);
524
+ }
217
525
  // In JS/TS/Python a bare identifier can never be a method value (methods
218
526
  // are only reachable through a receiver — `this.m` / `self.m` /
219
527
  // `Cls.m`), so bare fn-refs match FUNCTIONS only. This also sidesteps the
@@ -343,6 +651,8 @@ function matchFunctionRef(ref, context) {
343
651
  }
344
652
  return null;
345
653
  }
654
+ /** Languages with no nested named functions: nesting in the graph is never a scope. */
655
+ const NO_NESTED_FUNCTIONS = new Set(['c', 'cpp']);
346
656
  /**
347
657
  * A function nested inside another FUNCTION is only callable from within its
348
658
  * container — Python, JS/TS, and every closure language scope it lexically.
@@ -357,6 +667,15 @@ function matchFunctionRef(ref, context) {
357
667
  function isLexicallyReachable(candidate, ref, context) {
358
668
  if (candidate.kind !== 'function')
359
669
  return true;
670
+ // C and C++ have no nested named functions, so a function the graph shows
671
+ // inside another is an extraction artifact, not a scope: tree-sitter-c
672
+ // cannot parse a macro call whose arguments are designated initializers
673
+ // (betaflight's `RESET_CONFIG(pidProfile_t, pidProfile, .pid = {…})`), and
674
+ // its error recovery runs the enclosing function_definition to the end of
675
+ // the file, nesting every function after it. Trusting that nesting rejected
676
+ // 117 real calls into pid.c on that tree; the functions are reachable.
677
+ if (NO_NESTED_FUNCTIONS.has(candidate.language))
678
+ return true;
360
679
  const qn = candidate.qualifiedName;
361
680
  if (!qn || !qn.includes('::'))
362
681
  return true;
@@ -372,6 +691,503 @@ function isLexicallyReachable(candidate, ref, context) {
372
691
  return (ref.filePath === candidate.filePath &&
373
692
  containers.some((p) => ref.line >= p.startLine && ref.line <= p.endLine));
374
693
  }
694
+ /** Languages whose module boundary is `import`/`export` (or CommonJS). */
695
+ const ESM_FAMILY = new Set(['typescript', 'tsx', 'javascript', 'jsx', 'arkts']);
696
+ /**
697
+ * A line-initial `import` statement — the marker that a JS/TS file is a MODULE
698
+ * rather than a classic script. Line-anchored and followed by a name, brace,
699
+ * star or quote, so a dynamic `import(` and the word inside a comment or string
700
+ * do not match.
701
+ */
702
+ const HAS_IMPORT_STATEMENT = /^[ \t]*import[\s{*'"]/m;
703
+ /**
704
+ * Anything the file could offer another file, in every form the extractor's own
705
+ * `isExported` flag misses. `^export` covers the declaration and later forms
706
+ * (`export const`, `export { x }`, `export default x`, `export *`); the
707
+ * CommonJS shapes cover files that never use ESM syntax at all, in both the dot
708
+ * and the bracket form; and `declare global` contributes names to every file
709
+ * whether or not the module exports anything of its own. Kept as a source test
710
+ * rather than a node scan precisely because `isExported` is set only from an
711
+ * `export_statement` ancestor, so `const x = …; export { x }` and
712
+ * `module.exports = { x }` both read as unexported on the node.
713
+ */
714
+ const HAS_ESM_EXPORT = /^[ \t]*export[\s{*]|^[ \t]*declare\s+global\b/m;
715
+ const HAS_CJS_EXPORT = /\bmodule\.exports\b|\bexports\s*[.[]/;
716
+ /**
717
+ * Per-context memo of "this file is a module that exports nothing", asked once
718
+ * per candidate FILE rather than once per reference. Derived from file source,
719
+ * so it drops with the context's file caches — clearNameMatcherMemos deletes it
720
+ * alongside INFER_SCAN_STATES.
721
+ */
722
+ const SEALED_MODULES = new WeakMap();
723
+ /**
724
+ * Whether `filePath` is a JS/TS module that exports NOTHING — an import
725
+ * statement present, no export of any form. No reference from another file can
726
+ * reach any binding in such a file, so every one of its symbols is a false
727
+ * candidate for a cross-file name match.
728
+ *
729
+ * This is the general case behind a package name capturing a same-named local:
730
+ * on `vitejs/vite`, 157 cross-file `imports` refs — every `import { defineConfig
731
+ * } from 'vite'` in the playground and the create-vite templates — resolved onto
732
+ * `playground/ssr-html/test-stacktrace.js::vite`, which is `const vite = await
733
+ * createServer(…)` at module scope in a file with zero exports. The existing
734
+ * guards cannot see it: `isLexicallyReachable` returns early for any candidate
735
+ * that is not a `function`, and the bare-import guard correctly declines because
736
+ * `vite` IS a workspace member, so the specifier really is project-local. What
737
+ * is wrong is only which node the name lands on.
738
+ *
739
+ * Deliberately narrow on three axes, because each is a class this would
740
+ * otherwise resolve wrongly in the opposite direction:
741
+ *
742
+ * - **A classic script is exempt.** Requiring an `import` statement means a
743
+ * non-module `.js` file — concatenated globals, a browser `<script>` — keeps
744
+ * its cross-file matches, where a top-level binding genuinely is reachable.
745
+ * - **CommonJS is exempt.** `module.exports` and `exports.x` are matched as
746
+ * exports, so a CJS file is never sealed.
747
+ * - **Other languages are exempt.** Go, Python, Java and the rest have no
748
+ * equivalent boundary, and several extractors hardcode `isExported`.
749
+ */
750
+ function isSealedModule(filePath, context) {
751
+ let memo = SEALED_MODULES.get(context);
752
+ if (!memo) {
753
+ memo = new Map();
754
+ SEALED_MODULES.set(context, memo);
755
+ }
756
+ const hit = memo.get(filePath);
757
+ if (hit !== undefined)
758
+ return hit;
759
+ // CommonJS assignments can execute inside template interpolations, which the
760
+ // masker blanks. Keep the conservative raw-source exemption for those forms.
761
+ // Cheapest disqualifiers first: nearly every module exports a node (asked
762
+ // without reading or decoding the file), and masking the source is only
763
+ // needed to rule out the ones that don't. (The masker only blanks text, so
764
+ // no `import` in the source means none in code.)
765
+ const exportsNode = context.fileHasExportedNode
766
+ ? context.fileHasExportedNode(filePath)
767
+ : context.getNodesInFile(filePath).some((n) => n.isExported);
768
+ const source = exportsNode ? null : context.readFile(filePath);
769
+ const sealed = !exportsNode && source !== null && source.includes('import') &&
770
+ !HAS_CJS_EXPORT.test(source) &&
771
+ (() => {
772
+ const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(source, 'typescript'));
773
+ return HAS_IMPORT_STATEMENT.test(code) && !HAS_ESM_EXPORT.test(code);
774
+ })();
775
+ memo.set(filePath, sealed);
776
+ return sealed;
777
+ }
778
+ /**
779
+ * Whether `candidate` can be named by a reference in `ref`'s file at all.
780
+ * Both name-based strategies validate their chosen candidate. Removing an
781
+ * unreachable candidate before ranking can promote an unrelated runner-up;
782
+ * rejecting the chosen target must leave the reference unresolved instead.
783
+ */
784
+ function isCrossFileReachable(candidate, ref, context) {
785
+ if (ref.language !== 'markdown' && candidate.language === 'markdown')
786
+ return false;
787
+ if (ref.referenceKind === 'calls' && ESM_FAMILY.has(candidate.language) &&
788
+ (candidate.kind === 'constant' || candidate.kind === 'variable') &&
789
+ /^=\s*require\s*\(\s*(['"])[^'"]+\.json\1\s*\)\s*;?\s*$/.test(candidate.signature ?? ''))
790
+ return false;
791
+ return (candidate.filePath === ref.filePath ||
792
+ !ESM_FAMILY.has(candidate.language) ||
793
+ !isSealedModule(candidate.filePath, context));
794
+ }
795
+ /**
796
+ * Languages in which `visibility: 'private'` on a definition means no other
797
+ * FILE can name it: a Kotlin `private fun` is file- or class-local, and the
798
+ * same holds for Java, C#, Swift, Scala, Dart and PHP members.
799
+ */
800
+ const PRIVATE_IS_FILE_LOCAL = new Set(['kotlin', 'java', 'csharp', 'swift', 'scala', 'dart', 'php']);
801
+ /** Per-context memo: node id → "this C/C++ function is declared `static`". */
802
+ const C_STATIC_MEMO = new WeakMap();
803
+ /**
804
+ * A C/C++ file that IS a translation unit. A `static` defined here is local
805
+ * to it. A `static` (typically `static inline`) in a header is a different
806
+ * thing: the header is textually included, so the function exists in every
807
+ * unit that includes it and is callable from each — MAVLink's generated
808
+ * `mavlink_msg_*.h` are nothing but such functions, 4,306 real calls on one
809
+ * betaflight tree.
810
+ */
811
+ const C_SOURCE_EXT = /\.(c|cc|cpp|cxx|c\+\+|m|mm)$/i;
812
+ /**
813
+ * Whether a C/C++ function definition carries the `static` storage class —
814
+ * read from its first source line(s), since the extractor records no storage
815
+ * class and the kernel arm would need the same field. `static` on the line
816
+ * above the name (`static void\nfoo(void)`) is the common alternative layout.
817
+ */
818
+ function isStaticCFunction(candidate, context) {
819
+ let memo = C_STATIC_MEMO.get(context);
820
+ if (!memo) {
821
+ memo = new Map();
822
+ C_STATIC_MEMO.set(context, memo);
823
+ }
824
+ const hit = memo.get(candidate.id);
825
+ if (hit !== undefined)
826
+ return hit;
827
+ const lines = context.getFileLines?.(candidate.filePath) ?? context.readFile(candidate.filePath)?.split('\n') ?? [];
828
+ const head = [lines[candidate.startLine - 2] ?? '', lines[candidate.startLine - 1] ?? ''].join('\n');
829
+ const isStatic = /(^|[\s;}])static\s/.test(head);
830
+ memo.set(candidate.id, isStatic);
831
+ return isStatic;
832
+ }
833
+ /** Per-context memo: node id → "this Rust method implements a trait". */
834
+ const RUST_TRAIT_IMPL_MEMO = new WeakMap();
835
+ /**
836
+ * Whether a Rust method sits in an `impl Trait for Type` block. Such a method
837
+ * carries no `pub` — the trait decides its visibility — so the extractor
838
+ * records it as private; it is reachable wherever the trait is. Read from the
839
+ * nearest enclosing `impl` header above the method, memoised per node.
840
+ */
841
+ function isRustTraitImplMethod(candidate, context) {
842
+ if (candidate.kind !== 'method')
843
+ return false;
844
+ let memo = RUST_TRAIT_IMPL_MEMO.get(context);
845
+ if (!memo) {
846
+ memo = new Map();
847
+ RUST_TRAIT_IMPL_MEMO.set(context, memo);
848
+ }
849
+ const hit = memo.get(candidate.id);
850
+ if (hit !== undefined)
851
+ return hit;
852
+ const lines = context.getFileLines?.(candidate.filePath) ?? context.readFile(candidate.filePath)?.split('\n') ?? [];
853
+ let isTrait = false;
854
+ for (let i = candidate.startLine - 2; i >= 0; i--) {
855
+ const line = lines[i] ?? '';
856
+ if (/^\s*(pub(\([^)]*\))?\s+)?(unsafe\s+)?impl\b/.test(line)) {
857
+ isTrait = /\sfor\s/.test(line.replace(/\/\/.*$/, ''));
858
+ break;
859
+ }
860
+ // A top-level item above the method means it was not inside an impl.
861
+ if (/^(pub(\([^)]*\))?\s+)?(fn|struct|enum|mod|trait|const|static|type)\b/.test(line))
862
+ break;
863
+ }
864
+ memo.set(candidate.id, isTrait);
865
+ return isTrait;
866
+ }
867
+ /**
868
+ * The directory a Rust file's private items are visible from: the file's own
869
+ * module subtree. `src/net.rs` and `src/net/mod.rs` own `src/net/`; a crate
870
+ * root (`lib.rs` / `main.rs`) owns its directory. A child module reaches its
871
+ * ancestors' private items (`super::`), a sibling or another crate never does.
872
+ */
873
+ function rustModuleDir(filePath) {
874
+ const base = path.posix.basename(filePath);
875
+ const dir = path.posix.dirname(filePath);
876
+ if (base === 'mod.rs' || base === 'lib.rs' || base === 'main.rs')
877
+ return dir;
878
+ return path.posix.join(dir, base.replace(/\.rs$/, ''));
879
+ }
880
+ /**
881
+ * Whether `candidate` can be NAMED from a reference in `ref`'s file at all,
882
+ * given what its language says about the definition's visibility. A
883
+ * definition the language makes file-local is not a candidate for a
884
+ * cross-file name match, however well the names agree:
885
+ *
886
+ * - **C / C++**: a `static` function defined in a SOURCE file is local to
887
+ * that translation unit; one in a header is part of every unit that
888
+ * includes it and stays visible. On a 2,109-file betaflight tree 145
889
+ * cross-file calls resolved onto a `static` in another `.c` (#1730) —
890
+ * `usbd_get_descriptor` onto the `static get_device_descriptor` of
891
+ * whichever USB class file ranked first.
892
+ * - **Kotlin, Java, C#, Swift, Scala, Dart, PHP**: `private` is class- or
893
+ * file-local. An Android `editor.apply()` resolved onto an unrelated class's
894
+ * `private fun apply`.
895
+ * - **Go**: an unexported (lowercase) identifier is package-local, and a
896
+ * package is a directory. Judged by the name's case: the extractor's
897
+ * `isExported` is unset for every Go method.
898
+ * - **Rust**: a non-`pub` item is visible to its module and that module's
899
+ * descendants, never to a sibling module or another crate — `.count()` on
900
+ * an iterator resolved onto a `fn count` in a different crate. A method in
901
+ * an `impl Trait for Type` block has the trait's visibility, not `private`.
902
+ * - **JS / TS / ArkTS**: a binding in a module that exports nothing (an
903
+ * `import` present, no `export` / CommonJS / `declare global`) is sealed —
904
+ * the vite playground's `const vite = await createServer(…)` took 157
905
+ * `import { defineConfig } from 'vite'` edges (#1719). Classic scripts,
906
+ * CommonJS, later `export { … }`, and ambient globals stay visible.
907
+ *
908
+ * Same-file candidates are always visible. Applied by ReferenceResolver to
909
+ * the target the whole name-matching pipeline settled on, so a rejection ends
910
+ * the reference unresolved: declining inside matchByExactName instead let the
911
+ * ref fall through to matchFuzzy, which then committed to a same-language
912
+ * namesake the ranking had passed over — eight such edges on one tree, all
913
+ * onto a local `const fail = …` arrow the graph does not hold. matchFuzzy
914
+ * checks its own survivor as well, since nothing runs after it.
915
+ */
916
+ function isVisibleAcrossFiles(candidate, ref, context) {
917
+ if (candidate.filePath === ref.filePath)
918
+ return true;
919
+ const lang = candidate.language;
920
+ if (lang === 'c' || lang === 'cpp') {
921
+ return (candidate.kind !== 'function' ||
922
+ !C_SOURCE_EXT.test(candidate.filePath) ||
923
+ !isStaticCFunction(candidate, context));
924
+ }
925
+ if (lang === 'go') {
926
+ // By the name's first letter, not the extractor's flag: the flag is unset
927
+ // for every Go method, exported or not.
928
+ return /^[A-Z]/.test(candidate.name) || path.posix.dirname(candidate.filePath) === path.posix.dirname(ref.filePath);
929
+ }
930
+ if (lang === 'rust') {
931
+ if (candidate.visibility !== 'private')
932
+ return true;
933
+ if (isRustTraitImplMethod(candidate, context))
934
+ return true;
935
+ const owner = rustModuleDir(candidate.filePath);
936
+ return ref.filePath.startsWith(owner + '/');
937
+ }
938
+ if (PRIVATE_IS_FILE_LOCAL.has(lang))
939
+ return candidate.visibility !== 'private';
940
+ // JS/TS/ArkTS sealed modules + markdown/JSON call-target guards (#1719).
941
+ // Same predicate matchByExactName / matchFuzzy apply to their survivors so a
942
+ // rejection here cannot fall through to a promoted runner-up.
943
+ return isCrossFileReachable(candidate, ref, context);
944
+ }
945
+ const JS_FAMILY = new Set(['typescript', 'tsx', 'javascript', 'jsx']);
946
+ /**
947
+ * Whether a JS/TS `calls` ref is a RECEIVER-LESS call — `serialize(x)`, not
948
+ * `this.serialize(x)` / `obj.serialize(x)`. The extractor emits `this.m()`
949
+ * and `super.m()` under the bare method name, so the receiver is read back
950
+ * from the call site's own line: the text at the ref's column is the call
951
+ * expression, and it starts with the name itself only when nothing precedes
952
+ * it. In JS/TS a bare call can never bind to a class method (methods need a
953
+ * receiver), so a `method` node is not a candidate for it (#1714) — the
954
+ * enclosing method itself least of all, which the same-file proximity term
955
+ * used to pick over the module-scope function the call actually means.
956
+ */
957
+ function isBareJsCall(ref, context) {
958
+ return JS_FAMILY.has(ref.language) && isReceiverLessCall(ref, context);
959
+ }
960
+ /**
961
+ * Whether a Go `calls` ref is receiver-less — `relogin(ctx)`, not
962
+ * `l.relogin(ctx)`. A Go method is only reachable through a value or a method
963
+ * expression, so a bare call (a func parameter, a local func value, a
964
+ * package-level function) is never a method, in its own package or in one the
965
+ * file does not import (#1857). Read from the source line like the JS/TS
966
+ * check, because `pkg.Factory().Method()` reaches the resolver as a bare
967
+ * `Method` ref too.
968
+ */
969
+ function isBareGoCall(ref, context) {
970
+ return ref.language === 'go' && isReceiverLessCall(ref, context);
971
+ }
972
+ /**
973
+ * Whether a PHP `calls` ref is a bare function call — `redirect($url)`,
974
+ * `view('books.show')` — rather than `$this->redirect()` / `$obj->view()` /
975
+ * `Foo::view()`. PHP has no implicit `$this`: a call written without a
976
+ * receiver can only be a function, so a method, field or property that shares
977
+ * the name is never what it calls. Name-matching used to bind BookStack's
978
+ * every `return redirect(…)` to ApiDocsController::redirect and every
979
+ * `return view(…)` to a `$view` field.
980
+ *
981
+ * PHP refs record the column of the call EXPRESSION — `$this->setPageTitle(`
982
+ * sits at `$this` — so a bare call is the one whose text at its column is the
983
+ * name itself (a leading `\` for a fully qualified function is allowed).
984
+ */
985
+ function isBarePhpCall(ref, context) {
986
+ if (ref.language !== 'php' || ref.referenceKind !== 'calls')
987
+ return false;
988
+ if (!/^\w+$/.test(ref.referenceName))
989
+ return false;
990
+ const line = context.getFileLines?.(ref.filePath)?.[ref.line - 1]
991
+ ?? context.readFile(ref.filePath)?.split('\n')[ref.line - 1];
992
+ if (line === undefined)
993
+ return false;
994
+ const at = line[ref.column] === '\\' ? ref.column + 1 : ref.column;
995
+ if (!line.startsWith(ref.referenceName, at))
996
+ return false;
997
+ CALL_OPENER.lastIndex = at + ref.referenceName.length;
998
+ if (!CALL_OPENER.test(line))
999
+ return false;
1000
+ let end = at;
1001
+ while (end > 0 && WHITESPACE.test(line[end - 1]))
1002
+ end--;
1003
+ // `$obj->name(` / `Foo::name(` / `$obj?->name(`, should a column ever land on the name.
1004
+ return !(end > 0 && (line[end - 1] === '>' || line[end - 1] === ':'));
1005
+ }
1006
+ function isReceiverLessCall(ref, context) {
1007
+ if (ref.referenceKind !== 'calls')
1008
+ return false;
1009
+ if (ref.referenceName.includes('.'))
1010
+ return false;
1011
+ const line = context.getFileLines?.(ref.filePath)?.[ref.line - 1]
1012
+ ?? context.readFile(ref.filePath)?.split('\n')[ref.line - 1];
1013
+ if (line === undefined)
1014
+ return false;
1015
+ // `^<name>\s*[(<]` at the call's column, without compiling a pattern per call.
1016
+ if (!line.startsWith(ref.referenceName, ref.column))
1017
+ return false;
1018
+ CALL_OPENER.lastIndex = ref.column + ref.referenceName.length;
1019
+ if (!CALL_OPENER.test(line))
1020
+ return false;
1021
+ // Nothing but whitespace, an operator or an opener may precede a bare call:
1022
+ // read the text before the column backwards, past trailing whitespace,
1023
+ // instead of end-anchoring a pattern that scans the prefix from its start.
1024
+ let end = Math.min(ref.column, line.length);
1025
+ while (end > 0 && WHITESPACE.test(line[end - 1]))
1026
+ end--;
1027
+ if (end === 0 || !RECEIVER_TAIL_CHAR.test(line[end - 1]))
1028
+ return true;
1029
+ let start = end;
1030
+ while (start > 0 && WORD_CHAR.test(line[start - 1]))
1031
+ start--;
1032
+ return BARE_CALL_KEYWORDS.has(line.slice(start, end));
1033
+ }
1034
+ /** `\s*[(<]` from a given index (sticky). */
1035
+ const CALL_OPENER = /\s*[(<]/y;
1036
+ const WHITESPACE = /\s/;
1037
+ const WORD_CHAR = /\w/;
1038
+ /** A character that ends a receiver: `.`, a word character, `$`, `]` or `)`. */
1039
+ const RECEIVER_TAIL_CHAR = /[.\w$\])]/;
1040
+ /** Keywords after which a name starts an expression, so the call has no receiver. */
1041
+ const BARE_CALL_KEYWORDS = new Set([
1042
+ 'return', 'await', 'yield', 'typeof', 'void', 'new', 'else', 'case', 'throw', 'in', 'of', 'instanceof', 'go', 'defer',
1043
+ ]);
1044
+ /** Per-context memo: `file\0name` → "the file binds this name locally". */
1045
+ const LOCAL_BINDING_MEMO = new WeakMap();
1046
+ const LOCAL_BINDING_SITES = new WeakMap();
1047
+ const LOCAL_BINDING_SITES_KEEP = 16;
1048
+ const VAR_DECL_SITE = /\b(?:const|let|var)\s/g;
1049
+ const FN_DECL_SITE = /\b(?:function|class)\s/g;
1050
+ function localBindingSites(filePath, source, context) {
1051
+ let cache = LOCAL_BINDING_SITES.get(context);
1052
+ if (!cache) {
1053
+ cache = new Map();
1054
+ LOCAL_BINDING_SITES.set(context, cache);
1055
+ }
1056
+ let sites = cache.get(filePath);
1057
+ if (!sites) {
1058
+ const offsets = (re) => Array.from(source.matchAll(re), (m) => m.index);
1059
+ const arrows = [];
1060
+ for (let a = source.indexOf('=>'); a !== -1; a = source.indexOf('=>', a + 2))
1061
+ arrows.push(a);
1062
+ sites = { varDecls: offsets(VAR_DECL_SITE), fnDecls: offsets(FN_DECL_SITE), arrows };
1063
+ if (cache.size >= LOCAL_BINDING_SITES_KEEP)
1064
+ cache.delete(cache.keys().next().value);
1065
+ cache.set(filePath, sites);
1066
+ }
1067
+ return sites;
1068
+ }
1069
+ /** Sticky binding patterns by name — the same names recur file after file. */
1070
+ const LOCAL_BINDING_PATTERNS = new Map();
1071
+ const LOCAL_BINDING_PATTERNS_CAP = 4096;
1072
+ const JS_BINDING_NAME = /^[\w$]+$/;
1073
+ const ARROW_HEAD_CHAR = /[\w$.]/;
1074
+ const IMPORT_BINDING_VALUE = /^\s*(?:await\s+)?(?:require|import)\s*\(/;
1075
+ function localBindingPatterns(name, flags) {
1076
+ const n = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1077
+ return {
1078
+ // `const { name } = require('./m')` / `= await import('./m')` binds an IMPORT,
1079
+ // not a shadow: the symbol lives in the other file and the call means it.
1080
+ decl: new RegExp('\\b(?:const|let|var)\\s+(?:' + n + '\\b|[{\\[][^;=]*?\\b' + n + '\\b[^;=]*?[}\\]])\\s*(?:=\\s*([^;\\n]*))?', flags),
1081
+ fn: new RegExp('\\b(?:function|class)\\s+' + n + '\\b', flags),
1082
+ // a parameter: every token before the name in the list is itself a
1083
+ // parameter (identifier, optional type, optional default) — so a string
1084
+ // argument containing the word cannot match.
1085
+ // Each earlier parameter has exactly one parse: its first non-space
1086
+ // character after the identifier picks the type (`?`/`:`), default (`=`)
1087
+ // or bare alternative. Written as `(type)?(default)?\s*`, the same
1088
+ // strings split several ways per parameter, and a failing search
1089
+ // backtracked through every combination — 30-40s per name on a vscode
1090
+ // test file whose helper takes nine `name: T = value` parameters.
1091
+ param: new RegExp('\\(\\s*(?:(?:\\.\\.\\.)?[\\w$]+(?:\\s*(?:\\?\\s*)?:[^,()]+|\\s*=[^,()]+|\\s*),\\s*)*' +
1092
+ n + '\\b(?:\\s*\\??\\s*:[^,()]*)?(?:\\s*=[^,()]*)?(?:\\s*,\\s*[^()]*)?\\)\\s*(?::[^=;{]*)?(?:=>|\\{)', flags),
1093
+ };
1094
+ }
1095
+ /**
1096
+ * Whether a JS/TS file binds `name` itself — as a `const`/`let`/`var`/
1097
+ * `function`/`class` declaration (destructuring included) or as a parameter
1098
+ * of a function or arrow. Such a binding shadows every same-named symbol in
1099
+ * other files, so a bare call to it has no cross-file candidate: the
1100
+ * `resolve` of `new Promise((resolve, reject) => …)`, a spec's
1101
+ * `const transform = await makeTransform()`, a factory's `const now =
1102
+ * options.now || (() => new Date())`. None of these is a node the graph
1103
+ * holds (a parameter, a const bound to a call result), so without this the
1104
+ * matcher hands the call to whichever other file defines the name — and
1105
+ * once methods stop being candidates for a bare call (#1714), the function
1106
+ * that was out-ranked steps in. Read from source, memoised per file+name.
1107
+ */
1108
+ function isLocallyBoundJsName(name, filePath, context) {
1109
+ let memo = LOCAL_BINDING_MEMO.get(context);
1110
+ if (!memo) {
1111
+ memo = new Map();
1112
+ LOCAL_BINDING_MEMO.set(context, memo);
1113
+ }
1114
+ const key = filePath + '\0' + name;
1115
+ const hit = memo.get(key);
1116
+ if (hit !== undefined)
1117
+ return hit;
1118
+ const source = context.readFile(filePath) ?? '';
1119
+ const bound = JS_BINDING_NAME.test(name)
1120
+ ? bindsAtSites(source, name, localBindingSites(filePath, source, context))
1121
+ : bindsAnywhere(source, name);
1122
+ memo.set(key, bound);
1123
+ return bound;
1124
+ }
1125
+ /** isLocallyBoundJsName's patterns, searched through the whole source. */
1126
+ function bindsAnywhere(source, name) {
1127
+ const { decl, fn, param } = localBindingPatterns(name, 'g');
1128
+ for (const m of source.matchAll(decl)) {
1129
+ if (!IMPORT_BINDING_VALUE.test(m[1] ?? ''))
1130
+ return true;
1131
+ }
1132
+ const n = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1133
+ return fn.test(source) || param.test(source) || new RegExp('(?:^|[^\\w$.])' + n + '\\s*=>').test(source);
1134
+ }
1135
+ /**
1136
+ * bindsAnywhere for a plain identifier, tried only where a match can start:
1137
+ * a declaration at its keyword (in order, resuming past each match exactly as
1138
+ * the global search does), a parameter at the `(` its list opens with — the
1139
+ * last `(` before an occurrence of the name, with no `)` between, since no
1140
+ * earlier parameter can hold a parenthesis — and `name =>` at each arrow.
1141
+ */
1142
+ function bindsAtSites(source, name, sites) {
1143
+ let patterns = LOCAL_BINDING_PATTERNS.get(name);
1144
+ if (!patterns) {
1145
+ patterns = localBindingPatterns(name, 'y');
1146
+ if (LOCAL_BINDING_PATTERNS.size >= LOCAL_BINDING_PATTERNS_CAP) {
1147
+ LOCAL_BINDING_PATTERNS.delete(LOCAL_BINDING_PATTERNS.keys().next().value);
1148
+ }
1149
+ LOCAL_BINDING_PATTERNS.set(name, patterns);
1150
+ }
1151
+ const { decl, fn, param } = patterns;
1152
+ let from = 0;
1153
+ for (const at of sites.varDecls) {
1154
+ if (at < from)
1155
+ continue;
1156
+ decl.lastIndex = at;
1157
+ const m = decl.exec(source);
1158
+ if (!m)
1159
+ continue;
1160
+ from = at + m[0].length;
1161
+ if (!IMPORT_BINDING_VALUE.test(m[1] ?? ''))
1162
+ return true;
1163
+ }
1164
+ for (const at of sites.fnDecls) {
1165
+ fn.lastIndex = at;
1166
+ if (fn.test(source))
1167
+ return true;
1168
+ }
1169
+ let tried = -1;
1170
+ for (let at = source.indexOf(name); at !== -1; at = source.indexOf(name, at + 1)) {
1171
+ const open = at > 0 ? source.lastIndexOf('(', at - 1) : -1;
1172
+ if (open < 0 || open === tried || source.lastIndexOf(')', at - 1) > open)
1173
+ continue;
1174
+ tried = open;
1175
+ param.lastIndex = open;
1176
+ if (param.test(source))
1177
+ return true;
1178
+ }
1179
+ // `name =>`: the name ends where the whitespace before the arrow starts.
1180
+ for (const arrow of sites.arrows) {
1181
+ let end = arrow;
1182
+ while (end > 0 && WHITESPACE.test(source[end - 1]))
1183
+ end--;
1184
+ const start = end - name.length;
1185
+ if (start >= 0 && source.startsWith(name, start) && (start === 0 || !ARROW_HEAD_CHAR.test(source[start - 1]))) {
1186
+ return true;
1187
+ }
1188
+ }
1189
+ return false;
1190
+ }
375
1191
  /**
376
1192
  * Try to resolve a reference by exact name match
377
1193
  */
@@ -385,15 +1201,65 @@ function matchByExactName(ref, context) {
385
1201
  // unresolved import refs each scored K same-named import candidates through
386
1202
  // findBestMatch — O(K²) per package, the dominant cost of "Resolving refs" on
387
1203
  // large import-heavy (front-end + back-end) repos (#915).
388
- const candidates = applyLanguageGate(context.getNodesByName(ref.referenceName), ref)
389
- .filter((n) => n.kind !== 'import')
1204
+ const bareJs = isBareJsCall(ref, context);
1205
+ const bareGo = isBareGoCall(ref, context);
1206
+ const barePhp = isBarePhpCall(ref, context);
1207
+ if (bareJs) {
1208
+ const storeAction = matchJsStoreBindingCall(ref, context);
1209
+ if (storeAction)
1210
+ return storeAction;
1211
+ }
1212
+ // Every rule below judges one candidate on its own, so they run as ONE pass,
1213
+ // the kind/language checks before the ones that read source: a common name
1214
+ // has thousands of same-named nodes, and a chain of filters copied that
1215
+ // list once per rule for every reference.
1216
+ const valueRef = ref.referenceKind === 'references' || ref.referenceKind === 'function_ref';
1217
+ const importRef = ref.referenceKind === 'imports';
1218
+ const inheritanceRef = (0, types_1.isInheritanceRef)(ref);
1219
+ const sameName = context.getNodesByName(ref.referenceName);
1220
+ // `NAME(...)` where NAME is a function-like macro somewhere in the project is
1221
+ // an expansion or a call to a same-named function — never the macro itself
1222
+ // (#1839), and never a type that happens to share the name (#2070: expat's
1223
+ // `PREFIX(scanRef)(…)` bound to an unrelated `struct PREFIX`). Keep
1224
+ // upstream's language gate on the chosen result.
1225
+ const cMacroCall = ref.referenceKind === 'calls' && (ref.language === 'c' || ref.language === 'cpp') &&
1226
+ sameName.some((n) => n.kind === 'constant' && types_1.CPP_DEFINE_SIGNATURE.test(n.signature ?? ''));
1227
+ const candidates = sameName.filter((n) => !(cMacroCall && n.kind !== 'function' && n.kind !== 'method') &&
1228
+ // Type/value references retain same-family eligibility: a native namesake
1229
+ // must not hide the actual web type. Calls still gate only the winner.
1230
+ (!valueRef || sameLanguageFamily(n.language, ref.language)) &&
1231
+ n.kind !== 'import' &&
1232
+ // A receiver-less JS/TS or Go call cannot reach a method (#1714, #1857).
1233
+ !((bareJs || bareGo) && n.kind === 'method') &&
1234
+ // A bare PHP call is a function call: nothing else is callable without a receiver.
1235
+ !(barePhp && n.kind !== 'function') &&
1236
+ // An `extends`/`implements` ref names a supertype, so anything that can't
1237
+ // BE one is not a candidate at all. This is eligibility, not
1238
+ // ranking: kind is only a scoring bonus below (and none is awarded for
1239
+ // inheritance refs), so without this a same-named `enum_member` outranked
1240
+ // the real `trait`, and as the sole candidate was adopted outright by the
1241
+ // single-match shortcut. Restricting the pool BEFORE ranking lets the
1242
+ // legitimate supertype win instead of merely dropping the false edge.
1243
+ (!inheritanceRef || (0, types_1.isSupertypeTarget)(n)) &&
1244
+ // Likewise for `imports`: a member that only exists inside a type is not
1245
+ // importable, so it is not a candidate. Without this a `path`/`id`/`url`
1246
+ // import resolved to some interface's same-named property.
1247
+ (!importRef || (0, types_1.isImportableKind)(n.kind)) &&
390
1248
  // Nested locals are only reachable from inside their container (#1230).
391
- .filter((n) => isLexicallyReachable(n, ref, context));
1249
+ isLexicallyReachable(n, ref, context) &&
1250
+ // Preserve import ranking; calls reject the winner without promoting another.
1251
+ (!importRef || n.filePath === ref.filePath ||
1252
+ !ESM_FAMILY.has(n.language) || !isSealedModule(n.filePath, context)) &&
1253
+ // A name the file binds itself (a parameter, a const) shadows every other
1254
+ // file's symbol of that name, so a bare call has no cross-file candidate.
1255
+ !(bareJs && n.filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context)));
392
1256
  if (candidates.length === 0) {
393
1257
  return null;
394
1258
  }
395
1259
  // If only one match, use it — but penalize cross-language matches
396
1260
  if (candidates.length === 1) {
1261
+ if (!isCrossFileReachable(candidates[0], ref, context))
1262
+ return null;
397
1263
  const isCrossLanguage = candidates[0].language !== ref.language;
398
1264
  return {
399
1265
  original: ref,
@@ -412,7 +1278,7 @@ function matchByExactName(ref, context) {
412
1278
  }
413
1279
  // Multiple matches - try to narrow down
414
1280
  const bestMatch = findBestMatch(ref, candidates, context);
415
- if (bestMatch) {
1281
+ if (bestMatch && isCrossFileReachable(bestMatch, ref, context)) {
416
1282
  // Lower confidence when the match is from a distant/unrelated module
417
1283
  const proximity = computePathProximity(ref.filePath, bestMatch.filePath);
418
1284
  const confidence = proximity >= 30 ? 0.7 : 0.4;
@@ -467,6 +1333,32 @@ function matchByQualifiedName(ref, context) {
467
1333
  };
468
1334
  }
469
1335
  }
1336
+ // Erlang qualified refs (#1610): every erlang function's qualifiedName
1337
+ // carries its arity (`mod::f/2`), and refs carry the call-site arity when it
1338
+ // is statically known.
1339
+ if (ref.language === 'erlang' && ref.referenceName.includes('::')) {
1340
+ // A ref WITH arity that missed the exact lookup names an arity that isn't
1341
+ // defined (or a module out of repo). Never fall through to the partial
1342
+ // match — its "last segment" would be the arity digits — and never settle
1343
+ // for a sibling arity: silent beats wrong.
1344
+ if (/\/\d{1,3}$/.test(ref.referenceName))
1345
+ return null;
1346
+ // An arity-LESS qualified ref (dynamic MFA whose args list wasn't a
1347
+ // static literal): resolve only when the module defines exactly ONE arity
1348
+ // of that function; several arities with no signal is a guess.
1349
+ const base = ref.referenceName.slice(ref.referenceName.lastIndexOf('::') + 2);
1350
+ const prefix = `${ref.referenceName}/`;
1351
+ const arityCands = keepForRef(context.getNodesByName(base)).filter((n) => n.qualifiedName.startsWith(prefix) && /^\d{1,3}$/.test(n.qualifiedName.slice(prefix.length)));
1352
+ if (arityCands.length === 1) {
1353
+ return {
1354
+ original: ref,
1355
+ targetNodeId: arityCands[0].id,
1356
+ confidence: 0.85,
1357
+ resolvedBy: 'qualified-name',
1358
+ };
1359
+ }
1360
+ return null;
1361
+ }
470
1362
  // Try partial qualified name match — again preferring the call site's own
471
1363
  // file when more than one symbol's qualifiedName ends with the reference.
472
1364
  const parts = ref.referenceName.split(/[:.]/);
@@ -486,6 +1378,11 @@ function matchByQualifiedName(ref, context) {
486
1378
  }
487
1379
  return null;
488
1380
  }
1381
+ /** A node a `Receiver.method()` call can name as the method's owning type. */
1382
+ function isMethodOwnerKind(n) {
1383
+ return n.kind === 'class' || n.kind === 'struct' || n.kind === 'union' || n.kind === 'interface' ||
1384
+ (n.language === 'scala' && n.kind === 'module');
1385
+ }
489
1386
  /**
490
1387
  * When a symbol name is ambiguous across files, prefer the candidate(s) declared
491
1388
  * in the call site's own file, keeping the rest in their original order (#1079).
@@ -507,6 +1404,223 @@ function preferCallSiteFile(nodes, callSiteFile) {
507
1404
  }
508
1405
  return same.length ? [...same, ...other] : nodes;
509
1406
  }
1407
+ /**
1408
+ * Languages whose object literals declare callable members — `export const
1409
+ * api = { call() {…}, get: () => {…} }` used as a namespace (#1573).
1410
+ */
1411
+ const OBJECT_LITERAL_LANGUAGES = new Set(['typescript', 'tsx', 'javascript', 'jsx', 'arkts']);
1412
+ /** True when `inner`'s source range lies within `outer`'s (lines, then columns on a shared line). */
1413
+ function rangeWithin(inner, outer) {
1414
+ const innerEnd = inner.endLine ?? inner.startLine;
1415
+ const outerEnd = outer.endLine ?? outer.startLine;
1416
+ if (inner.startLine < outer.startLine || innerEnd > outerEnd)
1417
+ return false;
1418
+ if (inner.startLine === outer.startLine && inner.startColumn < outer.startColumn)
1419
+ return false;
1420
+ if (innerEnd === outerEnd && inner.endColumn > outer.endColumn)
1421
+ return false;
1422
+ return true;
1423
+ }
1424
+ function sameRange(a, b) {
1425
+ return (a.startLine === b.startLine &&
1426
+ a.startColumn === b.startColumn &&
1427
+ (a.endLine ?? a.startLine) === (b.endLine ?? b.startLine) &&
1428
+ a.endColumn === b.endColumn);
1429
+ }
1430
+ /**
1431
+ * Resolve `container.member` where `container` is a VALUE holding an object
1432
+ * literal — `export const api = { call() {…}, get: () => {…} }` used as the
1433
+ * module's namespace (#1573). The members are extracted as plain functions
1434
+ * with BARE qualified names inside the constant's source extent (there is no
1435
+ * `api::call`), so neither the `Container::member` lookup the class-shaped
1436
+ * kinds use (#825) nor the declared-type inference for singleton instances
1437
+ * (#1292) can reach them, and every such call resolved to nothing — or, via
1438
+ * an import, to the constant itself. This looks the member up by CONTAINMENT:
1439
+ * a node named `member` whose range lies inside the container's, in the
1440
+ * container's own file. A helper declared inside a member's body is not a
1441
+ * member and is skipped; nothing else in the file can donate a match. Calls
1442
+ * take callable kinds only; other references accept value members too.
1443
+ */
1444
+ function resolveObjectLiteralMember(container, member, ref, context, confidence, resolvedBy) {
1445
+ if (container.kind !== 'constant' && container.kind !== 'variable')
1446
+ return null;
1447
+ if (!OBJECT_LITERAL_LANGUAGES.has(container.language))
1448
+ return null;
1449
+ if (!sameLanguageFamily(container.language, ref.language))
1450
+ return null;
1451
+ const inFile = context.getNodesInFile(container.filePath);
1452
+ const callable = (n) => n.kind === 'function' || n.kind === 'method';
1453
+ const valueMember = (n) => callable(n) || n.kind === 'property' || n.kind === 'variable' || n.kind === 'constant';
1454
+ const accepts = ref.referenceKind === 'calls' ? callable : valueMember;
1455
+ const inside = inFile.filter((n) => n.id !== container.id && rangeWithin(n, container));
1456
+ const property = objectLiteralProperty(container, member, context);
1457
+ if (property === null || property?.binding)
1458
+ return null;
1459
+ let candidates = inside.filter((n) => n.name === member && accepts(n) && (!property || property.contains(n)));
1460
+ if (candidates.length === 0)
1461
+ return null;
1462
+ // Drop a candidate nested inside ANOTHER callable's body within the literal
1463
+ // (`{ run() { const call = () => {}; } }` — `call` is `run`'s local, not a
1464
+ // member). Strict containment: an identically-ranged sibling node for the
1465
+ // same member (a property node over an arrow function) is not a body.
1466
+ const bodies = inside.filter(callable);
1467
+ candidates = candidates.filter((c) => !bodies.some((b) => b.id !== c.id && !sameRange(b, c) && rangeWithin(c, b)));
1468
+ if (candidates.length === 0)
1469
+ return null;
1470
+ // Several survivors (a property AND a function for one arrow member, say):
1471
+ // a callable first, then the earliest in source order.
1472
+ candidates.sort((a, b) => {
1473
+ const ca = callable(a) ? 0 : 1;
1474
+ const cb = callable(b) ? 0 : 1;
1475
+ if (ca !== cb)
1476
+ return ca - cb;
1477
+ return a.startLine - b.startLine || a.startColumn - b.startColumn;
1478
+ });
1479
+ return {
1480
+ original: ref,
1481
+ targetNodeId: candidates[0].id,
1482
+ confidence,
1483
+ resolvedBy,
1484
+ };
1485
+ }
1486
+ /**
1487
+ * The binding an object-literal member names when it is a shorthand property
1488
+ * (`{ getUser }`) or a pair whose value is a bare identifier (`{ getUser:
1489
+ * fetchUser }`) — the usual way an API module assembles its namespace from
1490
+ * standalone functions (#1932). Only the literal's own members count: a member
1491
+ * of a nested object, a word inside a member's body, a comment or a string
1492
+ * never donates one. Null when the member is absent, or when its value is not
1493
+ * a bare identifier (`{ fn: 1 }` names nothing).
1494
+ */
1495
+ function objectLiteralMemberBinding(container, member, context) {
1496
+ return objectLiteralProperty(container, member, context)?.binding ?? null;
1497
+ }
1498
+ /** The last own property wins; an unknown spread/computed key invalidates earlier evidence. */
1499
+ function objectLiteralProperty(container, member, context) {
1500
+ const lines = context.getFileLines?.(container.filePath) ?? context.readFile(container.filePath)?.split('\n');
1501
+ if (!lines)
1502
+ return undefined;
1503
+ const extentLines = lines.slice(container.startLine - 1, container.endLine);
1504
+ if (!extentLines.length)
1505
+ return undefined;
1506
+ extentLines[extentLines.length - 1] = extentLines[extentLines.length - 1].slice(0, container.endColumn);
1507
+ extentLines[0] = extentLines[0].slice(container.startColumn);
1508
+ const extent = (0, strip_comments_1.stripCommentsForRegex)(extentLines.join('\n'), 'typescript');
1509
+ const code = (0, strip_comments_1.blankStringContents)(extent);
1510
+ // Start at THIS declarator, including its columns, never a sibling on the same line.
1511
+ const open = /^[^=]*=\s*(?:(?:Object\.(?:freeze|seal)\s*)?\(\s*)*\{/.exec(code);
1512
+ if (!open)
1513
+ return undefined;
1514
+ const members = [];
1515
+ let depth = 0;
1516
+ let start = open[0].length;
1517
+ for (let i = start; i < code.length; i++) {
1518
+ const ch = code[i];
1519
+ if (ch === '{' || ch === '(' || ch === '[')
1520
+ depth++;
1521
+ else if (ch === ')' || ch === ']')
1522
+ depth--;
1523
+ else if (ch === '}') {
1524
+ if (depth === 0) {
1525
+ members.push({ start, end: i });
1526
+ break;
1527
+ }
1528
+ depth--;
1529
+ }
1530
+ else if (ch === ',' && depth === 0) {
1531
+ members.push({ start, end: i });
1532
+ start = i + 1;
1533
+ }
1534
+ }
1535
+ let selected = null;
1536
+ for (const part of members) {
1537
+ const text = extent.slice(part.start, part.end).trim();
1538
+ if (/^(?:\.\.\.|\[)/.test(text)) {
1539
+ selected = null;
1540
+ continue;
1541
+ }
1542
+ const key = /^(?:(?:async|get|set)\s+)?\*?\s*(?:([A-Za-z_$][\w$]*)|['"]([^'"\\]*)['"])(?=\s*(?:[:(<,=]|$))/.exec(text);
1543
+ if ((key?.[1] ?? key?.[2]) !== member)
1544
+ continue;
1545
+ const value = text.slice(key[0].length).trim();
1546
+ const binding = value === '' ? member : /^:\s*([A-Za-z_$][\w$]*)$/.exec(value)?.[1] ?? null;
1547
+ selected = { ...part, binding };
1548
+ }
1549
+ if (!selected)
1550
+ return null;
1551
+ const offset = (node) => {
1552
+ let result = node.startColumn - container.startColumn;
1553
+ for (let line = container.startLine; line < node.startLine; line++)
1554
+ result += lines[line - 1].length + 1;
1555
+ return result;
1556
+ };
1557
+ const property = selected;
1558
+ return { binding: property.binding, contains: (node) => offset(node) >= property.start && offset(node) < property.end };
1559
+ }
1560
+ /**
1561
+ * Shared lexical lookup for namespace-object aliases (#1932): `api.getUser()` where
1562
+ * `api` is `const api = { getUser }` (or `{ getUser: fetchUser }`) in the
1563
+ * object's own file. The member's function is declared OUTSIDE the literal, so
1564
+ * containment (`resolveObjectLiteralMember`) finds nothing. Follow the binding
1565
+ * the member names — a symbol of this file, else one of its imports — unless a
1566
+ * parameter or nearer declaration shadows that name where the literal is
1567
+ * written (the edge would then name the wrong function).
1568
+ */
1569
+ function resolveObjectLiteralBinding(container, member, ref, context) {
1570
+ const binding = objectLiteralMemberBinding(container, member, context);
1571
+ if (!binding)
1572
+ return null;
1573
+ const at = { ...ref, filePath: container.filePath, language: container.language,
1574
+ fromNodeId: container.id, line: container.startLine, column: container.startColumn };
1575
+ const inFile = context.getNodesInFile(container.filePath);
1576
+ if (inFile.some((n) => (n.kind === 'function' || n.kind === 'method') &&
1577
+ rangeWithin(container, n) && n.signature &&
1578
+ hasParameterBinding(`${n.signature} {`, binding.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))))
1579
+ return null;
1580
+ const lines = context.getFileLines?.(container.filePath) ?? context.readFile(container.filePath)?.split('\n');
1581
+ if (!lines)
1582
+ return null;
1583
+ const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(lines.join('\n'), 'typescript'));
1584
+ const offsets = [0];
1585
+ for (let i = 0; i < code.length; i++)
1586
+ if (code[i] === '\n')
1587
+ offsets.push(i + 1);
1588
+ const scopeAt = (node) => {
1589
+ const end = (offsets[node.startLine - 1] ?? code.length) + node.startColumn;
1590
+ const scope = [];
1591
+ for (let i = 0; i < end; i++) {
1592
+ if (code[i] === '{')
1593
+ scope.push(i);
1594
+ else if (code[i] === '}')
1595
+ scope.pop();
1596
+ }
1597
+ return scope;
1598
+ };
1599
+ const scope = scopeAt(container);
1600
+ const callable = (n) => n.kind === 'function' || n.kind === 'method' || n.kind === 'class';
1601
+ const accepts = ref.referenceKind === 'calls'
1602
+ ? callable
1603
+ : (n) => callable(n) || n.kind === 'constant' || n.kind === 'variable' || n.kind === 'component';
1604
+ const locals = inFile
1605
+ .filter((n) => n.name === binding && n.id !== container.id &&
1606
+ ['function', 'class', 'constant', 'variable', 'component'].includes(n.kind))
1607
+ .map((node) => ({ node, scope: scopeAt(node) }))
1608
+ .filter((entry) => entry.scope.every((position, i) => scope[i] === position))
1609
+ .sort((a, b) => b.scope.length - a.scope.length);
1610
+ // Select the lexical binding BEFORE checking callability: a nearer value
1611
+ // shadows an outer function even if that value cannot be called.
1612
+ const local = locals[0]?.node;
1613
+ if (local)
1614
+ return accepts(local)
1615
+ ? { original: ref, targetNodeId: local.id, confidence: 0.85, resolvedBy: 'instance-method' }
1616
+ : null;
1617
+ const imported = context.resolveImport?.({ ...at, referenceName: binding });
1618
+ const target = imported ? context.getNodeById?.(imported.targetNodeId) : null;
1619
+ if (target && accepts(target)) {
1620
+ return { original: ref, targetNodeId: target.id, confidence: 0.85, resolvedBy: 'instance-method' };
1621
+ }
1622
+ return null;
1623
+ }
510
1624
  // Exported for the precedence unit tests (#1079): they assert the
511
1625
  // preferredFqn → same-file → matches[0] ordering directly.
512
1626
  function resolveMethodOnType(typeName, methodName, ref, context, confidence, resolvedBy,
@@ -542,7 +1656,7 @@ depth = 0) {
542
1656
  for (const m of methodCandidates) {
543
1657
  if (m.kind !== 'method')
544
1658
  continue;
545
- if (m.language !== ref.language)
1659
+ if (!sameLanguageFamily(m.language, ref.language))
546
1660
  continue;
547
1661
  const qn = m.qualifiedName;
548
1662
  if (qn === want || qn.endsWith(`::${want}`)) {
@@ -597,6 +1711,8 @@ depth = 0) {
597
1711
  // block, so Java/Kotlin import disambiguation — whose target is intentionally
598
1712
  // in ANOTHER file (#314) — is unaffected: that block returns early whenever
599
1713
  // an import FQN pins the class.
1714
+ if (ref.referenceKind === 'function_ref' && matches.length !== 1)
1715
+ return null;
600
1716
  const ordered = preferCallSiteFile(matches, ref.filePath);
601
1717
  return {
602
1718
  original: ref,
@@ -736,12 +1852,12 @@ function lookupCalleeReturnType(callee, ref, context) {
736
1852
  }
737
1853
  return candidates.find((n) => n.kind === 'function')?.returnType ?? null;
738
1854
  }
739
- /** Does the graph contain a class/struct named `name`'s last segment? */
1855
+ /** Does the graph contain an aggregate type named `name`'s last segment? */
740
1856
  function cppClassExists(name, ref, context) {
741
1857
  const last = cppLastSegment(name);
742
1858
  return context
743
1859
  .getNodesByName(last)
744
- .some((n) => (n.kind === 'class' || n.kind === 'struct') && n.language === ref.language);
1860
+ .some((n) => (n.kind === 'class' || n.kind === 'struct' || n.kind === 'union') && n.language === ref.language);
745
1861
  }
746
1862
  /**
747
1863
  * Infer the class produced by a C++ call/construction expression, using return
@@ -1065,6 +2181,8 @@ function normalizeInferredTypeName(raw) {
1065
2181
  const PATTERN_MEMO = new Map();
1066
2182
  const PATTERN_MEMO_CAP = 8192;
1067
2183
  const INFER_SCAN_STATES = new WeakMap();
2184
+ const AWAITED_TYPE_MEMO = new WeakMap();
2185
+ const AWAITED_FILES = new WeakMap();
1068
2186
  function getInferScanStates(context) {
1069
2187
  let m = INFER_SCAN_STATES.get(context);
1070
2188
  if (!m) {
@@ -1076,6 +2194,19 @@ function getInferScanStates(context) {
1076
2194
  /** Drop the per-context scan states (see ReferenceResolver.clearCaches). */
1077
2195
  function clearNameMatcherMemos(context) {
1078
2196
  INFER_SCAN_STATES.delete(context);
2197
+ PYTHON_MEMBER_LINES.delete(context);
2198
+ AWAITED_TYPE_MEMO.delete(context);
2199
+ AWAITED_FILES.delete(context);
2200
+ C_STATIC_MEMO.delete(context);
2201
+ RUST_TRAIT_IMPL_MEMO.delete(context);
2202
+ SEALED_MODULES.delete(context);
2203
+ LOCAL_BINDING_MEMO.delete(context);
2204
+ LOCAL_BINDING_SITES.delete(context);
2205
+ SELECTOR_NAMES.delete(context);
2206
+ GET_STATE_FILES.delete(context);
2207
+ TS_FIELD_DECL_MEMO.delete(context);
2208
+ TS_CLASS_LINES.delete(context);
2209
+ TARGET_LANGUAGE.delete(context);
1079
2210
  }
1080
2211
  function memoPatterns(key, build) {
1081
2212
  const hit = PATTERN_MEMO.get(key);
@@ -1114,6 +2245,12 @@ function buildLocalReceiverTypePatterns(language, r) {
1114
2245
  case 'python':
1115
2246
  return [
1116
2247
  new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // lg = Logger(...)
2248
+ // A quoted forward reference (`lg: "Logger"`, `lg: 'pkg.Logger'`) is the
2249
+ // same annotation — and what every file under `from __future__ import
2250
+ // annotations` or with a not-yet-defined class writes. The unquoted
2251
+ // pattern below stopped at the quote and read no type at all, so the
2252
+ // call produced no edge (#1684). Tried first: it is the stricter shape.
2253
+ new RegExp(`\\b${r}\\b\\s*:\\s*["']([A-Z][\\w.]*)["']`), // lg: "Logger"
1117
2254
  new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)`), // lg: Logger (PEP 526)
1118
2255
  ];
1119
2256
  case 'java':
@@ -1410,6 +2547,191 @@ function inferLocalReceiverType(receiverName, ref, context) {
1410
2547
  }
1411
2548
  return null;
1412
2549
  }
2550
+ /** Infer only a visible awaited binding and its actual local/imported callee.
2551
+ * The signature already carries the return annotation in both extractors, so
2552
+ * multiline declarations and neighboring declarations cannot donate a type.
2553
+ * `null` means no awaited evidence; a null NAME means an awaited receiver whose
2554
+ * type is unknown, which must not fall back to an unrelated method name. */
2555
+ function inferEsmAwaitedCallType(receiverName, ref, context) {
2556
+ if (!/^[A-Za-z_$][\w$]*$/.test(receiverName))
2557
+ return null;
2558
+ let files = AWAITED_FILES.get(context);
2559
+ if (!files) {
2560
+ files = new Map();
2561
+ AWAITED_FILES.set(context, files);
2562
+ }
2563
+ let file = files.get(ref.filePath);
2564
+ if (file === undefined) {
2565
+ const source = context.readFile(ref.filePath) ?? '';
2566
+ file = null;
2567
+ // Raw eligibility is cheap; sanitize and index scopes only when a ref
2568
+ // actually uses one of these names. Comments cannot donate a binding:
2569
+ // the names are checked again after sanitizing on the first real lookup.
2570
+ const names = new Set([...source.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*await\s+[\w$]+\s*\(/g)].map(m => m[1]));
2571
+ if (names.size)
2572
+ file = { code: source, ready: false, names, offsets: [], scopes: [], declarations: new Map() };
2573
+ if (files.size >= 256)
2574
+ files.delete(files.keys().next().value);
2575
+ files.set(ref.filePath, file);
2576
+ }
2577
+ if (!file?.names.has(receiverName))
2578
+ return null;
2579
+ if (!file.ready) {
2580
+ const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(file.code, 'typescript'));
2581
+ const names = new Set([...code.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*await\s+[\w$]+\s*\(/g)].map(m => m[1]));
2582
+ const offsets = [0];
2583
+ const scopes = [{ start: -1, end: code.length, parent: -1 }];
2584
+ const stack = [0];
2585
+ for (let i = 0; i < code.length; i++) {
2586
+ if (code[i] === '\n')
2587
+ offsets.push(i + 1);
2588
+ if (code[i] === '{') {
2589
+ scopes.push({ start: i, end: code.length, parent: stack[stack.length - 1] });
2590
+ stack.push(scopes.length - 1);
2591
+ }
2592
+ else if (code[i] === '}' && stack.length > 1)
2593
+ scopes[stack.pop()].end = i;
2594
+ }
2595
+ const declarations = new Map();
2596
+ for (const m of code.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*/g)) {
2597
+ if (!names.has(m[1]))
2598
+ continue;
2599
+ const entries = declarations.get(m[1]) ?? [];
2600
+ entries.push({ index: m.index, length: m[0].length });
2601
+ declarations.set(m[1], entries);
2602
+ }
2603
+ Object.assign(file, { code, ready: true, names, offsets, scopes, declarations });
2604
+ if (!names.has(receiverName))
2605
+ return null;
2606
+ }
2607
+ let memo = AWAITED_TYPE_MEMO.get(context);
2608
+ if (!memo) {
2609
+ memo = new Map();
2610
+ AWAITED_TYPE_MEMO.set(context, memo);
2611
+ }
2612
+ const key = `${ref.filePath}|${ref.line}|${ref.column}|${receiverName}`;
2613
+ if (memo.has(key))
2614
+ return memo.get(key);
2615
+ const result = resolveAwaitedCallType(receiverName, file, ref, context);
2616
+ if (memo.size >= PATTERN_MEMO_CAP)
2617
+ memo.delete(memo.keys().next().value);
2618
+ memo.set(key, result);
2619
+ return result;
2620
+ }
2621
+ function resolveAwaitedCallType(receiverName, file, ref, context) {
2622
+ const unknown = { name: null, filePath: ref.filePath };
2623
+ const end = (file.offsets[ref.line - 1] ?? file.code.length) + ref.column;
2624
+ const code = file.code.slice(0, end);
2625
+ const escaped = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2626
+ // Locate scopes in the precomputed brace tree. Rescanning the entire file
2627
+ // for every candidate binding made large test files quadratic in refs.
2628
+ const scopeAt = (offset) => {
2629
+ let lo = 0, hi = file.scopes.length;
2630
+ while (lo + 1 < hi) {
2631
+ const mid = (lo + hi) >>> 1;
2632
+ if (file.scopes[mid].start < offset)
2633
+ lo = mid;
2634
+ else
2635
+ hi = mid;
2636
+ }
2637
+ while (lo > 0 && file.scopes[lo].end < offset)
2638
+ lo = file.scopes[lo].parent;
2639
+ return lo;
2640
+ };
2641
+ const visibleAt = (declaration, use) => {
2642
+ const ancestor = scopeAt(declaration);
2643
+ for (let scope = scopeAt(use); scope >= 0; scope = file.scopes[scope].parent)
2644
+ if (scope === ancestor)
2645
+ return true;
2646
+ return false;
2647
+ };
2648
+ const binding = [...(file.declarations.get(receiverName) ?? [])].reverse()
2649
+ .find(m => m.index < end && visibleAt(m.index, end));
2650
+ if (!binding)
2651
+ return null;
2652
+ const init = code.slice(binding.index + binding.length);
2653
+ if (!/^await\b/.test(init))
2654
+ return null;
2655
+ // Only a bare call result, not a following member/index/conditional expression.
2656
+ const call = /^await\s+([A-Za-z_$][\w$]*)\s*\(/.exec(init);
2657
+ if (!call)
2658
+ return null;
2659
+ let depth = 1, callEnd = call[0].length;
2660
+ for (; callEnd < init.length && depth; callEnd++) {
2661
+ if (init[callEnd] === '(')
2662
+ depth++;
2663
+ else if (init[callEnd] === ')')
2664
+ depth--;
2665
+ }
2666
+ if (depth)
2667
+ return unknown;
2668
+ const tail = init.slice(callEnd);
2669
+ // A following property/index/call is not the callee's annotated value.
2670
+ if (!/^[ \t]*(?:;|\r?\n(?![ \t]*[.(\[?]))/.test(tail))
2671
+ return unknown;
2672
+ const rest = tail;
2673
+ if (new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${escaped}\\b|\\{[^}]*\\b${escaped}\\b)`).test(rest) ||
2674
+ new RegExp(`\\b${escaped}\\s*=(?!=)`).test(rest) || hasParameterBinding(rest, escaped))
2675
+ return unknown;
2676
+ const bindingLine = file.code.slice(0, binding.index).split('\n').length;
2677
+ const bindingRef = { ...ref, line: bindingLine, column: binding.index - file.offsets[bindingLine - 1] };
2678
+ const callee = call[1];
2679
+ const calleeEscaped = callee.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2680
+ if (context.getNodesInFile(ref.filePath).some(n => (n.kind === 'function' || n.kind === 'method') && n.startLine <= bindingLine && n.endLine >= bindingLine &&
2681
+ n.signature && hasParameterBinding(`${n.signature} {`, calleeEscaped)))
2682
+ return unknown;
2683
+ const imported = context.getImportMappings(ref.filePath, ref.language).some(m => m.localName === callee);
2684
+ let declaring;
2685
+ if (imported) {
2686
+ if (importShadowedAt(callee, bindingRef, context))
2687
+ return unknown;
2688
+ const resolved = context.resolveImport?.({ ...bindingRef, referenceName: callee, referenceKind: 'calls' });
2689
+ declaring = resolved ? context.getNodeById?.(resolved.targetNodeId) ?? undefined : undefined;
2690
+ }
2691
+ else {
2692
+ const local = context.getNodesByName(callee).filter(n => n.kind === 'function' &&
2693
+ n.filePath === ref.filePath && ESM_FAMILY.has(n.language) && isLexicallyReachable(n, bindingRef, context));
2694
+ if (local.length === 1)
2695
+ declaring = local[0];
2696
+ }
2697
+ if (!declaring || declaring.kind !== 'function' || !declaring.signature)
2698
+ return unknown;
2699
+ if (!imported) {
2700
+ const beforeBinding = code.slice(0, binding.index);
2701
+ const shadows = new RegExp(`\\b(?:const|let|var)\\s+${calleeEscaped}\\b`, 'g');
2702
+ for (const shadow of beforeBinding.matchAll(shadows)) {
2703
+ if (!visibleAt(shadow.index, binding.index))
2704
+ continue;
2705
+ // A typed arrow function may itself be the declared local factory.
2706
+ const line = file.code.slice(0, shadow.index).split('\n').length;
2707
+ if (line !== declaring.startLine || shadow.index - file.offsets[line - 1] > declaring.startColumn)
2708
+ return unknown;
2709
+ }
2710
+ }
2711
+ const signature = declaring.signature;
2712
+ const annotation = signature.slice(signature.lastIndexOf(')') + 1).match(/^\s*:\s*([\s\S]+)$/)?.[1]?.trim();
2713
+ if (!annotation)
2714
+ return unknown;
2715
+ // Do not turn unions, arrays, object/function types, or conditional types into
2716
+ // a project class. Await recursively unwraps promises, but this narrow path
2717
+ // accepts a single named Promise<T> layer only.
2718
+ const returned = annotation.match(/^Promise\s*<\s*([\w$]+)\s*>$/)?.[1] ?? annotation;
2719
+ if (!/^[A-Za-z_$][\w$]*$/.test(returned))
2720
+ return unknown;
2721
+ if (js_builtins_1.TS_PRIMITIVE_TYPES.has(returned))
2722
+ return { name: returned, filePath: declaring.filePath };
2723
+ const typeRef = { ...bindingRef, fromNodeId: declaring.id, filePath: declaring.filePath,
2724
+ language: declaring.language, line: declaring.startLine, column: declaring.startColumn,
2725
+ referenceName: returned, referenceKind: 'references' };
2726
+ const typeImport = context.getImportMappings(declaring.filePath, declaring.language).some(m => m.localName === returned);
2727
+ const resolved = typeImport ? context.resolveImport?.(typeRef) : null;
2728
+ const typeNode = resolved ? context.getNodeById?.(resolved.targetNodeId) :
2729
+ context.getNodesByName(returned).find(n => n.filePath === declaring.filePath &&
2730
+ ESM_FAMILY.has(n.language) && (n.kind === 'class' || n.kind === 'interface'));
2731
+ if (!typeNode || (typeNode.kind !== 'class' && typeNode.kind !== 'interface'))
2732
+ return unknown;
2733
+ return { name: typeNode.name, filePath: typeNode.filePath };
2734
+ }
1413
2735
  /**
1414
2736
  * Patterns that recover a PHP class property's declared type for a
1415
2737
  * `$this->prop` receiver. Deliberately NOT localReceiverTypePatterns: only
@@ -1547,6 +2869,15 @@ function matchMethodCall(ref, context) {
1547
2869
  return null;
1548
2870
  return resolveMethodOnType(inferredType, phpMethodName, ref, context, 0.9, 'instance-method', importedFqnOf(inferredType, ref, context));
1549
2871
  }
2872
+ // A TS/JS call through an ES private field of the enclosing class —
2873
+ // `this.#items.add()`, emitted as `this.#items.add` (#1987) — resolves
2874
+ // exactly like `this.<field>` below (#1496). `#` is outside dotMatch's
2875
+ // receiver class, so the shape is matched here.
2876
+ if (ref.language === 'typescript' || ref.language === 'javascript' || ref.language === 'tsx' || ref.language === 'jsx') {
2877
+ const privateField = ref.referenceName.match(/^this\.(#[\w$]+)\.(\w+)$/);
2878
+ if (privateField)
2879
+ return matchTsThisFieldCall(privateField[1], privateField[2], ref, context);
2880
+ }
1550
2881
  const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch;
1551
2882
  if (!match) {
1552
2883
  return null;
@@ -1561,9 +2892,16 @@ function matchMethodCall(ref, context) {
1561
2892
  // shared source-based inferrer. resolveMethodOnType validates the method
1562
2893
  // exists on the inferred type, so a mis-inference produces no edge.
1563
2894
  if (inferableReceiver) {
1564
- const inferredType = nmTimedT('mc-infer', ref, () => ref.language === 'cpp'
2895
+ let inferredType = nmTimedT('mc-infer', ref, () => ref.language === 'cpp'
1565
2896
  ? inferCppReceiverType(objectOrClass, ref, context)
1566
2897
  : inferLocalReceiverType(objectOrClass, ref, context));
2898
+ const awaited = !inferredType && ESM_FAMILY.has(ref.language)
2899
+ ? inferEsmAwaitedCallType(objectOrClass, ref, context) : null;
2900
+ if (awaited) {
2901
+ if (!awaited.name || js_builtins_1.TS_PRIMITIVE_TYPES.has(awaited.name))
2902
+ return null;
2903
+ inferredType = awaited.name;
2904
+ }
1567
2905
  if (inferredType) {
1568
2906
  // Java/Kotlin: when two classes share the simple name, the file's import
1569
2907
  // pins WHICH one (#314). Other languages disambiguate by call-site file.
@@ -1572,10 +2910,29 @@ function matchMethodCall(ref, context) {
1572
2910
  .getImportMappings(ref.filePath, ref.language)
1573
2911
  .find((i) => i.localName === inferredType)?.source
1574
2912
  : undefined;
1575
- const typedMatch = nmTimedT('mc-rmot', ref, () => resolveMethodOnType(inferredType, methodName, ref, context, 0.9, 'instance-method', importedFqn));
2913
+ const typedMatch = nmTimedT('mc-rmot', ref, () => resolveMethodOnType(inferredType, methodName, awaited ? { ...ref, filePath: awaited.filePath } : ref, context, 0.9, 'instance-method', importedFqn));
1576
2914
  if (typedMatch) {
2915
+ if (awaited) {
2916
+ const target = context.getNodeById?.(typedMatch.targetNodeId);
2917
+ if (!target || (target.qualifiedName.startsWith(`${inferredType}::`) && target.filePath !== awaited.filePath))
2918
+ return null;
2919
+ return { ...typedMatch, original: ref };
2920
+ }
1577
2921
  return typedMatch;
1578
2922
  }
2923
+ if (awaited)
2924
+ return null;
2925
+ // A known JS/TS builtin receiver is external when it has no project
2926
+ // method (#1566). Inference already strips generics (`Map<K, V>` →
2927
+ // `Map`); do not let Strategy 3 guess an unrelated `get`/`set`/`has`.
2928
+ // Keep the validated match above for a project type shadowing a builtin.
2929
+ // A primitive receiver joins the builtins here: `listed.split()` on a
2930
+ // `string` is the built-in method, and Strategy 3 would otherwise hand
2931
+ // it whichever project class happens to declare a lone `split` (#1840).
2932
+ if (ESM_FAMILY.has(ref.language) &&
2933
+ (js_builtins_1.JS_BUILT_INS.has(inferredType) || js_builtins_1.TS_PRIMITIVE_TYPES.has(inferredType))) {
2934
+ return null;
2935
+ }
1579
2936
  }
1580
2937
  }
1581
2938
  // Go 2-hop field chain `base.field.Method` (#1276): the base's type comes
@@ -1592,6 +2949,39 @@ function matchMethodCall(ref, context) {
1592
2949
  if (ref.language === 'go' && dotMatch && objectOrClass.includes('.')) {
1593
2950
  return matchGoFieldChainCall(objectOrClass, methodName, ref, context);
1594
2951
  }
2952
+ // Rust call through a field of the enclosing type — `self.inner.run()`,
2953
+ // emitted as `self.inner.run` (#1585). Same discipline as the Go branch
2954
+ // above, and EXCLUSIVE for the same reason: validated field-type inference
2955
+ // or nothing. Letting this shape reach the bare-name strategies below is
2956
+ // how `self.inner.run()` resolved to a same-named method on an unrelated
2957
+ // type — or to the calling method itself, a self-edge the source doesn't
2958
+ // contain — whenever the field's type was external or merely shared a
2959
+ // method name with something nearby.
2960
+ if (ref.language === 'rust' && dotMatch && objectOrClass.startsWith('self.')) {
2961
+ return matchRustSelfFieldCall(objectOrClass.slice('self.'.length), methodName, ref, context);
2962
+ }
2963
+ // Rust call on the enclosing type itself — `self.reset()`, emitted as
2964
+ // `self.reset` (#1861). Same discipline as the field branch above, and
2965
+ // EXCLUSIVE for the same reason: the owner is written on the `impl` line and
2966
+ // carried in the calling method's qualified name, so it is not a guess.
2967
+ // Letting this shape reach the bare-name strategies below is how
2968
+ // `self.reset()` resolved to a same-named method on an unrelated type
2969
+ // whenever that type's method happened to sit nearer the call site.
2970
+ if (ref.language === 'rust' && dotMatch && objectOrClass === 'self') {
2971
+ return matchRustSelfCall(methodName, ref, context);
2972
+ }
2973
+ // TS/JS call through a field of the enclosing class — `this.mailer.send()`,
2974
+ // emitted as `this.mailer.send` (#1496). Same discipline as the Rust branch
2975
+ // above, and EXCLUSIVE for the same reason: the field's declared type off
2976
+ // the class's own declaration, validated by resolveMethodOnType, or nothing.
2977
+ // Letting the bare name through is how `this.mailer.send()` inside
2978
+ // `Notifier.send()` resolved to the calling method itself — a self-edge the
2979
+ // source does not contain — whenever the two shared a name.
2980
+ if ((ref.language === 'typescript' || ref.language === 'javascript' || ref.language === 'tsx' || ref.language === 'jsx') &&
2981
+ dotMatch &&
2982
+ objectOrClass.startsWith('this.')) {
2983
+ return matchTsThisFieldCall(objectOrClass.slice('this.'.length), methodName, ref, context);
2984
+ }
1595
2985
  // Java/Kotlin: receiver may be a field whose name doesn't match the type by
1596
2986
  // Java naming convention (`userbo` → class `UserBO`, abbreviated). Look up
1597
2987
  // the field in the enclosing class to get its declared type, then resolve
@@ -1611,59 +3001,85 @@ function matchMethodCall(ref, context) {
1611
3001
  }
1612
3002
  }
1613
3003
  }
3004
+ // Object-literal namespace receiver (#1573): `api.call()` where `api` is a
3005
+ // same-file `const api = { call() {…}, get: () => {…} }`. Its members are
3006
+ // plain functions with bare names inside the constant's extent — no
3007
+ // `Container::member` qualified name — so none of the class-shaped
3008
+ // strategies below can see them (Strategy 3 only considers `method`
3009
+ // kinds) and the call resolved to nothing at all. Same file only: a
3010
+ // cross-file use reaches the same helper through the import path.
3011
+ if (dotMatch && !objectOrClass.includes('.') && OBJECT_LITERAL_LANGUAGES.has(ref.language)) {
3012
+ const literalMatch = nmTimedT('mc-literal', ref, () => {
3013
+ // Same-file holders only, so the call-site-first ordering is moot.
3014
+ const holders = context.getNodesByName(objectOrClass).filter((n) => (n.kind === 'constant' || n.kind === 'variable') && n.filePath === ref.filePath);
3015
+ for (const holder of holders) {
3016
+ const hit = resolveObjectLiteralMember(holder, methodName, ref, context, 0.85, 'instance-method') ??
3017
+ resolveObjectLiteralBinding(holder, methodName, ref, context);
3018
+ if (hit)
3019
+ return hit;
3020
+ }
3021
+ return null;
3022
+ });
3023
+ if (literalMatch)
3024
+ return literalMatch;
3025
+ }
1614
3026
  // Strategy 1: Direct class name match (existing logic). When the receiver
1615
3027
  // names a class that exists in several files (`Logger.log()` / `Logger::log()`
1616
3028
  // with a `Logger` in both `a/` and `b/`), try the class in the call site's
1617
3029
  // own file first — otherwise the first-indexed class wins and a call in `b/`
1618
3030
  // resolves to `a/`'s method (#1079).
1619
3031
  const strat1 = nmTimedT('mc-class', ref, () => {
1620
- const classCandidates = preferCallSiteFile(context.getNodesByName(objectOrClass), ref.filePath);
3032
+ const classCandidates = preferCallSiteFile(context.getNodesByName(objectOrClass).filter(isMethodOwnerKind), ref.filePath);
1621
3033
  for (const classNode of classCandidates) {
1622
- if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
1623
- // Skip cross-language class matches
1624
- if (classNode.language !== ref.language)
1625
- continue;
1626
- const nodesInFile = context.getNodesInFile(classNode.filePath);
1627
- const methodNode = nodesInFile.find((n) => n.kind === 'method' &&
1628
- n.name === methodName &&
1629
- n.qualifiedName.includes(classNode.name));
1630
- if (methodNode) {
1631
- return {
1632
- original: ref,
1633
- targetNodeId: methodNode.id,
1634
- confidence: 0.85,
1635
- resolvedBy: 'qualified-name',
1636
- };
1637
- }
3034
+ // Skip cross-language class matches
3035
+ if (classNode.language !== ref.language)
3036
+ continue;
3037
+ const nodesInFile = context.getNodesInFile(classNode.filePath);
3038
+ const methodNode = nodesInFile.find((n) => n.kind === 'method' &&
3039
+ n.name === methodName &&
3040
+ n.qualifiedName.includes(classNode.name));
3041
+ if (methodNode) {
3042
+ return {
3043
+ original: ref,
3044
+ targetNodeId: methodNode.id,
3045
+ confidence: 0.85,
3046
+ resolvedBy: 'qualified-name',
3047
+ };
1638
3048
  }
1639
3049
  }
1640
3050
  return null;
1641
3051
  });
1642
3052
  if (strat1)
1643
3053
  return strat1;
3054
+ // Built-in method names need a validated receiver (#1987). Typed, imported,
3055
+ // object-literal and direct class receivers have had their chance above;
3056
+ // capitalization, word overlap or a unique method name are not evidence
3057
+ // that `list.map()` / `cache.get()` calls a project class.
3058
+ if (ref.referenceKind === 'calls' && JS_FAMILY.has(ref.language) &&
3059
+ objectOrClass !== 'this' && objectOrClass !== 'super' &&
3060
+ js_builtins_1.JS_BUILTIN_METHODS.has(methodName))
3061
+ return null;
1644
3062
  // Strategy 2: Instance variable receiver - try capitalized form to find class
1645
3063
  // e.g., "permissionEngine" → look for classes containing "PermissionEngine"
1646
3064
  const capitalizedReceiver = objectOrClass.charAt(0).toUpperCase() + objectOrClass.slice(1);
1647
3065
  if (capitalizedReceiver !== objectOrClass) {
1648
3066
  const strat2 = nmTimedT('mc-capital', ref, () => {
1649
- const fuzzyClassCandidates = preferCallSiteFile(context.getNodesByName(capitalizedReceiver), ref.filePath);
3067
+ const fuzzyClassCandidates = preferCallSiteFile(context.getNodesByName(capitalizedReceiver).filter(isMethodOwnerKind), ref.filePath);
1650
3068
  for (const classNode of fuzzyClassCandidates) {
1651
- if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
1652
- // Skip cross-language class matches
1653
- if (classNode.language !== ref.language)
1654
- continue;
1655
- const nodesInFile = context.getNodesInFile(classNode.filePath);
1656
- const methodNode = nodesInFile.find((n) => n.kind === 'method' &&
1657
- n.name === methodName &&
1658
- n.qualifiedName.includes(classNode.name));
1659
- if (methodNode) {
1660
- return {
1661
- original: ref,
1662
- targetNodeId: methodNode.id,
1663
- confidence: 0.8,
1664
- resolvedBy: 'instance-method',
1665
- };
1666
- }
3069
+ // Skip cross-language class matches
3070
+ if (classNode.language !== ref.language)
3071
+ continue;
3072
+ const nodesInFile = context.getNodesInFile(classNode.filePath);
3073
+ const methodNode = nodesInFile.find((n) => n.kind === 'method' &&
3074
+ n.name === methodName &&
3075
+ n.qualifiedName.includes(classNode.name));
3076
+ if (methodNode) {
3077
+ return {
3078
+ original: ref,
3079
+ targetNodeId: methodNode.id,
3080
+ confidence: 0.8,
3081
+ resolvedBy: 'instance-method',
3082
+ };
1667
3083
  }
1668
3084
  }
1669
3085
  return null;
@@ -1814,6 +3230,641 @@ function matchGoFieldChainCall(receiverChain, methodName, ref, context) {
1814
3230
  }
1815
3231
  return null;
1816
3232
  }
3233
+ // Rust primitives and the prelude's own types: a field of one of these never
3234
+ // names a project type, so a `self.<field>.<method>()` on it stays unresolved.
3235
+ const RUST_NON_PROJECT_FIELD_TYPES = new Set([
3236
+ 'bool', 'char', 'str', 'String',
3237
+ 'i8', 'i16', 'i32', 'i64', 'i128', 'isize',
3238
+ 'u8', 'u16', 'u32', 'u64', 'u128', 'usize',
3239
+ 'f32', 'f64',
3240
+ 'Self', 'self',
3241
+ ]);
3242
+ /**
3243
+ * Reduce a Rust field's declared type text to the simple name of the type a
3244
+ * method call on that field auto-derefs to, or null when there is none we can
3245
+ * name. Only the layers Rust's method-call auto-deref looks through are
3246
+ * unwrapped: references (`&`, `&'a mut`) and the owning smart pointers
3247
+ * (`Box`, `Rc`, `Arc`) — `self.inner.run()` with `inner: Box<Inner>` calls
3248
+ * `Inner::run`. Containers that do NOT auto-deref to their parameter
3249
+ * (`Option<Inner>`, `Vec<Inner>`, `Mutex<Inner>`, `RefCell<Inner>`) keep their
3250
+ * own name and, having no project node, resolve to nothing — `self.items.push()`
3251
+ * must never become `Inner::push`. A trait object (`Box<dyn Source>`) yields
3252
+ * the trait, whose method node the interface-impl synthesizer fans out. A
3253
+ * generic parameter (`T`), a primitive, a tuple / array / raw pointer / fn
3254
+ * type, or a non-identifier yields null.
3255
+ */
3256
+ function rustFieldTypeName(raw) {
3257
+ let t = raw.trim();
3258
+ for (;;) {
3259
+ const before = t;
3260
+ t = t.replace(/^&\s*(?:'\w+\s+)?(?:mut\s+)?/, '');
3261
+ t = t.replace(/^(?:Box|Rc|Arc)\s*<\s*/, '');
3262
+ t = t.replace(/^(?:dyn|impl)\s+/, '');
3263
+ if (t === before)
3264
+ break;
3265
+ }
3266
+ // Drop generic args, the closing `>`s of unwrapped pointers, and trait-object
3267
+ // bounds (`dyn Source + Send`); keep the last path segment.
3268
+ t = t.replace(/[<>+].*$/, '').trim();
3269
+ const seg = t.split('::').filter(Boolean).pop();
3270
+ if (!seg || !/^[A-Za-z_]\w*$/.test(seg))
3271
+ return null;
3272
+ if (RUST_NON_PROJECT_FIELD_TYPES.has(seg))
3273
+ return null;
3274
+ if (/^[A-Z]$/.test(seg))
3275
+ return null; // bare single-letter generic parameter
3276
+ return seg;
3277
+ }
3278
+ /**
3279
+ * `self.method()` in Rust — the method on the type the call sits inside.
3280
+ *
3281
+ * The owner is the calling method's qualified-name prefix (`Target::run` →
3282
+ * `Target`), which is where the `impl` block's type ends up. A free function
3283
+ * has no `self`, so a caller whose qualified name carries no owner declines.
3284
+ * Exactly one candidate must belong to that owner: a project with two `impl`
3285
+ * blocks for the same type is normal, two same-named methods on it is not, and
3286
+ * guessing between them is the failure this replaces.
3287
+ */
3288
+ function matchRustSelfCall(methodName, ref, context) {
3289
+ const caller = context.getNodeById?.(ref.fromNodeId);
3290
+ if (!caller?.qualifiedName)
3291
+ return null;
3292
+ const sep = caller.qualifiedName.lastIndexOf('::');
3293
+ if (sep <= 0)
3294
+ return null; // a free fn has no `self`
3295
+ const owner = caller.qualifiedName.slice(0, sep);
3296
+ let owned = context
3297
+ .getNodesByQualifiedName(`${owner}::${methodName}`)
3298
+ .filter((n) => n.kind === 'method' &&
3299
+ n.language === 'rust' &&
3300
+ n.qualifiedName === `${owner}::${methodName}`);
3301
+ // Rust's extracted qualified names omit module paths. Two modules can
3302
+ // each declare `Target`; matching just `Target::reset` does not establish
3303
+ // ownership. In that case require a single owner declaration in the
3304
+ // caller's file and a method in that file. Otherwise leave it unresolved.
3305
+ // A unique owner still permits ordinary impl blocks split across files.
3306
+ const owners = context.getNodesByQualifiedName(owner).filter((n) => n.language === 'rust' && ['struct', 'enum', 'union', 'trait', 'class'].includes(n.kind));
3307
+ if (owners.length > 1) {
3308
+ if (owners.filter((n) => n.filePath === caller.filePath).length !== 1)
3309
+ return null;
3310
+ owned = owned.filter((n) => n.filePath === caller.filePath);
3311
+ }
3312
+ if (owned.length !== 1)
3313
+ return null;
3314
+ return {
3315
+ original: ref,
3316
+ targetNodeId: owned[0].id,
3317
+ confidence: 0.9,
3318
+ resolvedBy: 'qualified-name',
3319
+ };
3320
+ }
3321
+ /**
3322
+ * Resolve a Rust call through a field of the enclosing type —
3323
+ * `self.inner.run()`, emitted by the extractor as `self.inner.run` (#1585).
3324
+ * Mirrors the Go 2-hop precedent above (#1276): the owner type is the calling
3325
+ * method's qualified-name prefix (`Outer::run` → `Outer`), the field's declared
3326
+ * type comes from the owner struct's OWN declaration lines, and the method is
3327
+ * resolved AND VALIDATED on that type by resolveMethodOnType. The caller
3328
+ * treats this branch as exclusive for `self.<field>` receivers: a field whose
3329
+ * type is external (`std::vec::IntoIter`, `regex::Regex`), a generic
3330
+ * parameter, or not declared where we can see it yields null and the ref stays
3331
+ * unresolved. Rust struct fields are not graph nodes, so the declaration text
3332
+ * is the only place the type lives.
3333
+ */
3334
+ function matchRustSelfFieldCall(field, methodName, ref, context) {
3335
+ // The extractor only ever emits a single field hop; anything else is not ours.
3336
+ if (!field || field.includes('.'))
3337
+ return null;
3338
+ const caller = context.getNodeById?.(ref.fromNodeId);
3339
+ if (!caller)
3340
+ return null;
3341
+ const sep = caller.qualifiedName.lastIndexOf('::');
3342
+ if (sep <= 0)
3343
+ return null; // a free fn has no `self`
3344
+ const owner = caller.qualifiedName.slice(0, sep).split('::').pop();
3345
+ if (!owner)
3346
+ return null;
3347
+ const owners = preferCallSiteFile(context.getNodesByName(owner), ref.filePath).filter((n) => (n.kind === 'struct' || n.kind === 'union' || n.kind === 'class') &&
3348
+ n.language === 'rust');
3349
+ const fieldEsc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3350
+ // `pub inner: Inner,` / `inner: Box<dyn Source>,` / `pub(crate) inner: T }` —
3351
+ // the type text runs to the field separator. A comma inside generic args
3352
+ // (`HashMap<K, V>`) truncates the capture, which rustFieldTypeName then
3353
+ // reduces to the container's own name — exactly the non-deref case it
3354
+ // refuses anyway.
3355
+ const fieldRe = new RegExp(`\\b${fieldEsc}\\s*:\\s*([^,{}]+)`);
3356
+ for (const s of owners) {
3357
+ const source = context.readFile(s.filePath);
3358
+ if (!source)
3359
+ continue;
3360
+ // Only the struct's own declaration lines, comment-stripped line by line —
3361
+ // same discipline as the Go helper: prose or a same-named identifier
3362
+ // elsewhere in the file can never donate a type.
3363
+ const declLines = source.split('\n').slice(Math.max(0, s.startLine - 1), s.endLine);
3364
+ for (const rawLine of declLines) {
3365
+ const line = rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
3366
+ const m = line.match(fieldRe);
3367
+ if (!m || !m[1])
3368
+ continue;
3369
+ const fieldType = rustFieldTypeName(m[1]);
3370
+ // The field is declared here; whether or not its type names a project
3371
+ // symbol, this owner is the answer — no other same-named struct applies.
3372
+ if (!fieldType)
3373
+ return null;
3374
+ return resolveMethodOnType(fieldType, methodName, ref, context, 0.85, 'instance-method');
3375
+ }
3376
+ }
3377
+ return null;
3378
+ }
3379
+ /**
3380
+ * Per-context memo for matchTsThisFieldCall's declaration scan: `classId\0field`
3381
+ * → the first line of the class that declares the field (as `: typeof X`,
3382
+ * `: X`, or `= new X`, tried in that order per line) with its captured type,
3383
+ * or null. The scan depends only on the class and the field, but ran for every
3384
+ * `this.<field>.<method>()` call — re-splitting the file and compiling three
3385
+ * patterns each time, a third of all method-call matching on vscode. Derived
3386
+ * from file source: drops with clearNameMatcherMemos.
3387
+ */
3388
+ const TS_FIELD_DECL_MEMO = new WeakMap();
3389
+ /** The last few classes' declarations — calls arrive file by file. */
3390
+ const TS_CLASS_LINES = new WeakMap();
3391
+ const TS_CLASS_LINES_KEEP = 32;
3392
+ function tsClassDecl(cls, context) {
3393
+ let cache = TS_CLASS_LINES.get(context);
3394
+ if (!cache) {
3395
+ cache = new Map();
3396
+ TS_CLASS_LINES.set(context, cache);
3397
+ }
3398
+ const hit = cache.get(cls.id);
3399
+ if (hit !== undefined)
3400
+ return hit;
3401
+ const source = context.readFile(cls.filePath);
3402
+ const decl = source
3403
+ ? {
3404
+ lines: source
3405
+ .split('\n')
3406
+ .slice(Math.max(0, cls.startLine - 1), cls.endLine)
3407
+ .map((rawLine) => rawLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '')),
3408
+ lookups: 0,
3409
+ linesByToken: null,
3410
+ }
3411
+ : null;
3412
+ if (cache.size >= TS_CLASS_LINES_KEEP)
3413
+ cache.delete(cache.keys().next().value);
3414
+ cache.set(cls.id, decl);
3415
+ return decl;
3416
+ }
3417
+ const TS_FIELD_TOKEN = /^[\w$#]+$/;
3418
+ const TS_LINE_TOKEN = /[\w$#]+/g;
3419
+ const TS_TOKEN_CHAR = /[\w$#]/;
3420
+ /**
3421
+ * tsFieldPatterns with the field spelled `[\w$#]+`, tried sticky at a whole-
3422
+ * token occurrence of the field. There the run consumes exactly the field (a
3423
+ * shorter prefix cannot continue: every pattern needs `\s`, `?`, `!`, `:` or
3424
+ * `=` next), so each matches exactly where the field's own pattern would —
3425
+ * without compiling three patterns for every field of every class.
3426
+ */
3427
+ const TS_FIELD_PATTERNS_AT = [
3428
+ { re: /(?<![\w$#])[\w$#]+\b\s*[?!]?\s*:\s*(?:readonly\s+)?typeof\s+([A-Za-z_$][\w.$]*)/y, valueType: true },
3429
+ { re: /(?<![\w$#])[\w$#]+\b\s*[?!]?\s*:\s*(?:readonly\s+)?([A-Za-z_$][\w.$]*)/y, valueType: false },
3430
+ { re: /(?<![\w$#])[\w$#]+\b\s*=\s*new\s+([A-Za-z_$][\w.$]*)/y, valueType: false },
3431
+ ];
3432
+ /** The first declaration of `field` on one line, as `line.match` of its patterns in order would find it. */
3433
+ function tsFieldOnLine(line, field) {
3434
+ let at = null;
3435
+ for (let i = line.indexOf(field); i !== -1; i = line.indexOf(field, i + 1)) {
3436
+ const end = i + field.length;
3437
+ if ((i > 0 && TS_TOKEN_CHAR.test(line[i - 1])) || (end < line.length && TS_TOKEN_CHAR.test(line[end])))
3438
+ continue;
3439
+ (at ??= []).push(i);
3440
+ }
3441
+ if (!at)
3442
+ return null;
3443
+ for (const { re, valueType } of TS_FIELD_PATTERNS_AT) {
3444
+ for (const i of at) {
3445
+ re.lastIndex = i;
3446
+ const m = re.exec(line);
3447
+ if (m && m[1])
3448
+ return { valueType, type: m[1] };
3449
+ }
3450
+ }
3451
+ return null;
3452
+ }
3453
+ /** Indices of the class lines holding `token` as a whole `[\w$#]` run, ascending. */
3454
+ function tsClassLinesWithToken(decl, token) {
3455
+ if (!decl.linesByToken) {
3456
+ const index = new Map();
3457
+ for (let i = 0; i < decl.lines.length; i++) {
3458
+ for (const m of decl.lines[i].matchAll(TS_LINE_TOKEN)) {
3459
+ const rows = index.get(m[0]);
3460
+ if (!rows)
3461
+ index.set(m[0], [i]);
3462
+ else if (rows[rows.length - 1] !== i)
3463
+ rows.push(i);
3464
+ }
3465
+ }
3466
+ decl.linesByToken = index;
3467
+ }
3468
+ return decl.linesByToken.get(token) ?? [];
3469
+ }
3470
+ /** Compiled declaration patterns by field name — fields recur across classes. */
3471
+ const TS_FIELD_PATTERNS = new Map();
3472
+ const TS_FIELD_PATTERNS_CAP = 4096;
3473
+ function tsFieldPatterns(field) {
3474
+ const hit = TS_FIELD_PATTERNS.get(field);
3475
+ if (hit)
3476
+ return hit;
3477
+ const fieldEsc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3478
+ // A word boundary cannot open a private name; it also lets a public
3479
+ // `items` match `#items`. Keep the two field namespaces distinct (#1987).
3480
+ const fieldStart = '(?<![\\w$#])';
3481
+ const patterns = [
3482
+ // `storage: typeof DraftHubStorage` — the type OF a value: an object
3483
+ // literal used as a namespace. Its members are bare-named functions inside
3484
+ // the constant's extent (#1573), so they are found by containment, not by
3485
+ // `Type::method`. Tried first: the declared-type pattern below would
3486
+ // otherwise capture the word `typeof`.
3487
+ {
3488
+ re: new RegExp(`${fieldStart}${fieldEsc}\\b\\s*[?!]?\\s*:\\s*(?:readonly\\s+)?typeof\\s+([A-Za-z_$][\\w.$]*)`),
3489
+ valueType: true,
3490
+ },
3491
+ // `private readonly mailer?: Mailer` — a class field or a constructor
3492
+ // parameter property; the capture stops at `<`, `[` or `|`, so a generic
3493
+ // or union type yields its head and resolveMethodOnType decides.
3494
+ {
3495
+ re: new RegExp(`${fieldStart}${fieldEsc}\\b\\s*[?!]?\\s*:\\s*(?:readonly\\s+)?([A-Za-z_$][\\w.$]*)`),
3496
+ valueType: false,
3497
+ },
3498
+ // `mailer = new Mailer()` / `this.mailer = new Mailer()`
3499
+ { re: new RegExp(`${fieldStart}${fieldEsc}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), valueType: false },
3500
+ ];
3501
+ if (TS_FIELD_PATTERNS.size >= TS_FIELD_PATTERNS_CAP)
3502
+ TS_FIELD_PATTERNS.delete(TS_FIELD_PATTERNS.keys().next().value);
3503
+ TS_FIELD_PATTERNS.set(field, patterns);
3504
+ return patterns;
3505
+ }
3506
+ function tsFieldDeclaration(cls, field, context) {
3507
+ let memo = TS_FIELD_DECL_MEMO.get(context);
3508
+ if (!memo) {
3509
+ memo = new Map();
3510
+ TS_FIELD_DECL_MEMO.set(context, memo);
3511
+ }
3512
+ const key = cls.id + '\0' + field;
3513
+ const hit = memo.get(key);
3514
+ if (hit !== undefined)
3515
+ return hit;
3516
+ let found = null;
3517
+ const decl = tsClassDecl(cls, context);
3518
+ if (decl && TS_FIELD_TOKEN.test(field)) {
3519
+ // Every pattern needs the field as a whole `[\w$#]` run — the lookbehind
3520
+ // bars one before it, and nothing after it but `\s`, `?`, `!`, `:` or `=`
3521
+ // can continue a match — so only lines holding that token can match. A
3522
+ // class asked about several fields is indexed by token once.
3523
+ const rows = ++decl.lookups > 1 ? tsClassLinesWithToken(decl, field) : null;
3524
+ const count = rows ? rows.length : decl.lines.length;
3525
+ for (let k = 0; k < count && !found; k++) {
3526
+ const line = decl.lines[rows ? rows[k] : k];
3527
+ // Every pattern spells the field literally, so only a line that contains
3528
+ // it can match — and most of a class's lines never mention a given field.
3529
+ if (line.includes(field))
3530
+ found = tsFieldOnLine(line, field);
3531
+ }
3532
+ }
3533
+ else if (decl) {
3534
+ const patterns = tsFieldPatterns(field);
3535
+ scan: for (const line of decl.lines) {
3536
+ if (!line.includes(field))
3537
+ continue;
3538
+ for (const { re, valueType } of patterns) {
3539
+ const m = line.match(re);
3540
+ if (!m || !m[1])
3541
+ continue;
3542
+ found = { valueType, type: m[1] };
3543
+ break scan;
3544
+ }
3545
+ }
3546
+ }
3547
+ memo.set(key, found);
3548
+ return found;
3549
+ }
3550
+ /**
3551
+ * Resolve a TS/JS `this.<field>.<method>()` call (#1496) through the field's
3552
+ * declared type, read off the ENCLOSING class's own declaration lines:
3553
+ * a field or constructor-parameter property (`private mailer: Mailer`,
3554
+ * `mailer?: Mailer`, `readonly mailer: Mailer`) or an initializer
3555
+ * (`mailer = new Mailer()`, `this.mailer = new Mailer()`). The method is then
3556
+ * VALIDATED on that type by resolveMethodOnType. Null — never a bare-name
3557
+ * fallback — when the field is not declared there or its type is external,
3558
+ * a builtin (`this.items.push()`) or not spelled out.
3559
+ */
3560
+ function matchTsThisFieldCall(field, methodName, ref, context) {
3561
+ if (!field || field.includes('.'))
3562
+ return null;
3563
+ const caller = context.getNodeById?.(ref.fromNodeId);
3564
+ if (!caller)
3565
+ return null;
3566
+ const sep = caller.qualifiedName.lastIndexOf('::');
3567
+ if (sep <= 0)
3568
+ return null; // not inside a class
3569
+ const owner = caller.qualifiedName.slice(0, sep).split('::').pop();
3570
+ if (!owner)
3571
+ return null;
3572
+ const owners = preferCallSiteFile(context.getNodesByName(owner), ref.filePath).filter((n) => (n.kind === 'class' || n.kind === 'component') && sameLanguageFamily(n.language, ref.language));
3573
+ for (const cls of owners) {
3574
+ const decl = tsFieldDeclaration(cls, field, context);
3575
+ if (!decl)
3576
+ continue;
3577
+ if (decl.valueType) {
3578
+ // The value's declaration may live in another file (it is imported);
3579
+ // the call site's file is preferred when several share the name.
3580
+ const holderName = decl.type.split('.').pop();
3581
+ const holders = preferCallSiteFile(context.getNodesByName(holderName), ref.filePath).filter((n) => (n.kind === 'constant' || n.kind === 'variable') && sameLanguageFamily(n.language, ref.language));
3582
+ for (const holder of holders) {
3583
+ const hit = resolveObjectLiteralMember(holder, methodName, ref, context, 0.85, 'instance-method');
3584
+ if (hit)
3585
+ return hit;
3586
+ }
3587
+ return null;
3588
+ }
3589
+ // `ns.Mailer` → `Mailer`; a primitive or builtin names no project type.
3590
+ const typeName = decl.type.split('.').pop();
3591
+ if (!/^[A-Z]/.test(typeName))
3592
+ return null;
3593
+ // Two apps in one repo may each declare a `UserService`. The bare-name
3594
+ // path this replaces broke that tie by directory proximity, so keep the
3595
+ // same signal: among the type's declarations of the method, prefer the
3596
+ // one closest to the call site's directory (its own app), never index
3597
+ // order. resolveMethodOnType still answers the single-declaration and
3598
+ // supertype cases.
3599
+ const declared = context
3600
+ .getNodesByName(methodName)
3601
+ .filter((n) => n.kind === 'method' &&
3602
+ sameLanguageFamily(n.language, ref.language) &&
3603
+ (n.qualifiedName === `${typeName}::${methodName}` || n.qualifiedName.endsWith(`::${typeName}::${methodName}`)));
3604
+ if (declared.length > 1) {
3605
+ const callDirs = ref.filePath.split('/').slice(0, -1);
3606
+ const shared = (fp) => {
3607
+ const dirs = fp.split('/').slice(0, -1);
3608
+ let i = 0;
3609
+ while (i < dirs.length && i < callDirs.length && dirs[i] === callDirs[i])
3610
+ i++;
3611
+ return i;
3612
+ };
3613
+ const nearest = [...declared].sort((a, b) => shared(b.filePath) - shared(a.filePath) || a.filePath.localeCompare(b.filePath))[0];
3614
+ return { original: ref, targetNodeId: nearest.id, confidence: 0.85, resolvedBy: 'instance-method' };
3615
+ }
3616
+ return resolveMethodOnType(typeName, methodName, ref, context, 0.85, 'instance-method');
3617
+ }
3618
+ return null;
3619
+ }
3620
+ /**
3621
+ * The one fallback a TS/JS/Python call-receiver chain keeps (#1683): a STORE
3622
+ * ACCESSOR. Zustand's `get()` inside the store factory and
3623
+ * `useStore.getState()` outside it hand back the store whose actions are
3624
+ * indexed as functions (#1573). JS/TS resolves the member within that store;
3625
+ * the existing Python fallback still requires a unique callable. Nothing else
3626
+ * qualifies: a chain rooted in a project value still says nothing about what
3627
+ * the inner call RETURNS — `db.prepare(sql).all()` would bind to any project
3628
+ * function named `all` — so it resolves to nothing, exactly like a chain
3629
+ * rooted in a parameter (`d.setdefault(k, []).append(v)`).
3630
+ */
3631
+ function matchStoreAccessorChain(ref, context) {
3632
+ const m = ref.referenceName.match(/^([\w$.]+)\(\)\.(\w+)$/);
3633
+ if (!m || !m[1] || !m[2])
3634
+ return null;
3635
+ const inner = m[1];
3636
+ const method = m[2];
3637
+ if (!(inner === 'get' || inner === 'getState' || inner.endsWith('.getState')))
3638
+ return null;
3639
+ if (JS_FAMILY.has(ref.language)) {
3640
+ return resolveStoreAction(inner, method, ref, context);
3641
+ }
3642
+ const callables = context
3643
+ .getNodesByName(method)
3644
+ .filter((n) => (n.kind === 'function' || n.kind === 'method') && sameLanguageFamily(n.language, ref.language) && n.id !== ref.fromNodeId);
3645
+ if (callables.length !== 1)
3646
+ return null;
3647
+ return { original: ref, targetNodeId: callables[0].id, confidence: 0.6, resolvedBy: 'exact-match' };
3648
+ }
3649
+ /** Resolve the implementation inside the identified store, not a namesake or
3650
+ * an interface signature elsewhere in the project. Import resolution already
3651
+ * follows aliases/barrels; containment already excludes nested action locals. */
3652
+ function resolveStoreAction(inner, member, ref, context, selector = false) {
3653
+ let holders;
3654
+ if (inner === 'get' || inner === 'getState') {
3655
+ const caller = context.getNodeById?.(ref.fromNodeId);
3656
+ if (!caller)
3657
+ return null;
3658
+ holders = context.getNodesInFile(ref.filePath).filter((n) => {
3659
+ if ((n.kind !== 'constant' && n.kind !== 'variable') || !rangeWithin(caller, n))
3660
+ return false;
3661
+ const source = context.readFile(n.filePath)?.split('\n').slice(n.startLine - 1, caller.startLine).join('\n') ?? '';
3662
+ // The accessor must actually be a parameter of the enclosing factory.
3663
+ return new RegExp(`\\(\\s*[\\w$]+\\s*,\\s*${inner}\\s*(?:,\\s*[\\w$]+\\s*)?\\)\\s*=>`).test(source);
3664
+ });
3665
+ }
3666
+ else {
3667
+ const name = inner.slice(0, -'.getState'.length);
3668
+ if (!/^[\w$]+$/.test(name))
3669
+ return null;
3670
+ const imported = context.resolveImport?.({ ...ref, referenceName: name, referenceKind: 'references' });
3671
+ const node = imported && context.getNodeById?.(imported.targetNodeId);
3672
+ if (node && importShadowedAt(name, ref, context))
3673
+ return null;
3674
+ holders = node ? [node] : context.getNodesByName(name).filter((n) => n.filePath === ref.filePath && isLexicallyReachable(n, ref, context));
3675
+ }
3676
+ if (holders.length !== 1)
3677
+ return null;
3678
+ const holder = holders[0];
3679
+ if (selector) {
3680
+ // Only a Zustand hook promises to return the selector's result. An
3681
+ // arbitrary function accepting that callback is not a store binding.
3682
+ const text = context.readFile(holder.filePath)?.split('\n').slice(holder.startLine - 1, holder.endLine).join('\n') ?? '';
3683
+ const escaped = holder.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3684
+ const factory = new RegExp(`\\b(?:const|let)\\s+${escaped}\\s*=\\s*([\\w$]+)\\s*[<(]`).exec(text)?.[1];
3685
+ if (!factory || !context.getImportMappings(holder.filePath, holder.language).some(m => m.localName === factory && m.source === 'zustand' && (m.exportedName === 'create' || m.isDefault)))
3686
+ return null;
3687
+ }
3688
+ return resolveObjectLiteralMember(holder, member, ref, context, 0.9, 'instance-method');
3689
+ }
3690
+ // Eligibility is a file property, not a call-site property. Cache both answers
3691
+ // within the same stable-source window as the resolver's file cache; sync drops
3692
+ // it via clearNameMatcherMemos. Keep only booleans, FIFO-capped like PATTERN_MEMO
3693
+ // to avoid per-hit LRU churn. Eviction merely repeats the source scan.
3694
+ const GET_STATE_FILES = new WeakMap();
3695
+ const GET_STATE_FILES_CAP = 8192;
3696
+ /** A const destructuring is a bound reference, so it is eligible even though
3697
+ * arbitrary locally-bound bare calls must never guess a cross-file target. */
3698
+ function matchDestructuredStoreCall(ref, context) {
3699
+ let files = GET_STATE_FILES.get(context);
3700
+ if (!files) {
3701
+ files = new Map();
3702
+ GET_STATE_FILES.set(context, files);
3703
+ }
3704
+ let eligible = files.get(ref.filePath);
3705
+ let source;
3706
+ if (eligible === undefined) {
3707
+ source = context.readFile(ref.filePath);
3708
+ eligible = source?.includes('.getState') ?? false;
3709
+ if (files.size >= GET_STATE_FILES_CAP) {
3710
+ const oldest = files.keys().next().value;
3711
+ if (oldest !== undefined)
3712
+ files.delete(oldest);
3713
+ }
3714
+ files.set(ref.filePath, eligible);
3715
+ }
3716
+ if (!eligible)
3717
+ return null;
3718
+ source ??= context.readFile(ref.filePath);
3719
+ if (!source)
3720
+ return null;
3721
+ const lines = source.split('\n');
3722
+ const start = enclosingScopeStartLine(ref, context) - 1;
3723
+ const before = lines.slice(start, ref.line - 1).concat(lines[ref.line - 1].slice(0, ref.column)).join('\n');
3724
+ const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(before, 'typescript'));
3725
+ const name = ref.referenceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3726
+ const binding = /\bconst\s*\{([^{}]*)\}\s*=\s*([\w$]+)\.getState\s*\(\s*\)/g;
3727
+ // Compare block identities, not just nesting depth: a binding in a sibling
3728
+ // or already-closed block is not in scope at this call.
3729
+ const stackAt = (end) => {
3730
+ const stack = [];
3731
+ for (let i = 0; i < end; i++) {
3732
+ if (code[i] === '{')
3733
+ stack.push(i);
3734
+ else if (code[i] === '}')
3735
+ stack.pop();
3736
+ }
3737
+ return stack;
3738
+ };
3739
+ const callScope = stackAt(code.length);
3740
+ for (const m of [...code.matchAll(binding)].reverse()) {
3741
+ // Plain named bindings only; defaults, rest and computed keys need their
3742
+ // own value tracing rather than a same-name guess.
3743
+ if (!m[1].split(',').some(part => part.trim() === ref.referenceName))
3744
+ continue;
3745
+ const scope = stackAt(m.index);
3746
+ if (!scope.every((pos, i) => callScope[i] === pos))
3747
+ continue;
3748
+ const rest = code.slice(m.index + m[0].length);
3749
+ // Keep the guard when another declaration shadows the captured const.
3750
+ if (new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${name}\\b|\\{[^}]*\\b${name}\\b)`).test(rest))
3751
+ return null;
3752
+ return resolveStoreAction(`${m[2]}.getState`, ref.referenceName, ref, context);
3753
+ }
3754
+ return null;
3755
+ }
3756
+ /** Bound action names need not have a same-named definition (selectors may
3757
+ * rename them). The resolver's symbol-existence prefilter must allow them. */
3758
+ function matchJsStoreBindingCall(ref, context) {
3759
+ if (!isBareJsCall(ref, context))
3760
+ return null;
3761
+ return matchDestructuredStoreCall(ref, context) ?? matchSelectedStoreCall(ref, context);
3762
+ }
3763
+ /** A qualified untyped chain is useful source evidence, not permission to
3764
+ * infer a property type. Framework resolution runs before this guard. */
3765
+ function isUnresolvedJsMemberCall(ref) {
3766
+ return ref.referenceKind === 'calls' && JS_FAMILY.has(ref.language) &&
3767
+ !/^(?:this|window)\./.test(ref.referenceName) &&
3768
+ /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*){2,}$/.test(ref.referenceName);
3769
+ }
3770
+ const SELECTOR_NAMES = new WeakMap();
3771
+ /** A selector returns the named action from one identified store. Keep the
3772
+ * lexical block identity so closures may capture it but sibling scopes and
3773
+ * shadowing parameters/declarations cannot donate a binding. */
3774
+ function matchSelectedStoreCall(ref, context) {
3775
+ const source = context.readFile(ref.filePath);
3776
+ if (!source?.includes('=>'))
3777
+ return null;
3778
+ let files = SELECTOR_NAMES.get(context);
3779
+ if (!files) {
3780
+ files = new Map();
3781
+ SELECTOR_NAMES.set(context, files);
3782
+ }
3783
+ let names = files.get(ref.filePath);
3784
+ if (!names) {
3785
+ names = new Set([...source.matchAll(/\bconst\s+([\w$]+)\s*=\s*[\w$]+\s*\(\s*(?:\(\s*[\w$]+\s*\)|[\w$]+)\s*=>/g)].map(m => m[1]));
3786
+ files.set(ref.filePath, names);
3787
+ }
3788
+ if (!names.has(ref.referenceName))
3789
+ return null;
3790
+ const name = ref.referenceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3791
+ const lines = source.split('\n');
3792
+ const before = lines.slice(0, ref.line - 1).concat(lines[ref.line - 1].slice(0, ref.column)).join('\n');
3793
+ const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(before, 'typescript'));
3794
+ const binding = new RegExp(`\\bconst\\s+${name}\\s*=\\s*([\\w$]+)\\s*\\(\\s*(?:\\(\\s*([\\w$]+)\\s*\\)|([\\w$]+))\\s*=>\\s*([\\w$]+)\\.([\\w$]+)\\s*\\)`, 'g');
3795
+ const stackAt = (end) => {
3796
+ const stack = [];
3797
+ for (let i = 0; i < end; i++) {
3798
+ if (code[i] === '{')
3799
+ stack.push(i);
3800
+ else if (code[i] === '}')
3801
+ stack.pop();
3802
+ }
3803
+ return stack;
3804
+ };
3805
+ const callScope = stackAt(code.length);
3806
+ for (const m of [...code.matchAll(binding)].reverse()) {
3807
+ if ((m[2] ?? m[3]) !== m[4])
3808
+ continue;
3809
+ if (!stackAt(m.index).every((pos, i) => callScope[i] === pos))
3810
+ continue;
3811
+ const rest = code.slice(m.index + m[0].length);
3812
+ if (new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${name}\\b|\\{[^}]*\\b${name}\\b)`).test(rest) ||
3813
+ hasParameterBinding(rest, name))
3814
+ return null;
3815
+ return resolveStoreAction(`${m[1]}.getState`, m[5], ref, context, true);
3816
+ }
3817
+ return null;
3818
+ }
3819
+ /** Import resolution names the module binding; a nearer parameter or block
3820
+ * declaration can shadow that binding at this particular call site. */
3821
+ function importShadowedAt(name, ref, context) {
3822
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3823
+ for (const fn of context.getNodesInFile(ref.filePath)) {
3824
+ if ((fn.kind === 'function' || fn.kind === 'method') && fn.startLine <= ref.line && fn.endLine >= ref.line &&
3825
+ fn.signature && hasParameterBinding(`${fn.signature} {`, escaped))
3826
+ return true;
3827
+ }
3828
+ const lines = (context.readFile(ref.filePath) ?? '').split('\n');
3829
+ const before = lines.slice(0, ref.line - 1).concat(lines[ref.line - 1]?.slice(0, ref.column) ?? '').join('\n');
3830
+ const code = (0, strip_comments_1.blankStringContents)((0, strip_comments_1.stripCommentsForRegex)(before, 'typescript'));
3831
+ const stackAt = (end) => {
3832
+ const stack = [];
3833
+ for (let i = 0; i < end; i++) {
3834
+ if (code[i] === '{')
3835
+ stack.push(i);
3836
+ else if (code[i] === '}')
3837
+ stack.pop();
3838
+ }
3839
+ return stack;
3840
+ };
3841
+ const scope = stackAt(code.length);
3842
+ const declarations = new RegExp(`\\b(?:const|let|var|function|class)\\s+(?:${escaped}\\b|\\{[^}]*\\b${escaped}\\b)`, 'g');
3843
+ return [...code.matchAll(declarations)].some(m => stackAt(m.index).every((p, i) => scope[i] === p));
3844
+ }
3845
+ /** Balanced parameter lists also cover function-typed parameters, whose own
3846
+ * parentheses must not make the outer shadow invisible. Conservative when a
3847
+ * parameter's type mentions the same name: leave that call unresolved. */
3848
+ function hasParameterBinding(code, escapedName) {
3849
+ const name = new RegExp(`\\b${escapedName}\\b`);
3850
+ if (new RegExp(`\\b${escapedName}\\s*=>`).test(code))
3851
+ return true;
3852
+ for (let i = 0; i < code.length; i++) {
3853
+ if (code[i] !== '(' || /\b(?:if|while|for|switch|with)\s*$/.test(code.slice(0, i)))
3854
+ continue;
3855
+ let depth = 1, j = i + 1;
3856
+ for (; j < code.length && depth; j++) {
3857
+ if (code[j] === '(')
3858
+ depth++;
3859
+ else if (code[j] === ')')
3860
+ depth--;
3861
+ }
3862
+ if (depth === 0 && name.test(code.slice(i + 1, j - 1)) &&
3863
+ /^\s*(?::[^=;{]*)?(?:=>|\{)/.test(code.slice(j)))
3864
+ return true;
3865
+ }
3866
+ return false;
3867
+ }
1817
3868
  /**
1818
3869
  * Split a camelCase or PascalCase string into words.
1819
3870
  */
@@ -1912,6 +3963,7 @@ function findBestMatch(ref, candidates, _context) {
1912
3963
  if (ref.referenceKind === 'instantiates') {
1913
3964
  if (candidate.kind === 'class' ||
1914
3965
  candidate.kind === 'struct' ||
3966
+ candidate.kind === 'union' ||
1915
3967
  candidate.kind === 'interface') {
1916
3968
  score += 25;
1917
3969
  }
@@ -1952,11 +4004,38 @@ function matchFuzzy(ref, context) {
1952
4004
  const candidates = context.getNodesByLowerName(lowerName);
1953
4005
  // Filter to callable kinds only (function, method, class)
1954
4006
  const callableKinds = new Set(['function', 'method', 'class']);
1955
- const callableCandidates = applyLanguageGate(candidates.filter((n) => callableKinds.has(n.kind)), ref);
4007
+ const callableCandidates = candidates.filter((n) => callableKinds.has(n.kind))
4008
+ .filter((n) => (ref.referenceKind !== 'references' && ref.referenceKind !== 'function_ref') ||
4009
+ sameLanguageFamily(n.language, ref.language));
1956
4010
  // Prefer same-language matches
1957
4011
  const sameLanguageCandidates = callableCandidates.filter(n => n.language === ref.language);
1958
4012
  const finalCandidates = sameLanguageCandidates.length > 0 ? sameLanguageCandidates : callableCandidates;
1959
- if (finalCandidates.length === 1) {
4013
+ // Both post-pipeline visibility guards (#1745 language-local + #1719 sealed
4014
+ // module). The sealed-module test rejects the survivor and never filters the
4015
+ // set that produced it: removing a sealed candidate from a crowd would leave
4016
+ // a lone one and manufacture a 0.5 guess out of an ambiguity fuzzy declines.
4017
+ // Also decline a bare JS/TS call whose only survivor is a method or a
4018
+ // cross-file name the file already binds locally (#1714).
4019
+ // A function nested inside another function is only callable from inside
4020
+ // its container (#1230), so a builtin method call (`res.text()`) whose only
4021
+ // same-named project symbol is some file's closure must decline (#1708).
4022
+ // The check sits on the ONE candidate this strategy would commit to, not on
4023
+ // the candidate set: filtering the unreachable ones out of a crowd would
4024
+ // leave a single survivor and hand it every call of that name — on vite,
4025
+ // `import { resolve } from 'node:path'` in a dozen playground configs onto
4026
+ // the one reachable `resolve` method (#1709). Reachability may reject a
4027
+ // unique guess; it must never manufacture one.
4028
+ if (finalCandidates.length === 1 &&
4029
+ isVisibleAcrossFiles(finalCandidates[0], ref, context) &&
4030
+ isCrossFileReachable(finalCandidates[0], ref, context) &&
4031
+ !(isBareJsCall(ref, context) &&
4032
+ (finalCandidates[0].kind === 'method' ||
4033
+ (finalCandidates[0].filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context)))) &&
4034
+ !(finalCandidates[0].kind === 'method' && isBareGoCall(ref, context)) &&
4035
+ // A bare PHP call is a function call (case-insensitive, so fuzzy may find
4036
+ // one) — never the class `View` for `view(…)`, never a method.
4037
+ !(finalCandidates[0].kind !== 'function' && isBarePhpCall(ref, context)) &&
4038
+ isLexicallyReachable(finalCandidates[0], ref, context)) {
1960
4039
  const isCrossLanguage = finalCandidates[0].language !== ref.language;
1961
4040
  return {
1962
4041
  original: ref,
@@ -2012,6 +4091,9 @@ function dumpNameMatcherProfile(label) {
2012
4091
  }
2013
4092
  }
2014
4093
  function matchReference(ref, context) {
4094
+ return gateLanguageMatch(matchReferenceInner(ref, context), ref, context);
4095
+ }
4096
+ function matchReferenceInner(ref, context) {
2015
4097
  // Function-as-value refs (#756) resolve ONLY through the dedicated matcher —
2016
4098
  // never the fuzzy/qualified fallthrough below (a wrong callback edge is
2017
4099
  // worse than none).
@@ -2069,6 +4151,48 @@ function matchReference(ref, context) {
2069
4151
  resolvedBy: 'exact-match',
2070
4152
  };
2071
4153
  }
4154
+ // Erlang call/fun refs carry the call-site arity (`f/1` — #1610) because
4155
+ // arity is part of the function's identity and every erlang function's
4156
+ // qualifiedName carries it (`mod::f/1`). Resolve ONLY to a definition of
4157
+ // that exact arity: the call site's own file first (a local call targets its
4158
+ // own module by language semantics; `-import`ed functions ride the
4159
+ // cross-file branch), and when no definition of that arity exists anywhere,
4160
+ // resolve to NOTHING rather than a sibling arity — the real target may be
4161
+ // macro-generated or out of repo, and a wrong-arity edge is worse than none.
4162
+ if (ref.language === 'erlang' &&
4163
+ !ref.referenceName.includes('::') &&
4164
+ (ref.referenceKind === 'calls' || ref.referenceKind === 'references')) {
4165
+ const am = /^(.+)\/(\d{1,3})$/.exec(ref.referenceName);
4166
+ if (am) {
4167
+ // endsWith is length-anchored, so `/1` cannot match `…/11`.
4168
+ const arityTail = `/${am[2]}`;
4169
+ const candidates = context
4170
+ .getNodesByName(am[1])
4171
+ .filter((n) => n.language === 'erlang' && n.kind === 'function' && n.qualifiedName.endsWith(arityTail));
4172
+ if (candidates.length > 0) {
4173
+ const sameFile = candidates.find((n) => n.filePath === ref.filePath);
4174
+ if (sameFile) {
4175
+ return { original: ref, targetNodeId: sameFile.id, confidence: 0.95, resolvedBy: 'exact-match' };
4176
+ }
4177
+ if (candidates.length === 1) {
4178
+ return { original: ref, targetNodeId: candidates[0].id, confidence: 0.8, resolvedBy: 'exact-match' };
4179
+ }
4180
+ const best = findBestMatch(ref, candidates, context);
4181
+ if (best) {
4182
+ const proximity = computePathProximity(ref.filePath, best.filePath);
4183
+ return {
4184
+ original: ref,
4185
+ targetNodeId: best.id,
4186
+ confidence: proximity >= 30 ? 0.7 : 0.4,
4187
+ resolvedBy: 'exact-match',
4188
+ };
4189
+ }
4190
+ }
4191
+ return null;
4192
+ }
4193
+ }
4194
+ if (isUnresolvedJsMemberCall(ref))
4195
+ return null;
2072
4196
  // Try strategies in order of confidence
2073
4197
  let result;
2074
4198
  // 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
@@ -2116,6 +4240,16 @@ function matchReference(ref, context) {
2116
4240
  if (result)
2117
4241
  return result;
2118
4242
  }
4243
+ // A call-receiver chain the extractor encoded as `<inner>().<method>` for a
4244
+ // language with no chain resolver above (TS/JS, Python — #1683) is a
4245
+ // receiver whose type is unknown. Nothing below may guess for it: the
4246
+ // method-call pattern rejects the parens, exact name never matches, but the
4247
+ // fuzzy strategy splits on `.` and would hand `make().run` to any `run` —
4248
+ // the fabricated edge the encoding exists to prevent.
4249
+ if (ref.referenceName.includes('().') &&
4250
+ (ref.language === 'typescript' || ref.language === 'javascript' || ref.language === 'tsx' || ref.language === 'jsx' || ref.language === 'python')) {
4251
+ return nmTimed('storeAccessorChain', ref, () => matchStoreAccessorChain(ref, context));
4252
+ }
2119
4253
  // 2. Method call pattern
2120
4254
  result = nmTimed('methodCall', ref, () => matchMethodCall(ref, context));
2121
4255
  if (result)