codeguardian-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/dist/agent/agentTools.d.ts +26 -0
  4. package/dist/agent/agentTools.d.ts.map +1 -0
  5. package/dist/agent/agentTools.js +699 -0
  6. package/dist/agent/agentTools.js.map +1 -0
  7. package/dist/agent/autoValidator.d.ts +110 -0
  8. package/dist/agent/autoValidator.d.ts.map +1 -0
  9. package/dist/agent/autoValidator.js +964 -0
  10. package/dist/agent/autoValidator.js.map +1 -0
  11. package/dist/agent/fileWatcher.d.ts +28 -0
  12. package/dist/agent/fileWatcher.d.ts.map +1 -0
  13. package/dist/agent/fileWatcher.js +88 -0
  14. package/dist/agent/fileWatcher.js.map +1 -0
  15. package/dist/agent/guardianPersistence.d.ts +98 -0
  16. package/dist/agent/guardianPersistence.d.ts.map +1 -0
  17. package/dist/agent/guardianPersistence.js +296 -0
  18. package/dist/agent/guardianPersistence.js.map +1 -0
  19. package/dist/agent/mcpNotifications.d.ts +38 -0
  20. package/dist/agent/mcpNotifications.d.ts.map +1 -0
  21. package/dist/agent/mcpNotifications.js +81 -0
  22. package/dist/agent/mcpNotifications.js.map +1 -0
  23. package/dist/analyzers/aiPatterns.d.ts +16 -0
  24. package/dist/analyzers/aiPatterns.d.ts.map +1 -0
  25. package/dist/analyzers/aiPatterns.js +103 -0
  26. package/dist/analyzers/aiPatterns.js.map +1 -0
  27. package/dist/analyzers/antiPatterns.d.ts +60 -0
  28. package/dist/analyzers/antiPatterns.d.ts.map +1 -0
  29. package/dist/analyzers/antiPatterns.js +198 -0
  30. package/dist/analyzers/antiPatterns.js.map +1 -0
  31. package/dist/analyzers/builtinTypes.d.ts +18 -0
  32. package/dist/analyzers/builtinTypes.d.ts.map +1 -0
  33. package/dist/analyzers/builtinTypes.js +1275 -0
  34. package/dist/analyzers/builtinTypes.js.map +1 -0
  35. package/dist/analyzers/complexity.d.ts +14 -0
  36. package/dist/analyzers/complexity.d.ts.map +1 -0
  37. package/dist/analyzers/complexity.js +610 -0
  38. package/dist/analyzers/complexity.js.map +1 -0
  39. package/dist/analyzers/findingVerifier.d.ts +59 -0
  40. package/dist/analyzers/findingVerifier.d.ts.map +1 -0
  41. package/dist/analyzers/findingVerifier.js +1169 -0
  42. package/dist/analyzers/findingVerifier.js.map +1 -0
  43. package/dist/analyzers/impactAnalyzer.d.ts +53 -0
  44. package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
  45. package/dist/analyzers/impactAnalyzer.js +152 -0
  46. package/dist/analyzers/impactAnalyzer.js.map +1 -0
  47. package/dist/analyzers/languageDetector.d.ts +48 -0
  48. package/dist/analyzers/languageDetector.d.ts.map +1 -0
  49. package/dist/analyzers/languageDetector.js +404 -0
  50. package/dist/analyzers/languageDetector.js.map +1 -0
  51. package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
  52. package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
  53. package/dist/analyzers/parsers/incrementalParser.js +193 -0
  54. package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
  55. package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
  56. package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
  57. package/dist/analyzers/parsers/scopeResolver.js +324 -0
  58. package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
  59. package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
  60. package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
  61. package/dist/analyzers/parsers/semanticIndex.js +429 -0
  62. package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
  63. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
  64. package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
  65. package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
  66. package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
  67. package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
  68. package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
  69. package/dist/analyzers/parsers/treeSitterParser.js +709 -0
  70. package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
  71. package/dist/analyzers/relevanceScorer.d.ts +43 -0
  72. package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
  73. package/dist/analyzers/relevanceScorer.js +200 -0
  74. package/dist/analyzers/relevanceScorer.js.map +1 -0
  75. package/dist/analyzers/standardLibrary.d.ts +22 -0
  76. package/dist/analyzers/standardLibrary.d.ts.map +1 -0
  77. package/dist/analyzers/standardLibrary.js +211 -0
  78. package/dist/analyzers/standardLibrary.js.map +1 -0
  79. package/dist/analyzers/symbolGraph.d.ts +30 -0
  80. package/dist/analyzers/symbolGraph.d.ts.map +1 -0
  81. package/dist/analyzers/symbolGraph.js +380 -0
  82. package/dist/analyzers/symbolGraph.js.map +1 -0
  83. package/dist/analyzers/symbolTable.d.ts +18 -0
  84. package/dist/analyzers/symbolTable.d.ts.map +1 -0
  85. package/dist/analyzers/symbolTable.js +176 -0
  86. package/dist/analyzers/symbolTable.js.map +1 -0
  87. package/dist/analyzers/typeChecker.d.ts +13 -0
  88. package/dist/analyzers/typeChecker.d.ts.map +1 -0
  89. package/dist/analyzers/typeChecker.js +580 -0
  90. package/dist/analyzers/typeChecker.js.map +1 -0
  91. package/dist/analyzers/usagePatterns.d.ts +42 -0
  92. package/dist/analyzers/usagePatterns.d.ts.map +1 -0
  93. package/dist/analyzers/usagePatterns.js +75 -0
  94. package/dist/analyzers/usagePatterns.js.map +1 -0
  95. package/dist/api-contract/context/backend.d.ts +19 -0
  96. package/dist/api-contract/context/backend.d.ts.map +1 -0
  97. package/dist/api-contract/context/backend.js +64 -0
  98. package/dist/api-contract/context/backend.js.map +1 -0
  99. package/dist/api-contract/context/contract.d.ts +34 -0
  100. package/dist/api-contract/context/contract.d.ts.map +1 -0
  101. package/dist/api-contract/context/contract.js +306 -0
  102. package/dist/api-contract/context/contract.js.map +1 -0
  103. package/dist/api-contract/context/frontend.d.ts +19 -0
  104. package/dist/api-contract/context/frontend.d.ts.map +1 -0
  105. package/dist/api-contract/context/frontend.js +64 -0
  106. package/dist/api-contract/context/frontend.js.map +1 -0
  107. package/dist/api-contract/detector.d.ts +28 -0
  108. package/dist/api-contract/detector.d.ts.map +1 -0
  109. package/dist/api-contract/detector.js +393 -0
  110. package/dist/api-contract/detector.js.map +1 -0
  111. package/dist/api-contract/extractors/python.d.ts +32 -0
  112. package/dist/api-contract/extractors/python.d.ts.map +1 -0
  113. package/dist/api-contract/extractors/python.js +521 -0
  114. package/dist/api-contract/extractors/python.js.map +1 -0
  115. package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
  116. package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
  117. package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
  118. package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
  119. package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
  120. package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
  121. package/dist/api-contract/extractors/tsAstUtils.js +173 -0
  122. package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
  123. package/dist/api-contract/extractors/typescript.d.ts +32 -0
  124. package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
  125. package/dist/api-contract/extractors/typescript.js +666 -0
  126. package/dist/api-contract/extractors/typescript.js.map +1 -0
  127. package/dist/api-contract/index.d.ts +104 -0
  128. package/dist/api-contract/index.d.ts.map +1 -0
  129. package/dist/api-contract/index.js +232 -0
  130. package/dist/api-contract/index.js.map +1 -0
  131. package/dist/api-contract/types.d.ts +151 -0
  132. package/dist/api-contract/types.d.ts.map +1 -0
  133. package/dist/api-contract/types.js +19 -0
  134. package/dist/api-contract/types.js.map +1 -0
  135. package/dist/api-contract/validators/endpoint.d.ts +21 -0
  136. package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
  137. package/dist/api-contract/validators/endpoint.js +224 -0
  138. package/dist/api-contract/validators/endpoint.js.map +1 -0
  139. package/dist/api-contract/validators/index.d.ts +40 -0
  140. package/dist/api-contract/validators/index.d.ts.map +1 -0
  141. package/dist/api-contract/validators/index.js +875 -0
  142. package/dist/api-contract/validators/index.js.map +1 -0
  143. package/dist/api-contract/validators/parameter.d.ts +17 -0
  144. package/dist/api-contract/validators/parameter.d.ts.map +1 -0
  145. package/dist/api-contract/validators/parameter.js +250 -0
  146. package/dist/api-contract/validators/parameter.js.map +1 -0
  147. package/dist/api-contract/validators/type.d.ts +38 -0
  148. package/dist/api-contract/validators/type.d.ts.map +1 -0
  149. package/dist/api-contract/validators/type.js +244 -0
  150. package/dist/api-contract/validators/type.js.map +1 -0
  151. package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
  152. package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
  153. package/dist/context/apiContract/complexTypeSupport.js +665 -0
  154. package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
  155. package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
  156. package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
  157. package/dist/context/apiContract/graphqlSupport.js +671 -0
  158. package/dist/context/apiContract/graphqlSupport.js.map +1 -0
  159. package/dist/context/apiContract/index.d.ts +14 -0
  160. package/dist/context/apiContract/index.d.ts.map +1 -0
  161. package/dist/context/apiContract/index.js +17 -0
  162. package/dist/context/apiContract/index.js.map +1 -0
  163. package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
  164. package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
  165. package/dist/context/apiContract/webSocketSupport.js +465 -0
  166. package/dist/context/apiContract/webSocketSupport.js.map +1 -0
  167. package/dist/context/apiContractContext.d.ts +15 -0
  168. package/dist/context/apiContractContext.d.ts.map +1 -0
  169. package/dist/context/apiContractContext.js +979 -0
  170. package/dist/context/apiContractContext.js.map +1 -0
  171. package/dist/context/apiContractExtraction.d.ts +52 -0
  172. package/dist/context/apiContractExtraction.d.ts.map +1 -0
  173. package/dist/context/apiContractExtraction.js +438 -0
  174. package/dist/context/apiContractExtraction.js.map +1 -0
  175. package/dist/context/contextLineage.d.ts +79 -0
  176. package/dist/context/contextLineage.d.ts.map +1 -0
  177. package/dist/context/contextLineage.js +259 -0
  178. package/dist/context/contextLineage.js.map +1 -0
  179. package/dist/context/contextOrchestrator.d.ts +57 -0
  180. package/dist/context/contextOrchestrator.d.ts.map +1 -0
  181. package/dist/context/contextOrchestrator.js +162 -0
  182. package/dist/context/contextOrchestrator.js.map +1 -0
  183. package/dist/context/intentTracker.d.ts +73 -0
  184. package/dist/context/intentTracker.d.ts.map +1 -0
  185. package/dist/context/intentTracker.js +168 -0
  186. package/dist/context/intentTracker.js.map +1 -0
  187. package/dist/context/projectContext.d.ts +219 -0
  188. package/dist/context/projectContext.d.ts.map +1 -0
  189. package/dist/context/projectContext.js +1984 -0
  190. package/dist/context/projectContext.js.map +1 -0
  191. package/dist/prompts/index.d.ts +17 -0
  192. package/dist/prompts/index.d.ts.map +1 -0
  193. package/dist/prompts/index.js +260 -0
  194. package/dist/prompts/index.js.map +1 -0
  195. package/dist/prompts/library.d.ts +51 -0
  196. package/dist/prompts/library.d.ts.map +1 -0
  197. package/dist/prompts/library.js +65 -0
  198. package/dist/prompts/library.js.map +1 -0
  199. package/dist/prompts/templates.d.ts +44 -0
  200. package/dist/prompts/templates.d.ts.map +1 -0
  201. package/dist/prompts/templates.js +97 -0
  202. package/dist/prompts/templates.js.map +1 -0
  203. package/dist/queue/jobPersistence.d.ts +46 -0
  204. package/dist/queue/jobPersistence.d.ts.map +1 -0
  205. package/dist/queue/jobPersistence.js +158 -0
  206. package/dist/queue/jobPersistence.js.map +1 -0
  207. package/dist/queue/jobQueue.d.ts +116 -0
  208. package/dist/queue/jobQueue.d.ts.map +1 -0
  209. package/dist/queue/jobQueue.js +275 -0
  210. package/dist/queue/jobQueue.js.map +1 -0
  211. package/dist/queue/validationJob.d.ts +69 -0
  212. package/dist/queue/validationJob.d.ts.map +1 -0
  213. package/dist/queue/validationJob.js +435 -0
  214. package/dist/queue/validationJob.js.map +1 -0
  215. package/dist/resources/index.d.ts +15 -0
  216. package/dist/resources/index.d.ts.map +1 -0
  217. package/dist/resources/index.js +328 -0
  218. package/dist/resources/index.js.map +1 -0
  219. package/dist/resources/validationReportStore.d.ts +170 -0
  220. package/dist/resources/validationReportStore.d.ts.map +1 -0
  221. package/dist/resources/validationReportStore.js +515 -0
  222. package/dist/resources/validationReportStore.js.map +1 -0
  223. package/dist/server.d.ts +12 -0
  224. package/dist/server.d.ts.map +1 -0
  225. package/dist/server.js +102 -0
  226. package/dist/server.js.map +1 -0
  227. package/dist/tools/asyncValidation.d.ts +19 -0
  228. package/dist/tools/asyncValidation.d.ts.map +1 -0
  229. package/dist/tools/asyncValidation.js +346 -0
  230. package/dist/tools/asyncValidation.js.map +1 -0
  231. package/dist/tools/buildContext.d.ts +17 -0
  232. package/dist/tools/buildContext.d.ts.map +1 -0
  233. package/dist/tools/buildContext.js +188 -0
  234. package/dist/tools/buildContext.js.map +1 -0
  235. package/dist/tools/getDependencyGraph.d.ts +16 -0
  236. package/dist/tools/getDependencyGraph.d.ts.map +1 -0
  237. package/dist/tools/getDependencyGraph.js +436 -0
  238. package/dist/tools/getDependencyGraph.js.map +1 -0
  239. package/dist/tools/incrementalValidation.d.ts +71 -0
  240. package/dist/tools/incrementalValidation.d.ts.map +1 -0
  241. package/dist/tools/incrementalValidation.js +203 -0
  242. package/dist/tools/incrementalValidation.js.map +1 -0
  243. package/dist/tools/index.d.ts +24 -0
  244. package/dist/tools/index.d.ts.map +1 -0
  245. package/dist/tools/index.js +106 -0
  246. package/dist/tools/index.js.map +1 -0
  247. package/dist/tools/validateCode.d.ts +17 -0
  248. package/dist/tools/validateCode.d.ts.map +1 -0
  249. package/dist/tools/validateCode.js +368 -0
  250. package/dist/tools/validateCode.js.map +1 -0
  251. package/dist/tools/validateCodeLite.d.ts +2 -0
  252. package/dist/tools/validateCodeLite.d.ts.map +1 -0
  253. package/dist/tools/validateCodeLite.js +2 -0
  254. package/dist/tools/validateCodeLite.js.map +1 -0
  255. package/dist/tools/validation/builtins.d.ts +92 -0
  256. package/dist/tools/validation/builtins.d.ts.map +1 -0
  257. package/dist/tools/validation/builtins.js +2184 -0
  258. package/dist/tools/validation/builtins.js.map +1 -0
  259. package/dist/tools/validation/contextualNaming.d.ts +99 -0
  260. package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
  261. package/dist/tools/validation/contextualNaming.js +959 -0
  262. package/dist/tools/validation/contextualNaming.js.map +1 -0
  263. package/dist/tools/validation/deadCode.d.ts +115 -0
  264. package/dist/tools/validation/deadCode.d.ts.map +1 -0
  265. package/dist/tools/validation/deadCode.js +861 -0
  266. package/dist/tools/validation/deadCode.js.map +1 -0
  267. package/dist/tools/validation/extractors/index.d.ts +131 -0
  268. package/dist/tools/validation/extractors/index.d.ts.map +1 -0
  269. package/dist/tools/validation/extractors/index.js +233 -0
  270. package/dist/tools/validation/extractors/index.js.map +1 -0
  271. package/dist/tools/validation/extractors/javascript.d.ts +73 -0
  272. package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
  273. package/dist/tools/validation/extractors/javascript.js +1841 -0
  274. package/dist/tools/validation/extractors/javascript.js.map +1 -0
  275. package/dist/tools/validation/extractors/python.d.ts +93 -0
  276. package/dist/tools/validation/extractors/python.d.ts.map +1 -0
  277. package/dist/tools/validation/extractors/python.js +799 -0
  278. package/dist/tools/validation/extractors/python.js.map +1 -0
  279. package/dist/tools/validation/manifest.d.ts +45 -0
  280. package/dist/tools/validation/manifest.d.ts.map +1 -0
  281. package/dist/tools/validation/manifest.js +719 -0
  282. package/dist/tools/validation/manifest.js.map +1 -0
  283. package/dist/tools/validation/parser.d.ts +58 -0
  284. package/dist/tools/validation/parser.d.ts.map +1 -0
  285. package/dist/tools/validation/parser.js +232 -0
  286. package/dist/tools/validation/parser.js.map +1 -0
  287. package/dist/tools/validation/registry.d.ts +15 -0
  288. package/dist/tools/validation/registry.d.ts.map +1 -0
  289. package/dist/tools/validation/registry.js +169 -0
  290. package/dist/tools/validation/registry.js.map +1 -0
  291. package/dist/tools/validation/scoring.d.ts +54 -0
  292. package/dist/tools/validation/scoring.d.ts.map +1 -0
  293. package/dist/tools/validation/scoring.js +242 -0
  294. package/dist/tools/validation/scoring.js.map +1 -0
  295. package/dist/tools/validation/types.d.ts +120 -0
  296. package/dist/tools/validation/types.d.ts.map +1 -0
  297. package/dist/tools/validation/types.js +11 -0
  298. package/dist/tools/validation/types.js.map +1 -0
  299. package/dist/tools/validation/unusedLocals.d.ts +36 -0
  300. package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
  301. package/dist/tools/validation/unusedLocals.js +333 -0
  302. package/dist/tools/validation/unusedLocals.js.map +1 -0
  303. package/dist/tools/validation/validation.d.ts +98 -0
  304. package/dist/tools/validation/validation.d.ts.map +1 -0
  305. package/dist/tools/validation/validation.js +1837 -0
  306. package/dist/tools/validation/validation.js.map +1 -0
  307. package/dist/types/codeGraph.d.ts +163 -0
  308. package/dist/types/codeGraph.d.ts.map +1 -0
  309. package/dist/types/codeGraph.js +9 -0
  310. package/dist/types/codeGraph.js.map +1 -0
  311. package/dist/types/symbolGraph.d.ts +68 -0
  312. package/dist/types/symbolGraph.d.ts.map +1 -0
  313. package/dist/types/symbolGraph.js +10 -0
  314. package/dist/types/symbolGraph.js.map +1 -0
  315. package/dist/types/tools.d.ts +43 -0
  316. package/dist/types/tools.d.ts.map +1 -0
  317. package/dist/types/tools.js +7 -0
  318. package/dist/types/tools.js.map +1 -0
  319. package/dist/utils/fileFilter.d.ts +37 -0
  320. package/dist/utils/fileFilter.d.ts.map +1 -0
  321. package/dist/utils/fileFilter.js +91 -0
  322. package/dist/utils/fileFilter.js.map +1 -0
  323. package/dist/utils/gitUtils.d.ts +28 -0
  324. package/dist/utils/gitUtils.d.ts.map +1 -0
  325. package/dist/utils/gitUtils.js +81 -0
  326. package/dist/utils/gitUtils.js.map +1 -0
  327. package/dist/utils/logger.d.ts +15 -0
  328. package/dist/utils/logger.d.ts.map +1 -0
  329. package/dist/utils/logger.js +38 -0
  330. package/dist/utils/logger.js.map +1 -0
  331. package/dist/utils/serialization.d.ts +25 -0
  332. package/dist/utils/serialization.d.ts.map +1 -0
  333. package/dist/utils/serialization.js +53 -0
  334. package/dist/utils/serialization.js.map +1 -0
  335. package/package.json +90 -0
@@ -0,0 +1,1841 @@
1
+ /**
2
+ * JavaScript/TypeScript AST Extraction Module
3
+ *
4
+ * This module provides functions to extract symbols, usages, imports, and type references
5
+ * from JavaScript and TypeScript code using Tree-sitter AST parsing.
6
+ *
7
+ * Handles:
8
+ * - Function declarations, arrow functions, class declarations, method definitions
9
+ * - Function calls, method calls, instantiations (new expressions)
10
+ * - ES6 imports, dynamic imports
11
+ * - TypeScript type annotations, generic parameters, type references
12
+ * - Destructuring patterns (object and array)
13
+ *
14
+ * @format
15
+ */
16
+ import { isJSBuiltin, isTSBuiltinType } from "../builtins.js";
17
+ // ============================================================================
18
+ // Symbol Extraction
19
+ // ============================================================================
20
+ /**
21
+ * Extract symbol definitions from JavaScript/TypeScript AST
22
+ * Finds: functions, classes, methods, variables, interfaces, types, enums
23
+ */
24
+ export function extractJSSymbols(node, code, filePath, symbols, currentClass, options = {}) {
25
+ if (!node)
26
+ return;
27
+ switch (node.type) {
28
+ case "function_declaration":
29
+ case "function_expression": {
30
+ const nameNode = node.childForFieldName("name");
31
+ const paramsNode = node.childForFieldName("parameters");
32
+ if (nameNode) {
33
+ const name = getText(nameNode, code);
34
+ const params = extractJSParams(paramsNode, code, filePath, symbols, options);
35
+ const isAsync = node.children.some((c) => getText(c, code) === "async");
36
+ const isExported = node.parent?.type === "export_statement";
37
+ symbols.push({
38
+ name,
39
+ type: "function",
40
+ file: filePath,
41
+ line: node.startPosition.row + 1,
42
+ column: node.startPosition.column,
43
+ params,
44
+ paramCount: params.length,
45
+ isAsync,
46
+ isExported,
47
+ });
48
+ }
49
+ break;
50
+ }
51
+ case "lexical_declaration":
52
+ case "variable_declaration": {
53
+ // const/let/var declarations
54
+ // Check if this declaration is exported
55
+ const isExported = node.parent?.type === "export_statement";
56
+ for (const child of node.children) {
57
+ if (child.type === "variable_declarator") {
58
+ const nameNode = child.childForFieldName("name");
59
+ const valueNode = child.childForFieldName("value");
60
+ if (nameNode) {
61
+ // Handle object destructuring: const { a, b } = something()
62
+ if (nameNode.type === "object_pattern") {
63
+ extractDestructuredNames(nameNode, code, filePath, symbols, node.startPosition.row + 1);
64
+ }
65
+ // Handle array destructuring: const [a, b] = something()
66
+ else if (nameNode.type === "array_pattern") {
67
+ extractDestructuredNames(nameNode, code, filePath, symbols, node.startPosition.row + 1);
68
+ }
69
+ // Handle simple identifier: const x = something
70
+ else {
71
+ const name = getText(nameNode, code);
72
+ // Check if it's a function-valued initializer
73
+ // Tree-sitter uses different node types across grammars:
74
+ // - JavaScript: `function` / `arrow_function`
75
+ // - Some contexts/grammars may surface `function_expression`
76
+ if (valueNode?.type === "arrow_function" ||
77
+ valueNode?.type === "function" ||
78
+ valueNode?.type === "function_expression") {
79
+ const paramsNode = valueNode.childForFieldName("parameters") || valueNode.childForFieldName("parameter");
80
+ const params = extractJSParams(paramsNode, code, filePath, symbols, options);
81
+ const isAsync = valueNode.children.some((c) => getText(c, code) === "async");
82
+ symbols.push({
83
+ name,
84
+ type: "function",
85
+ file: filePath,
86
+ line: node.startPosition.row + 1,
87
+ column: node.startPosition.column,
88
+ params,
89
+ paramCount: params.length,
90
+ isAsync,
91
+ isExported,
92
+ });
93
+ }
94
+ else {
95
+ symbols.push({
96
+ name,
97
+ type: "variable",
98
+ file: filePath,
99
+ line: node.startPosition.row + 1,
100
+ column: node.startPosition.column,
101
+ isExported,
102
+ });
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ break;
109
+ }
110
+ case "arrow_function": {
111
+ const pNode = node.childForFieldName("parameter");
112
+ const psNode = node.childForFieldName("parameters");
113
+ if (pNode) {
114
+ if (pNode.type === "identifier") {
115
+ // Single parameter arrow function: x => x + 1
116
+ symbols.push({
117
+ name: getText(pNode, code),
118
+ type: "variable",
119
+ file: filePath,
120
+ line: pNode.startPosition.row + 1,
121
+ column: pNode.startPosition.column,
122
+ isExported: false,
123
+ });
124
+ }
125
+ else if (pNode.type === "object_pattern" ||
126
+ pNode.type === "array_pattern") {
127
+ extractDestructuredNames(pNode, code, filePath, symbols, node.startPosition.row + 1);
128
+ }
129
+ }
130
+ if (psNode) {
131
+ extractJSParams(psNode, code, filePath, symbols, options);
132
+ }
133
+ // Continue recursion to process function body, but parameter identifiers
134
+ // will be skipped by the case "identifier" check below
135
+ // IMPORTANT: Use return here, not break, to prevent falling through to
136
+ // the recursive call at the end of extractJSSymbols. The recursive call
137
+ // for arrow_function children is handled by the recursive call to
138
+ // extractJSParams and subsequent processing.
139
+ // Actually, we DO need to recurse into the function body to find nested symbols.
140
+ // The break allows the loop at the end of extractJSSymbols to handle recursion.
141
+ break;
142
+ }
143
+ case "class_declaration": {
144
+ const nameNode = node.childForFieldName("name");
145
+ if (nameNode) {
146
+ const className = getText(nameNode, code);
147
+ const isExported = node.parent?.type === "export_statement";
148
+ symbols.push({
149
+ name: className,
150
+ type: "class",
151
+ file: filePath,
152
+ line: node.startPosition.row + 1,
153
+ column: node.startPosition.column,
154
+ isExported,
155
+ });
156
+ // Process class body
157
+ const bodyNode = node.childForFieldName("body");
158
+ if (bodyNode) {
159
+ for (const child of bodyNode.children) {
160
+ extractJSSymbols(child, code, filePath, symbols, className, options);
161
+ }
162
+ }
163
+ return;
164
+ }
165
+ break;
166
+ }
167
+ case "method_definition": {
168
+ const nameNode = node.childForFieldName("name");
169
+ const paramsNode = node.childForFieldName("parameters");
170
+ if (nameNode) {
171
+ const name = getText(nameNode, code);
172
+ if (name !== "constructor") {
173
+ const params = extractJSParams(paramsNode, code, filePath, symbols, options);
174
+ const isAsync = node.children.some((c) => getText(c, code) === "async");
175
+ // Extract return type for TypeScript methods
176
+ const returnTypeNode = node.childForFieldName("return_type");
177
+ const returnType = returnTypeNode ? getText(returnTypeNode, code) : undefined;
178
+ symbols.push({
179
+ name,
180
+ type: "method",
181
+ file: filePath,
182
+ line: node.startPosition.row + 1,
183
+ column: node.startPosition.column,
184
+ params,
185
+ paramCount: params.length,
186
+ scope: currentClass || undefined,
187
+ isAsync,
188
+ returnType,
189
+ });
190
+ }
191
+ }
192
+ break;
193
+ }
194
+ // TypeScript class field declarations (private/public/protected properties)
195
+ // e.g., class Foo { private bar: string = ''; }
196
+ case "public_field_definition":
197
+ case "private_field_definition":
198
+ case "protected_field_definition":
199
+ case "field_definition":
200
+ case "property_definition": {
201
+ const nameNode = node.childForFieldName("name");
202
+ if (nameNode) {
203
+ const name = getText(nameNode, code);
204
+ symbols.push({
205
+ name,
206
+ type: "variable",
207
+ file: filePath,
208
+ line: node.startPosition.row + 1,
209
+ column: node.startPosition.column,
210
+ scope: currentClass || undefined,
211
+ });
212
+ }
213
+ break;
214
+ }
215
+ // TypeScript interface declarations
216
+ case "interface_declaration": {
217
+ const nameNode = node.childForFieldName("name");
218
+ if (nameNode) {
219
+ const name = getText(nameNode, code);
220
+ const isExported = node.parent?.type === "export_statement";
221
+ symbols.push({
222
+ name,
223
+ type: "interface",
224
+ file: filePath,
225
+ line: node.startPosition.row + 1,
226
+ column: node.startPosition.column,
227
+ isExported,
228
+ });
229
+ }
230
+ break;
231
+ }
232
+ // TypeScript type alias declarations
233
+ case "type_alias_declaration": {
234
+ const nameNode = node.childForFieldName("name");
235
+ if (nameNode) {
236
+ const name = getText(nameNode, code);
237
+ const isExported = node.parent?.type === "export_statement";
238
+ symbols.push({
239
+ name,
240
+ type: "type",
241
+ file: filePath,
242
+ line: node.startPosition.row + 1,
243
+ column: node.startPosition.column,
244
+ isExported,
245
+ });
246
+ }
247
+ break;
248
+ }
249
+ // TypeScript enum declarations
250
+ case "enum_declaration": {
251
+ const nameNode = node.childForFieldName("name");
252
+ if (nameNode) {
253
+ const name = getText(nameNode, code);
254
+ const isExported = node.parent?.type === "export_statement";
255
+ symbols.push({
256
+ name,
257
+ type: "variable", // enums are treated as variables
258
+ file: filePath,
259
+ line: node.startPosition.row + 1,
260
+ column: node.startPosition.column,
261
+ isExported,
262
+ });
263
+ }
264
+ break;
265
+ }
266
+ // Object literals: const api = { method: () => {} }
267
+ // Extract methods from object literals to support API client patterns
268
+ case "object": {
269
+ // Check if this object is the value of a variable declarator
270
+ // This handles: const timeEntriesApi = { getPending: async () => {} }
271
+ const parent = node.parent;
272
+ let parentVariableName = null;
273
+ if (parent?.type === "variable_declarator") {
274
+ const nameNode = parent.childForFieldName("name");
275
+ if (nameNode?.type === "identifier") {
276
+ parentVariableName = getText(nameNode, code);
277
+ }
278
+ }
279
+ // Extract all method properties from the object literal
280
+ for (const child of node.children) {
281
+ if (child.type === "pair") {
282
+ const keyNode = child.childForFieldName("key");
283
+ const valueNode = child.childForFieldName("value");
284
+ if (keyNode && valueNode) {
285
+ const keyName = getText(keyNode, code);
286
+ // Check if the value is a function (arrow_function, function, or call_expression that returns a function)
287
+ const isFunctionValue = valueNode.type === "arrow_function" ||
288
+ valueNode.type === "function";
289
+ if (isFunctionValue) {
290
+ const paramsNode = valueNode.childForFieldName("parameters") || valueNode.childForFieldName("parameter");
291
+ // Pass undefined for symbols to avoid registering params as local variables
292
+ // We only want the parameter names for the method signature
293
+ const params = extractJSParams(paramsNode, code, filePath, undefined, options);
294
+ const isAsync = valueNode.children.some((c) => getText(c, code) === "async");
295
+ // Register as a method with scope = parent variable name
296
+ // Note: We don't extract params here - let recursion handle it
297
+ // to avoid duplicate parameter symbols
298
+ symbols.push({
299
+ name: keyName,
300
+ type: "method",
301
+ file: filePath,
302
+ line: child.startPosition.row + 1,
303
+ column: child.startPosition.column,
304
+ params,
305
+ paramCount: params.length,
306
+ scope: parentVariableName || undefined,
307
+ isAsync,
308
+ isExported: parent?.parent?.parent?.type === "export_statement",
309
+ });
310
+ // Recurse into the arrow function to extract its parameter symbols
311
+ // This ensures parameters like (id) in { mutationFn: (id) => ... } are registered
312
+ extractJSSymbols(valueNode, code, filePath, symbols, null, options);
313
+ }
314
+ else {
315
+ // Not a function value, recurse to find any nested symbols
316
+ extractJSSymbols(valueNode, code, filePath, symbols, null, options);
317
+ }
318
+ }
319
+ }
320
+ // Handle shorthand methods: { method() {} }
321
+ else if (child.type === "method_definition") {
322
+ const nameNode = child.childForFieldName("name");
323
+ const paramsNode = child.childForFieldName("parameters");
324
+ if (nameNode) {
325
+ const name = getText(nameNode, code);
326
+ // Pass symbols to register params as local variables
327
+ const params = extractJSParams(paramsNode, code, filePath, symbols, options);
328
+ const isAsync = child.children.some((c) => getText(c, code) === "async");
329
+ // Extract return type for TypeScript methods
330
+ const returnTypeNode = child.childForFieldName("return_type");
331
+ const returnType = returnTypeNode ? getText(returnTypeNode, code) : undefined;
332
+ symbols.push({
333
+ name,
334
+ type: "method",
335
+ file: filePath,
336
+ line: child.startPosition.row + 1,
337
+ column: child.startPosition.column,
338
+ params,
339
+ paramCount: params.length,
340
+ scope: parentVariableName || undefined,
341
+ isAsync,
342
+ isExported: parent?.parent?.parent?.type === "export_statement",
343
+ returnType,
344
+ });
345
+ // Recurse into method body to extract any nested symbols
346
+ const bodyNode = child.childForFieldName("body");
347
+ if (bodyNode) {
348
+ extractJSSymbols(bodyNode, code, filePath, symbols, null, options);
349
+ }
350
+ }
351
+ }
352
+ else if (child.type !== "{" && child.type !== "}" && child.type !== ",") {
353
+ // Recurse into other children (but skip punctuation)
354
+ extractJSSymbols(child, code, filePath, symbols, null, options);
355
+ }
356
+ }
357
+ // Use break (not return) so we don't process the same nodes again in the main recursion
358
+ // But we've already handled all children, so this prevents double-processing
359
+ return;
360
+ }
361
+ // Catch clause parameters: catch (error) { ... }
362
+ case "catch_clause": {
363
+ // Find the parameter node - it's usually the first identifier after 'catch'
364
+ for (const child of node.children) {
365
+ if (child.type === "identifier") {
366
+ symbols.push({
367
+ name: getText(child, code),
368
+ type: "variable",
369
+ file: filePath,
370
+ line: child.startPosition.row + 1,
371
+ column: child.startPosition.column,
372
+ isExported: false,
373
+ });
374
+ break; // Only one catch parameter
375
+ }
376
+ }
377
+ break;
378
+ }
379
+ // Named exports: export { a, b as c }
380
+ case "export_clause": {
381
+ for (const child of node.children) {
382
+ if (child.type === "export_specifier") {
383
+ const nameNode = child.childForFieldName("alias") || child.childForFieldName("name");
384
+ if (nameNode) {
385
+ const name = getText(nameNode, code);
386
+ symbols.push({
387
+ name,
388
+ type: "variable",
389
+ file: filePath,
390
+ line: node.startPosition.row + 1,
391
+ column: node.startPosition.column,
392
+ isExported: true,
393
+ });
394
+ }
395
+ }
396
+ }
397
+ break;
398
+ }
399
+ // Function parameters (arrow functions, methods, etc.)
400
+ // NOTE: Parameters are extracted by extractJSParams which is called from
401
+ // function_declaration, arrow_function, and method_definition cases.
402
+ // The function body will be processed by recursion into other children.
403
+ case "formal_parameters":
404
+ case "parameters":
405
+ case "required_parameter":
406
+ case "optional_parameter": {
407
+ // Fall through to recursion - function body will be processed
408
+ break;
409
+ }
410
+ }
411
+ for (const child of node.children) {
412
+ extractJSSymbols(child, code, filePath, symbols, currentClass, options);
413
+ }
414
+ }
415
+ // ============================================================================
416
+ // Parameter Extraction
417
+ // ============================================================================
418
+ /**
419
+ * Extract parameter names from a formal_parameters node.
420
+ * Returns the parameter names for the function's params list.
421
+ * For destructured parameters (object/array patterns), also registers the
422
+ * individual variable names as symbols for validation purposes.
423
+ */
424
+ export function extractJSParams(paramsNode, code, filePath = "", symbols, options = {}) {
425
+ if (!paramsNode)
426
+ return [];
427
+ const params = [];
428
+ for (const child of paramsNode.children) {
429
+ if (child.type === "identifier") {
430
+ const name = getText(child, code);
431
+ params.push(name);
432
+ // ALWAYS register simple parameter identifiers as local symbols.
433
+ // This prevents false positives where function parameters are flagged
434
+ // as "undefinedVariable" when used within the function body.
435
+ if (symbols) {
436
+ symbols.push({
437
+ name,
438
+ type: "variable",
439
+ file: filePath,
440
+ line: child.startPosition.row + 1,
441
+ column: child.startPosition.column,
442
+ });
443
+ }
444
+ }
445
+ else if (child.type === "required_parameter" ||
446
+ child.type === "optional_parameter" ||
447
+ child.type === "rest_pattern") {
448
+ // Logic for destructuring or simple names in typed parameters
449
+ // TypeScript: function foo({ x }: Type) or function foo(x: Type)
450
+ const nameNode = child.childForFieldName("pattern") ||
451
+ child.children.find((c) => c.type === "identifier" ||
452
+ c.type === "rest_pattern" ||
453
+ c.type === "object_pattern" ||
454
+ c.type === "array_pattern");
455
+ if (nameNode) {
456
+ if (nameNode.type === "object_pattern" || nameNode.type === "array_pattern") {
457
+ // For destructured params, register the individual names as symbols
458
+ // This is needed for validation to recognize them as defined variables
459
+ if (symbols) {
460
+ extractDestructuredNames(nameNode, code, filePath, symbols, nameNode.startPosition.row + 1);
461
+ }
462
+ // For param list, we use the raw text
463
+ params.push(getText(nameNode, code));
464
+ }
465
+ else if (nameNode.type === "rest_pattern") {
466
+ const id = nameNode.children.find((c) => c.type === "identifier");
467
+ if (id) {
468
+ const name = getText(id, code);
469
+ params.push(name);
470
+ // Rest parameters are registered as symbols
471
+ if (symbols) {
472
+ symbols.push({
473
+ name,
474
+ type: "variable",
475
+ file: filePath,
476
+ line: id.startPosition.row + 1,
477
+ column: id.startPosition.column,
478
+ });
479
+ }
480
+ }
481
+ }
482
+ else if (nameNode.type === "identifier") {
483
+ // Handle simple typed parameters: function foo(x: Type)
484
+ const name = getText(nameNode, code);
485
+ params.push(name);
486
+ // ALWAYS register parameter symbols to prevent false positives
487
+ // when validating local variable usage within the function scope
488
+ if (symbols) {
489
+ symbols.push({
490
+ name,
491
+ type: "variable",
492
+ file: filePath,
493
+ line: nameNode.startPosition.row + 1,
494
+ column: nameNode.startPosition.column,
495
+ });
496
+ }
497
+ }
498
+ }
499
+ }
500
+ else if (child.type === "object_pattern" || child.type === "array_pattern") {
501
+ // For destructured params at the top level, register the individual names
502
+ if (symbols) {
503
+ extractDestructuredNames(child, code, filePath, symbols, child.startPosition.row + 1);
504
+ }
505
+ params.push(getText(child, code));
506
+ }
507
+ }
508
+ return params;
509
+ }
510
+ /**
511
+ * Extract variable names from destructuring patterns
512
+ * Handles: const { a, b } = x and const [a, b] = x
513
+ */
514
+ export function extractDestructuredNames(node, code, filePath, symbols, line, depth = 0) {
515
+ if (!node || depth > 50)
516
+ return; // Recursion loop guard
517
+ switch (node.type) {
518
+ case "identifier":
519
+ case "shorthand_property_identifier_pattern": {
520
+ const name = getText(node, code);
521
+ symbols.push({
522
+ name,
523
+ type: "variable",
524
+ file: filePath,
525
+ line,
526
+ column: node.startPosition.column,
527
+ });
528
+ break;
529
+ }
530
+ case "pair_pattern": {
531
+ // Handle { oldName: newName } - extract newName
532
+ const valueNode = node.childForFieldName("value");
533
+ if (valueNode) {
534
+ extractDestructuredNames(valueNode, code, filePath, symbols, line, depth + 1);
535
+ }
536
+ break;
537
+ }
538
+ case "assignment_pattern": {
539
+ // Handle [a = 1] or {a = 1} - extract the left side
540
+ const leftNode = node.childForFieldName("left");
541
+ if (leftNode) {
542
+ extractDestructuredNames(leftNode, code, filePath, symbols, line, depth + 1);
543
+ }
544
+ break;
545
+ }
546
+ case "object_assignment_pattern": {
547
+ // Handle { a = 1 } in object destructuring (with default value)
548
+ for (const child of node.children) {
549
+ // Only extract the identifier being defined, not the default value
550
+ if (child.type === "shorthand_property_identifier_pattern" ||
551
+ child.type === "identifier") {
552
+ extractDestructuredNames(child, code, filePath, symbols, line, depth + 1);
553
+ }
554
+ }
555
+ break;
556
+ }
557
+ case "object_pattern":
558
+ case "array_pattern": {
559
+ for (const child of node.children) {
560
+ // Only recurse into relevant pattern parts (skip punctuation like {, }, [, ], commas)
561
+ if (child.type === "pair_pattern" ||
562
+ child.type === "shorthand_property_identifier_pattern" ||
563
+ child.type === "object_pattern" ||
564
+ child.type === "array_pattern" ||
565
+ child.type === "assignment_pattern" ||
566
+ child.type === "object_assignment_pattern" ||
567
+ child.type === "identifier" ||
568
+ child.type === "rest_pattern") {
569
+ extractDestructuredNames(child, code, filePath, symbols, line, depth + 1);
570
+ }
571
+ }
572
+ break;
573
+ }
574
+ case "rest_pattern": {
575
+ // Handle ...rest - find the identifier inside
576
+ for (const child of node.children) {
577
+ if (child.type === "identifier") {
578
+ extractDestructuredNames(child, code, filePath, symbols, line, depth + 1);
579
+ }
580
+ }
581
+ break;
582
+ }
583
+ }
584
+ }
585
+ // ============================================================================
586
+ // Local Definition Collection (for JS/TS)
587
+ // ============================================================================
588
+ /**
589
+ * Collect all locally-defined identifier names from JavaScript/TypeScript code.
590
+ * This includes: variable declarations (const/let/var), function parameters,
591
+ * for-of/for-in loop variables, catch clause variables, destructured names,
592
+ * function/class declaration names.
593
+ *
594
+ * Used by the validator to prevent false positives on local variable references
595
+ * and method calls (e.g., `for (const training of records) { training.status }`)
596
+ */
597
+ export function collectJSLocalDefinitions(node, code, definitions) {
598
+ if (!node)
599
+ return;
600
+ switch (node.type) {
601
+ case "variable_declarator": {
602
+ const nameNode = node.childForFieldName("name");
603
+ if (nameNode) {
604
+ if (nameNode.type === "identifier") {
605
+ definitions.add(getText(nameNode, code));
606
+ }
607
+ else if (nameNode.type === "object_pattern" || nameNode.type === "array_pattern") {
608
+ collectDestructuredDefinitions(nameNode, code, definitions);
609
+ }
610
+ }
611
+ break;
612
+ }
613
+ // For-in/for-of loop variable: for (const x of arr) / for (const x in obj)
614
+ case "for_in_statement": {
615
+ const leftNode = node.childForFieldName("left");
616
+ if (leftNode) {
617
+ if (leftNode.type === "identifier") {
618
+ definitions.add(getText(leftNode, code));
619
+ }
620
+ else if (leftNode.type === "object_pattern" || leftNode.type === "array_pattern") {
621
+ // Destructured loop variable: for (const [key, value] of entries)
622
+ collectDestructuredDefinitions(leftNode, code, definitions);
623
+ }
624
+ else {
625
+ // Could be lexical_declaration wrapping the variable
626
+ collectJSLocalDefinitions(leftNode, code, definitions);
627
+ }
628
+ }
629
+ break;
630
+ }
631
+ // Catch clause: catch (err) { ... }
632
+ case "catch_clause": {
633
+ const paramNode = node.childForFieldName("parameter");
634
+ if (paramNode) {
635
+ if (paramNode.type === "identifier") {
636
+ definitions.add(getText(paramNode, code));
637
+ }
638
+ else if (paramNode.type === "object_pattern" || paramNode.type === "array_pattern") {
639
+ collectDestructuredDefinitions(paramNode, code, definitions);
640
+ }
641
+ }
642
+ break;
643
+ }
644
+ // Function/class declaration names
645
+ case "function_declaration":
646
+ case "class_declaration": {
647
+ const nameNode = node.childForFieldName("name");
648
+ if (nameNode && nameNode.type === "identifier") {
649
+ definitions.add(getText(nameNode, code));
650
+ }
651
+ break;
652
+ }
653
+ // Function parameters
654
+ case "required_parameter":
655
+ case "optional_parameter": {
656
+ const nameNode = node.childForFieldName("pattern") || node.childForFieldName("name");
657
+ if (nameNode) {
658
+ if (nameNode.type === "identifier") {
659
+ definitions.add(getText(nameNode, code));
660
+ }
661
+ else if (nameNode.type === "object_pattern" || nameNode.type === "array_pattern") {
662
+ collectDestructuredDefinitions(nameNode, code, definitions);
663
+ }
664
+ }
665
+ break;
666
+ }
667
+ // Rest parameter: ...args
668
+ case "rest_pattern": {
669
+ for (const child of node.children) {
670
+ if (child && child.type === "identifier") {
671
+ definitions.add(getText(child, code));
672
+ }
673
+ }
674
+ break;
675
+ }
676
+ // Enum declaration
677
+ case "enum_declaration": {
678
+ const nameNode = node.childForFieldName("name");
679
+ if (nameNode && nameNode.type === "identifier") {
680
+ definitions.add(getText(nameNode, code));
681
+ }
682
+ break;
683
+ }
684
+ }
685
+ for (const child of node.children) {
686
+ if (child) {
687
+ collectJSLocalDefinitions(child, code, definitions);
688
+ }
689
+ }
690
+ }
691
+ /**
692
+ * Collect identifier names from destructuring patterns (object/array).
693
+ */
694
+ function collectDestructuredDefinitions(node, code, definitions) {
695
+ if (!node)
696
+ return;
697
+ for (const child of node.children) {
698
+ if (!child)
699
+ continue;
700
+ if (child.type === "identifier") {
701
+ definitions.add(getText(child, code));
702
+ }
703
+ else if (child.type === "shorthand_property_identifier_pattern") {
704
+ definitions.add(getText(child, code));
705
+ }
706
+ else if (child.type === "pair_pattern") {
707
+ const valueNode = child.childForFieldName("value");
708
+ if (valueNode) {
709
+ if (valueNode.type === "identifier") {
710
+ definitions.add(getText(valueNode, code));
711
+ }
712
+ else if (valueNode.type === "object_pattern" || valueNode.type === "array_pattern") {
713
+ collectDestructuredDefinitions(valueNode, code, definitions);
714
+ }
715
+ else if (valueNode.type === "assignment_pattern") {
716
+ const leftNode = valueNode.childForFieldName("left");
717
+ if (leftNode?.type === "identifier") {
718
+ definitions.add(getText(leftNode, code));
719
+ }
720
+ }
721
+ }
722
+ }
723
+ else if (child.type === "rest_pattern") {
724
+ for (const restChild of child.children) {
725
+ if (restChild?.type === "identifier") {
726
+ definitions.add(getText(restChild, code));
727
+ }
728
+ }
729
+ }
730
+ else if (child.type === "assignment_pattern") {
731
+ const leftNode = child.childForFieldName("left");
732
+ if (leftNode?.type === "identifier") {
733
+ definitions.add(getText(leftNode, code));
734
+ }
735
+ }
736
+ else if (child.type === "object_pattern" || child.type === "array_pattern") {
737
+ collectDestructuredDefinitions(child, code, definitions);
738
+ }
739
+ }
740
+ }
741
+ // ============================================================================
742
+ // Usage Extraction
743
+ // ============================================================================
744
+ /**
745
+ * Extract symbol usages from JavaScript/TypeScript AST
746
+ * Finds: function calls, method calls, instantiations (new expressions)
747
+ */
748
+ export function extractJSUsages(node, code, usages, externalSymbols) {
749
+ if (!node)
750
+ return;
751
+ switch (node.type) {
752
+ case "call_expression": {
753
+ const funcNode = node.childForFieldName("function");
754
+ const argsNode = node.childForFieldName("arguments");
755
+ const argCount = countArgs(argsNode);
756
+ if (funcNode) {
757
+ if (funcNode.type === "identifier") {
758
+ const name = getText(funcNode, code);
759
+ // Don't skip imported symbols or built-ins here; we need to track them
760
+ // for unused import detection and proper validation.
761
+ usages.push({
762
+ name,
763
+ type: "call",
764
+ line: node.startPosition.row + 1,
765
+ column: node.startPosition.column,
766
+ code: getLineText(code, node.startPosition.row),
767
+ argCount,
768
+ });
769
+ }
770
+ else if (funcNode.type === "member_expression") {
771
+ const objNode = funcNode.childForFieldName("object");
772
+ const propNode = funcNode.childForFieldName("property");
773
+ if (objNode && propNode) {
774
+ const obj = getText(objNode, code);
775
+ const method = getText(propNode, code);
776
+ // Skip built-in objects (console, window, etc.) but NOT imported symbols
777
+ // We need to track method calls on imported symbols for validation
778
+ // e.g., screen from @testing-library/react shadows the browser's window.screen
779
+ //
780
+ // EXCEPTION: If the builtin is cast to `any` (e.g., `(window as any).foo()`),
781
+ // do NOT skip — the `as any` cast is a deliberate type-safety bypass,
782
+ // which is a strong signal of a stealth hallucination.
783
+ const rawObjText = getText(objNode, code);
784
+ const isCastToAny = rawObjText.includes("as any") || rawObjText.includes("as unknown");
785
+ if (isJSBuiltin(obj) && !externalSymbols.has(obj) && !isCastToAny) {
786
+ // Skip method calls on built-in objects (Array.map, String.split, etc.)
787
+ // These are standard library methods we don't need to validate
788
+ }
789
+ else {
790
+ // Extract the root object from complex expressions like:
791
+ // - (err as Type).property -> err
792
+ // - arr[index].property -> arr[index]
793
+ // - fn().property -> fn()
794
+ const rootObj = getRootObject(objNode, code);
795
+ usages.push({
796
+ name: method,
797
+ type: "methodCall",
798
+ object: rootObj,
799
+ line: node.startPosition.row + 1,
800
+ column: node.startPosition.column,
801
+ code: getLineText(code, node.startPosition.row),
802
+ argCount,
803
+ });
804
+ }
805
+ }
806
+ }
807
+ }
808
+ break;
809
+ }
810
+ case "new_expression": {
811
+ const constructorNode = node.childForFieldName("constructor");
812
+ if (constructorNode?.type === "identifier") {
813
+ const name = getText(constructorNode, code);
814
+ usages.push({
815
+ name,
816
+ type: "instantiation",
817
+ line: node.startPosition.row + 1,
818
+ column: node.startPosition.column,
819
+ code: getLineText(code, node.startPosition.row),
820
+ });
821
+ }
822
+ // Don't recurse into children to avoid extracting the constructor name again as a reference
823
+ return;
824
+ }
825
+ case "identifier":
826
+ case "jsx_identifier":
827
+ case "property_identifier":
828
+ case "type_identifier": {
829
+ const name = getText(node, code);
830
+ // Skip built-in objects that are commonly used as property access base
831
+ // (e.g., console in console.log, window in window.location)
832
+ // These are handled specially and shouldn't be flagged as undefined references
833
+ if (isJSBuiltin(name)) {
834
+ // But only skip if it's being used as an object base (member_expression.object)
835
+ const parent = node.parent;
836
+ if (parent?.type === "member_expression" &&
837
+ parent.childForFieldName("object")?.id === node.id) {
838
+ break; // Skip this identifier
839
+ }
840
+ }
841
+ // Semantic Bridge: Detect potential API calls (fetch('/api/...'))
842
+ if (name === "fetch" ||
843
+ name === "axios" ||
844
+ name === "get" ||
845
+ name === "post") {
846
+ const parentCall = node.parent?.type === "call_expression" ? node.parent
847
+ : node.parent?.parent?.type === "call_expression" ? node.parent.parent
848
+ : null;
849
+ if (parentCall) {
850
+ const argsNode = parentCall.childForFieldName("arguments");
851
+ if (argsNode && argsNode.children.length > 0) {
852
+ const firstArg = argsNode.children.find((c) => c.type === "string" || c.type === "template_string");
853
+ if (firstArg) {
854
+ const url = getText(firstArg, code).replace(/['"`]/g, "");
855
+ if (url.startsWith("/")) {
856
+ const exists = usages.some((u) => u.name === url && u.line === node.startPosition.row + 1);
857
+ // Record API endpoint usage for the Semantic Bridge / symbol graph.
858
+ // IMPORTANT: This is NOT validated as a variable (validateSymbols ignores apiCall).
859
+ if (!exists) {
860
+ usages.push({
861
+ name: url,
862
+ type: "apiCall",
863
+ line: node.startPosition.row + 1,
864
+ column: node.startPosition.column,
865
+ code: getLineText(code, node.startPosition.row),
866
+ });
867
+ }
868
+ }
869
+ }
870
+ }
871
+ }
872
+ }
873
+ // Only skip standard lowercase JSX tags (div, span, etc.)
874
+ // tree-sitter-typescript/tsx uses plain "identifier" (not "jsx_identifier") for tag names
875
+ // inside jsx_opening_element and jsx_closing_element, so check both node types
876
+ if (node.type === "jsx_identifier" && /^[a-z]/.test(name))
877
+ break;
878
+ if (node.type === "identifier" && /^[a-z]/.test(name)) {
879
+ const p = node.parent;
880
+ if (p?.type === "jsx_opening_element" || p?.type === "jsx_closing_element" || p?.type === "jsx_self_closing_element") {
881
+ break;
882
+ }
883
+ }
884
+ // Filter out non-usage positions:
885
+ const parent = node.parent;
886
+ if (!parent)
887
+ break;
888
+ // 0. Skip if inside a string literal or JSX text content
889
+ // This prevents false positives like "Notes" in <h3>Comments & Notes</h3>
890
+ // Also skip ERROR nodes inside JSX (parser errors from invalid JSX characters like &)
891
+ let ancestor = parent;
892
+ while (ancestor) {
893
+ // template_substitution (${...}) is real code inside a template string
894
+ // Don't skip identifiers inside substitution expressions
895
+ if (ancestor.type === "template_substitution")
896
+ break;
897
+ if (ancestor.type === "string" ||
898
+ ancestor.type === "template_string" ||
899
+ ancestor.type === "jsx_text" ||
900
+ ancestor.type === "string_fragment") {
901
+ return; // Don't process this identifier at all
902
+ }
903
+ // Skip identifiers inside ERROR nodes that are within JSX elements
904
+ // This happens when JSX contains invalid characters like unescaped &
905
+ if (ancestor.type === "ERROR") {
906
+ // Check if this ERROR is within a JSX element
907
+ let jsxAncestor = ancestor.parent;
908
+ while (jsxAncestor) {
909
+ if (jsxAncestor.type === "jsx_element" ||
910
+ jsxAncestor.type === "jsx_self_closing_element" ||
911
+ jsxAncestor.type === "jsx_expression" ||
912
+ jsxAncestor.type === "jsx_fragment") {
913
+ return; // This is an identifier in invalid JSX text, skip it
914
+ }
915
+ jsxAncestor = jsxAncestor.parent;
916
+ }
917
+ }
918
+ ancestor = ancestor.parent;
919
+ }
920
+ // 1. Handle member expression property access (x.NAME)
921
+ // This is used for method references like: { queryFn: api.getMethod }
922
+ if ((parent.type === "member_expression" ||
923
+ parent.type === "jsx_member_expression") &&
924
+ parent.childForFieldName("property")?.id === node.id) {
925
+ const objNode = parent.childForFieldName("object");
926
+ // Use getRootObject to handle complex expressions like (err as Type).property
927
+ const objName = objNode ? getRootObject(objNode, code) : "";
928
+ // Special case: member_expression inside JSX-misparsed context
929
+ // This happens with: <button aria-label="Close" role="button">
930
+ // where 'role' becomes property_identifier in member_expression inside assignment_expression
931
+ // Check if we're in a JSX-like context
932
+ let isInJsxContext = false;
933
+ let ancestor = parent.parent;
934
+ while (ancestor) {
935
+ if (ancestor.type === "ERROR") {
936
+ // Check if this ERROR looks like it came from JSX parsing
937
+ isInJsxContext = true;
938
+ break;
939
+ }
940
+ // Check for JSX-misparsed patterns
941
+ // Pattern: binary_expression containing type_assertion followed by - followed by assignment_expression
942
+ // This is the structure from: <button aria-label="..." role="...">
943
+ if (ancestor.type === "binary_expression") {
944
+ const grandparent = ancestor.parent;
945
+ if (grandparent) {
946
+ const ancestorIndex = grandparent.children.indexOf(ancestor);
947
+ // Check if binary_expression is at the right position
948
+ if (ancestorIndex >= 0) {
949
+ // Check if binary_expression contains: type_assertion, -, assignment_expression
950
+ const typeAssertChild = ancestor.children.find(c => c.type === "type_assertion");
951
+ const dashChild = ancestor.children.find(c => c.type === "-" || c.text === "-");
952
+ const assignChild = ancestor.children.find(c => c.type === "assignment_expression");
953
+ if (typeAssertChild && dashChild && assignChild) {
954
+ isInJsxContext = true;
955
+ break;
956
+ }
957
+ }
958
+ }
959
+ }
960
+ if (ancestor.type === "jsx_expression" || ancestor.type === "jsx_element") {
961
+ // Real JSX context - don't skip here, let the jsx_attribute check handle it
962
+ isInJsxContext = false;
963
+ break;
964
+ }
965
+ ancestor = ancestor.parent;
966
+ }
967
+ if (isInJsxContext) {
968
+ // Skip this property_identifier - it's likely a JSX attribute name
969
+ // that got misparsed as member_expression due to parser confusion
970
+ break;
971
+ }
972
+ // Check if this member_expression is the function of a call_expression.
973
+ // If so, it's already handled by the call_expression case above (line ~892).
974
+ // If NOT, it's just property access (e.g., dashboard.projects, arr[0].name)
975
+ // which should NOT be flagged as a method call.
976
+ const memberParent = parent.parent;
977
+ const isCallTarget = memberParent?.type === "call_expression" &&
978
+ memberParent.childForFieldName("function")?.id === parent.id;
979
+ if (isCallTarget) {
980
+ // Already handled by call_expression case — skip to avoid double-reporting
981
+ break;
982
+ }
983
+ // This is pure property access (not a call), e.g.:
984
+ // - dashboard.projects (API response property)
985
+ // - updated[idx].deliverables (array element property)
986
+ // - integrations[0].provider (indexed property)
987
+ // Only track as methodCall if the object is a known project symbol
988
+ // (e.g., { queryFn: paymentsApi.getPaymentMethods } where paymentsApi is imported).
989
+ // Skip property access on local/dynamic variables to avoid FPs on API responses.
990
+ if (objName === "state") {
991
+ // We ALLOW this to be processed as a usage to catch hallucinations in stores
992
+ }
993
+ else if (!isJSBuiltin(objName) && externalSymbols.has(objName)) {
994
+ // Only track property access on imported/external symbols where we can validate
995
+ usages.push({
996
+ name: name,
997
+ type: "methodCall",
998
+ object: objName,
999
+ line: node.startPosition.row + 1,
1000
+ column: node.startPosition.column,
1001
+ code: getLineText(code, node.startPosition.row),
1002
+ });
1003
+ }
1004
+ break;
1005
+ }
1006
+ // 2. Skip if it's a field name in an object literal ({ NAME: val })
1007
+ if (parent.type === "pair" && parent.childForFieldName("key")?.id === node.id)
1008
+ break;
1009
+ // 2a. Skip if it's a property name in an interface or type literal
1010
+ // e.g., interface Foo { bar: string } or type Foo = { bar: string }
1011
+ // The property name is a definition, not a usage of an external variable
1012
+ if (parent.type === "property_signature" && parent.childForFieldName("name")?.id === node.id)
1013
+ break;
1014
+ // 2b. Skip if it's a class field declaration (public/private/protected property)
1015
+ // e.g., class Foo { private bar: string }
1016
+ // TypeScript uses public_field_definition, private_field_definition, etc.
1017
+ // Note: property_identifier is used as the name node for class fields
1018
+ if ((parent.type === "property_definition" ||
1019
+ parent.type === "public_field_definition" ||
1020
+ parent.type === "private_field_definition" ||
1021
+ parent.type === "protected_field_definition" ||
1022
+ parent.type === "field_definition") &&
1023
+ parent.childForFieldName("name")?.id === node.id)
1024
+ break;
1025
+ // 2c. Skip if it's a JSX attribute name (e.g., <div className="foo" />)
1026
+ // These are HTML/SVG/React props, not variable references
1027
+ // JSX attributes have the name as the first child (property_identifier or jsx_identifier)
1028
+ if (parent.type === "jsx_attribute" && parent.children[0]?.id === node.id)
1029
+ break;
1030
+ // 2d. Skip property_identifier when it's used as a JSX attribute name
1031
+ // This handles cases where JSX parsing produces different AST structures
1032
+ // e.g., <img src={url} /> where 'src' might be parsed as property_identifier
1033
+ // or <button role="..."> where 'role' is parsed as property_identifier in member_expression
1034
+ if (node.type === "property_identifier") {
1035
+ // Check if this property_identifier is inside a JSX attribute context
1036
+ let ancestor = parent;
1037
+ while (ancestor) {
1038
+ if (ancestor.type === "jsx_attribute") {
1039
+ // This property_identifier is the attribute name
1040
+ if (ancestor.children[0]?.id === node.id ||
1041
+ (ancestor.childForFieldName("name")?.id === node.id)) {
1042
+ return; // Skip - this is a JSX attribute name, not a variable reference
1043
+ }
1044
+ }
1045
+ // Also check for ERROR nodes that might contain JSX attributes
1046
+ if (ancestor.type === "ERROR") {
1047
+ // Check if this property_identifier is followed by = in an ERROR node
1048
+ // by looking at the parent's siblings
1049
+ const grandparent = ancestor.parent;
1050
+ if (grandparent) {
1051
+ const ancestorIndex = grandparent.children.indexOf(ancestor);
1052
+ if (ancestorIndex >= 0) {
1053
+ const nextAtAncestorLevel = grandparent.children[ancestorIndex + 1];
1054
+ if (nextAtAncestorLevel && nextAtAncestorLevel.type === "=") {
1055
+ return; // This looks like a JSX attribute name in an ERROR node
1056
+ }
1057
+ }
1058
+ }
1059
+ // Special case: property_identifier inside member_expression inside ERROR
1060
+ // This happens with: <button aria-label="Close" role="button">
1061
+ // where 'role' becomes property_identifier in member_expression
1062
+ if (parent.type === "member_expression") {
1063
+ // Check if the member_expression is followed by = in the ERROR node
1064
+ const memberExprIndex = ancestor.children.indexOf(parent);
1065
+ if (memberExprIndex >= 0) {
1066
+ const nextAfterMember = ancestor.children[memberExprIndex + 1];
1067
+ if (nextAfterMember && nextAfterMember.type === "=") {
1068
+ return; // This looks like a JSX attribute name (role=)
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+ ancestor = ancestor.parent;
1074
+ }
1075
+ // Additional check: property_identifier in member_expression that's part of
1076
+ // a JSX-like pattern even without ERROR nodes
1077
+ // This handles: <button aria-label="Close menu" role="button">
1078
+ // where 'role' is property_identifier in member_expression in assignment_expression
1079
+ if (parent.type === "member_expression") {
1080
+ // Check if this member_expression has a specific pattern suggesting JSX misparsing
1081
+ // Pattern: string + ?. + property_identifier followed by =
1082
+ // This is "value"?.property = something
1083
+ const objNode = parent.childForFieldName("object");
1084
+ const propNode = parent.childForFieldName("property");
1085
+ if (objNode?.type === "string" && propNode === node) {
1086
+ // The member_expression looks like "string"?.property
1087
+ // Check if parent of member_expression is followed by =
1088
+ const grandparent = parent.parent;
1089
+ if (grandparent) {
1090
+ const parentIndex = grandparent.children.indexOf(parent);
1091
+ if (parentIndex >= 0) {
1092
+ const nextAfterParent = grandparent.children[parentIndex + 1];
1093
+ if (nextAfterParent && nextAfterParent.type === "=") {
1094
+ // This looks like "value"?.attr = value pattern from JSX parsing
1095
+ return; // Skip - this is a JSX attribute name
1096
+ }
1097
+ }
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ // 2e. Skip identifiers/type_identifiers that are actually JSX attribute names
1103
+ // This handles cases where JSX parsing produces type-related AST nodes
1104
+ // e.g., <img src={url} /> where 'src' might be parsed as type_identifier
1105
+ // or <Tag attr={val} /> where 'attr' is an identifier inside an ERROR node
1106
+ if (node.type === "identifier" || node.type === "type_identifier") {
1107
+ // First check: is this identifier directly followed by = in its parent?
1108
+ // This handles: <Tag attr={val} /> where attr is an identifier child of ERROR
1109
+ const nodeIndexInParent = parent.children.indexOf(node);
1110
+ if (nodeIndexInParent >= 0) {
1111
+ const nextSibling = parent.children[nodeIndexInParent + 1];
1112
+ if (nextSibling && (nextSibling.type === "=" ||
1113
+ nextSibling.text === "=")) {
1114
+ return; // Skip - this is attr= pattern (JSX attribute name)
1115
+ }
1116
+ // Handle hyphenated attributes like aria-label, data-testid
1117
+ // Pattern: attr-name= (identifier, -, identifier, =)
1118
+ if (nextSibling && (nextSibling.type === "-" ||
1119
+ nextSibling.text === "-")) {
1120
+ const nextNextSibling = parent.children[nodeIndexInParent + 2];
1121
+ const nextNextNextSibling = parent.children[nodeIndexInParent + 3];
1122
+ if (nextNextSibling && nextNextSibling.type === "identifier") {
1123
+ // Check if after the second part there's an =
1124
+ if (nextNextNextSibling && (nextNextNextSibling.type === "=" ||
1125
+ nextNextNextSibling.text === "=")) {
1126
+ return; // Skip - this is aria-label= pattern (JSX attribute name)
1127
+ }
1128
+ }
1129
+ }
1130
+ // Handle identifiers in type_assertion that are actually JSX attribute names
1131
+ // Pattern: <button aria-label="..."> where 'aria' is parsed as type_assertion's expression
1132
+ // The structure is: type_assertion (type_arguments + identifier) - assignment_expression
1133
+ if (parent.type === "type_assertion") {
1134
+ // Check if type_assertion is followed by - (aria- pattern)
1135
+ const parentIndex = parent.parent?.children.indexOf(parent);
1136
+ if (parentIndex !== undefined && parentIndex >= 0 && parent.parent) {
1137
+ const nextAfterParent = parent.parent.children[parentIndex + 1];
1138
+ if (nextAfterParent && (nextAfterParent.type === "-" ||
1139
+ nextAfterParent.text === "-")) {
1140
+ return; // Skip - this looks like aria-label pattern
1141
+ }
1142
+ }
1143
+ }
1144
+ }
1145
+ // Check if this is inside a JSX-like context (ERROR node or type_parameters)
1146
+ // where the identifier is followed by = or has JSX-like structure
1147
+ let ancestor = parent;
1148
+ while (ancestor) {
1149
+ // Check if we're inside what looks like a JSX element
1150
+ if (ancestor.type === "ERROR" || ancestor.type === "type_parameters" ||
1151
+ ancestor.type === "type_arguments") {
1152
+ // For type_identifiers inside type_parameter (e.g., src={val} parsed as type)
1153
+ // Structure: type_parameter -> type_identifier (attr name) -> default_type (=value)
1154
+ if (parent.type === "type_parameter") {
1155
+ // Check if there's a default_type (the =value part) after this type_identifier
1156
+ const nodeIndex = parent.children.indexOf(node);
1157
+ if (nodeIndex >= 0) {
1158
+ const nextSibling = parent.children[nodeIndex + 1];
1159
+ if (nextSibling && nextSibling.type === "default_type") {
1160
+ return; // Skip - this looks like attr={value} pattern
1161
+ }
1162
+ }
1163
+ }
1164
+ // Handle identifiers inside ERROR nodes that are inside type_parameter
1165
+ // This happens with: <Tag type="file" /> where 'type' is parsed as type_identifier
1166
+ // Structure: type_parameter -> ERROR (only child is identifier "type") -> default_type
1167
+ if (parent.type === "ERROR") {
1168
+ const grandparent = parent.parent;
1169
+ // Case 1: ERROR is inside type_parameter, and ERROR only contains this identifier
1170
+ // This is the "type" in <input type="file" ...>
1171
+ if (grandparent && grandparent.type === "type_parameter") {
1172
+ // Check if ERROR only has one child (this identifier)
1173
+ if (parent.children.length === 1 && parent.children[0]?.id === node.id) {
1174
+ // Check if the ERROR is followed by default_type in type_parameter
1175
+ const errorIndex = grandparent.children.indexOf(parent);
1176
+ if (errorIndex >= 0) {
1177
+ const nextAfterError = grandparent.children[errorIndex + 1];
1178
+ if (nextAfterError && nextAfterError.type === "default_type") {
1179
+ return; // Skip - this is a JSX attribute name
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+ // Case 2: ERROR is inside default_type, containing multiple identifiers
1185
+ // This is "multiple" or "accept" in <input type="file" multiple accept="..." />
1186
+ const greatGrandparent = grandparent?.parent;
1187
+ if (grandparent?.type === "default_type" && greatGrandparent?.type === "type_parameter") {
1188
+ // This ERROR contains multiple identifiers and/or literal_types
1189
+ // Check if this identifier follows a literal_type (boolean attr after value)
1190
+ const nodeIndex = parent.children.indexOf(node);
1191
+ const prevSibling = nodeIndex > 0 ? parent.children[nodeIndex - 1] : null;
1192
+ if (prevSibling && (prevSibling.type === "literal_type" ||
1193
+ prevSibling.type === "identifier")) {
1194
+ return; // Skip - this looks like a boolean JSX attribute after a value
1195
+ }
1196
+ }
1197
+ }
1198
+ }
1199
+ // Check if we're inside a jsx_expression - this is the {value} part, not the attribute name
1200
+ if (ancestor.type === "jsx_expression") {
1201
+ break; // Don't skip - identifiers inside { } are actual variable references
1202
+ }
1203
+ ancestor = ancestor.parent;
1204
+ }
1205
+ }
1206
+ // 2f. Skip identifiers inside nested_identifier (type-level namespace access)
1207
+ // e.g., Express.Multer.File[] — Express and Multer are namespace types, not variable references
1208
+ if (parent.type === "nested_identifier" || parent.type === "nested_type_identifier")
1209
+ break;
1210
+ // 3. Skip if it's a function/class/variable declaration name
1211
+ if ((parent.type === "function_declaration" ||
1212
+ parent.type === "class_declaration" ||
1213
+ parent.type === "variable_declarator" ||
1214
+ parent.type === "method_definition" ||
1215
+ parent.type === "interface_declaration" ||
1216
+ parent.type === "type_alias_declaration") &&
1217
+ parent.childForFieldName("name")?.id === node.id)
1218
+ break;
1219
+ // 3a. Skip if it's the name of a function expression
1220
+ // e.g., return function ProtectedComponent(props: P) { ... }
1221
+ // The 'ProtectedComponent' here is a local name for the function expression,
1222
+ // NOT a reference to an external variable
1223
+ // Note: Tree-sitter uses "function_expression" for function expressions, not "function"
1224
+ if ((parent.type === "function" || parent.type === "function_expression") &&
1225
+ parent.childForFieldName("name")?.id === node.id) {
1226
+ break;
1227
+ }
1228
+ // 3b. Skip TypeScript generic type parameter declarations
1229
+ // e.g., <T> in function foo<T>(...) — T is declared here, not a reference
1230
+ if (parent.type === "type_parameter" || parent.type === "type_parameters")
1231
+ break;
1232
+ // 3c. Skip generic type parameter USAGES (T, P, K, V, etc.) in type positions
1233
+ // These are single-letter uppercase identifiers used in type annotations/arguments
1234
+ // that were declared as generic params, not imported symbols
1235
+ if (node.type === "type_identifier" && /^[A-Z]$/.test(name))
1236
+ break;
1237
+ // 3d. Skip type_identifier that is the property part of a qualified type name
1238
+ // e.g., ErrorInfo in React.ErrorInfo — this is a namespace property, not a standalone reference
1239
+ if (node.type === "type_identifier" && parent.type === "nested_type_identifier")
1240
+ break;
1241
+ // 4. Skip if it's a formal parameter (including rest parameters)
1242
+ if (parent.type === "formal_parameters" ||
1243
+ parent.type === "required_parameter" ||
1244
+ parent.type === "optional_parameter" ||
1245
+ parent.type === "rest_pattern")
1246
+ break;
1247
+ // 5. Skip if it's the property in shorthand ({ name }) - this IS a usage of the outer 'name'
1248
+ // but Tree-sitter treats it as a 'shorthand_property_identifier'.
1249
+ if (parent.type === "shorthand_property_identifier") {
1250
+ // This IS a usage.
1251
+ }
1252
+ // 6. Skip if it's part of an import statement (handled separately)
1253
+ // Note: export_specifier is NOT skipped — re-exports like `export type { Project }`
1254
+ // should count as usages so the original import isn't flagged as unused
1255
+ if (parent.type === "import_specifier")
1256
+ break;
1257
+ // 7. If we reach here, it's likely a reference (usage as a value)
1258
+ // Check if it was already added as a call or instantiation in the same line/col
1259
+ const exists = usages.some((u) => u.line === node.startPosition.row + 1 &&
1260
+ u.column === node.startPosition.column);
1261
+ if (!exists) {
1262
+ usages.push({
1263
+ name,
1264
+ type: "reference",
1265
+ line: node.startPosition.row + 1,
1266
+ column: node.startPosition.column,
1267
+ code: getLineText(code, node.startPosition.row),
1268
+ });
1269
+ }
1270
+ break;
1271
+ }
1272
+ }
1273
+ for (const child of node.children) {
1274
+ if (child) {
1275
+ extractJSUsages(child, code, usages, externalSymbols);
1276
+ }
1277
+ }
1278
+ }
1279
+ // ============================================================================
1280
+ // Import Extraction
1281
+ // ============================================================================
1282
+ /**
1283
+ * Extract import statements from JavaScript/TypeScript AST
1284
+ * Handles: ES6 imports, dynamic imports (await import(...))
1285
+ */
1286
+ export function extractJSImports(node, code, imports) {
1287
+ if (!node)
1288
+ return;
1289
+ if (node.type === "import_statement") {
1290
+ const sourceNode = node.childForFieldName("source");
1291
+ if (sourceNode) {
1292
+ const module = getText(sourceNode, code).replace(/['"]/g, "");
1293
+ const names = [];
1294
+ let isTypeOnly = false;
1295
+ for (const child of node.children) {
1296
+ if (!child)
1297
+ continue;
1298
+ // Detect "import type" - TypeScript specific
1299
+ if (child.type === "type") {
1300
+ isTypeOnly = true;
1301
+ }
1302
+ if (child.type === "import_clause") {
1303
+ for (const clauseChild of child.children) {
1304
+ if (!clauseChild)
1305
+ continue;
1306
+ if (clauseChild.type === "identifier") {
1307
+ // Default import
1308
+ const name = getText(clauseChild, code);
1309
+ names.push({ imported: "default", local: name });
1310
+ }
1311
+ else if (clauseChild.type === "named_imports") {
1312
+ for (const specifier of clauseChild.children) {
1313
+ if (!specifier)
1314
+ continue;
1315
+ if (specifier.type === "import_specifier") {
1316
+ const nameNode = specifier.childForFieldName("name");
1317
+ const aliasNode = specifier.childForFieldName("alias");
1318
+ if (nameNode) {
1319
+ const imported = getText(nameNode, code);
1320
+ const local = aliasNode ? getText(aliasNode, code) : imported;
1321
+ names.push({ imported, local });
1322
+ }
1323
+ }
1324
+ }
1325
+ }
1326
+ else if (clauseChild.type === "namespace_import") {
1327
+ const nameNode = clauseChild.children.find((c) => c && c.type === "identifier");
1328
+ if (nameNode) {
1329
+ const name = getText(nameNode, code);
1330
+ names.push({ imported: "*", local: name });
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ }
1336
+ const isExternal = !module.startsWith(".") &&
1337
+ !module.startsWith("@/") &&
1338
+ !module.startsWith("~/");
1339
+ imports.push({
1340
+ module,
1341
+ names,
1342
+ isExternal,
1343
+ line: node.startPosition.row + 1,
1344
+ isTypeOnly,
1345
+ });
1346
+ }
1347
+ }
1348
+ // Handle dynamic imports: await import('...')
1349
+ // These appear as call_expression with "import" as the function
1350
+ // Also handle CommonJS requires: require('...')
1351
+ if (node.type === "call_expression") {
1352
+ const funcNode = node.childForFieldName("function");
1353
+ const funcName = funcNode ? getText(funcNode, code) : "";
1354
+ if (funcName === "import") {
1355
+ const argsNode = node.childForFieldName("arguments");
1356
+ if (argsNode) {
1357
+ // Find the string argument
1358
+ for (const arg of argsNode.children) {
1359
+ if (arg &&
1360
+ (arg.type === "string" || arg.type === "template_string")) {
1361
+ const module = getText(arg, code).replace(/['"`]/g, "");
1362
+ if (module) {
1363
+ // For dynamic imports, we mark all possible named imports
1364
+ // by looking at the destructuring pattern if available
1365
+ const names = [];
1366
+ // Check if this is part of a destructuring assignment
1367
+ // e.g., const { foo, bar } = await import('...')
1368
+ const parent = node.parent;
1369
+ if (parent?.type === "await_expression") {
1370
+ const grandparent = parent.parent;
1371
+ if (grandparent?.type === "variable_declarator") {
1372
+ const nameNode = grandparent.childForFieldName("name");
1373
+ if (nameNode?.type === "object_pattern") {
1374
+ // Extract destructured names
1375
+ for (const prop of nameNode.children) {
1376
+ if (prop?.type === "shorthand_property_identifier_pattern") {
1377
+ const name = getText(prop, code);
1378
+ names.push({ imported: name, local: name });
1379
+ }
1380
+ else if (prop?.type === "pair_pattern") {
1381
+ const keyNode = prop.childForFieldName("key");
1382
+ const valueNode = prop.childForFieldName("value");
1383
+ if (keyNode && valueNode) {
1384
+ names.push({
1385
+ imported: getText(keyNode, code),
1386
+ local: getText(valueNode, code),
1387
+ });
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+ }
1393
+ }
1394
+ // If no destructuring found, mark as wildcard import
1395
+ if (names.length === 0) {
1396
+ names.push({ imported: "*", local: "*" });
1397
+ }
1398
+ const isExternal = !module.startsWith(".") &&
1399
+ !module.startsWith("@/") &&
1400
+ !module.startsWith("~/");
1401
+ imports.push({
1402
+ module,
1403
+ names,
1404
+ isExternal,
1405
+ line: node.startPosition.row + 1,
1406
+ });
1407
+ }
1408
+ }
1409
+ }
1410
+ }
1411
+ }
1412
+ // CommonJS require('...')
1413
+ if (funcNode?.type === "identifier" && funcName === "require") {
1414
+ const argsNode = node.childForFieldName("arguments");
1415
+ if (argsNode) {
1416
+ const firstArg = argsNode.children.find((c) => c && (c.type === "string" || c.type === "template_string"));
1417
+ if (firstArg) {
1418
+ // Skip non-static template strings (require(`./${x}`))
1419
+ if (firstArg.type === "template_string" &&
1420
+ firstArg.children.some((c) => c?.type === "template_substitution")) {
1421
+ // ignore
1422
+ }
1423
+ else {
1424
+ const module = getText(firstArg, code).replace(/['"`]/g, "");
1425
+ if (module) {
1426
+ const line = node.startPosition.row + 1;
1427
+ const exists = imports.some((i) => i.module === module && i.line === line);
1428
+ if (!exists) {
1429
+ const names = [];
1430
+ // Infer imported names from the assignment pattern:
1431
+ // const x = require('mod')
1432
+ // const { a, b: c } = require('mod')
1433
+ const parent = node.parent;
1434
+ if (parent?.type === "variable_declarator") {
1435
+ const nameNode = parent.childForFieldName("name");
1436
+ if (nameNode?.type === "identifier") {
1437
+ names.push({ imported: "default", local: getText(nameNode, code) });
1438
+ }
1439
+ else if (nameNode?.type === "object_pattern") {
1440
+ for (const prop of nameNode.children) {
1441
+ if (prop?.type === "shorthand_property_identifier_pattern") {
1442
+ const name = getText(prop, code);
1443
+ names.push({ imported: name, local: name });
1444
+ }
1445
+ else if (prop?.type === "pair_pattern") {
1446
+ const keyNode = prop.childForFieldName("key");
1447
+ const valueNode = prop.childForFieldName("value");
1448
+ if (keyNode && valueNode) {
1449
+ names.push({
1450
+ imported: getText(keyNode, code),
1451
+ local: getText(valueNode, code),
1452
+ });
1453
+ }
1454
+ }
1455
+ }
1456
+ }
1457
+ }
1458
+ if (names.length === 0) {
1459
+ names.push({ imported: "*", local: "*" });
1460
+ }
1461
+ const isExternal = !module.startsWith(".") &&
1462
+ !module.startsWith("@/") &&
1463
+ !module.startsWith("~/");
1464
+ imports.push({
1465
+ module,
1466
+ names,
1467
+ isExternal,
1468
+ line,
1469
+ });
1470
+ }
1471
+ }
1472
+ }
1473
+ }
1474
+ }
1475
+ }
1476
+ }
1477
+ for (const child of node.children) {
1478
+ if (child) {
1479
+ extractJSImports(child, code, imports);
1480
+ }
1481
+ }
1482
+ }
1483
+ // ============================================================================
1484
+ // Type Reference Extraction
1485
+ // ============================================================================
1486
+ /**
1487
+ * Extract type references from JavaScript/TypeScript AST
1488
+ * Finds where types/interfaces are USED (not defined), including:
1489
+ * - Type annotations: `param: TypeName`
1490
+ * - Generic parameters: `Array<TypeName>`, `base.extend<TypeName>`
1491
+ * - Return types: `): TypeName`
1492
+ * - Extends/implements: `extends TypeName`, `implements TypeName`
1493
+ * - Property types in interfaces/types
1494
+ */
1495
+ export function extractJSTypeReferences(node, code, references) {
1496
+ if (!node)
1497
+ return;
1498
+ switch (node.type) {
1499
+ // Type annotations: `param: TypeName` or `const x: TypeName`
1500
+ case "type_annotation": {
1501
+ extractTypeNamesFromNode(node, code, references, "typeAnnotation");
1502
+ break;
1503
+ }
1504
+ // Generic type arguments: `Array<TypeName>`, `Promise<TypeName>`, `base.extend<TypeName>`
1505
+ case "type_arguments": {
1506
+ extractTypeNamesFromNode(node, code, references, "genericParam");
1507
+ break;
1508
+ }
1509
+ // Return type: `function(): TypeName`
1510
+ case "return_type": {
1511
+ extractTypeNamesFromNode(node, code, references, "returnType");
1512
+ break;
1513
+ }
1514
+ // Extends clause: `class X extends Y` or `interface X extends Y`
1515
+ case "extends_clause":
1516
+ case "extends_type_clause": {
1517
+ extractTypeNamesFromNode(node, code, references, "extends");
1518
+ break;
1519
+ }
1520
+ // Implements clause: `class X implements Y`
1521
+ case "implements_clause": {
1522
+ extractTypeNamesFromNode(node, code, references, "implements");
1523
+ break;
1524
+ }
1525
+ // Property signature in interface: `prop: TypeName`
1526
+ case "property_signature": {
1527
+ const typeNode = node.childForFieldName("type");
1528
+ if (typeNode) {
1529
+ extractTypeNamesFromNode(typeNode, code, references, "propertyType");
1530
+ }
1531
+ break;
1532
+ }
1533
+ // Index signature: `[key: string]: TypeName`
1534
+ case "index_signature": {
1535
+ const typeNode = node.childForFieldName("type");
1536
+ if (typeNode) {
1537
+ extractTypeNamesFromNode(typeNode, code, references, "propertyType");
1538
+ }
1539
+ break;
1540
+ }
1541
+ // Method signature return type
1542
+ case "method_signature": {
1543
+ const returnTypeNode = node.childForFieldName("return_type");
1544
+ if (returnTypeNode) {
1545
+ extractTypeNamesFromNode(returnTypeNode, code, references, "returnType");
1546
+ }
1547
+ // Also check parameters
1548
+ const paramsNode = node.childForFieldName("parameters");
1549
+ if (paramsNode) {
1550
+ extractJSTypeReferences(paramsNode, code, references);
1551
+ }
1552
+ break;
1553
+ }
1554
+ // Required/optional parameters with type annotations
1555
+ case "required_parameter":
1556
+ case "optional_parameter": {
1557
+ const typeNode = node.childForFieldName("type");
1558
+ if (typeNode) {
1559
+ extractTypeNamesFromNode(typeNode, code, references, "typeAnnotation");
1560
+ }
1561
+ break;
1562
+ }
1563
+ }
1564
+ // Recurse into children
1565
+ for (const child of node.children) {
1566
+ if (child) {
1567
+ extractJSTypeReferences(child, code, references);
1568
+ }
1569
+ }
1570
+ }
1571
+ /**
1572
+ * Extract type names from a type node (handles nested types, unions, etc.)
1573
+ */
1574
+ export function extractTypeNamesFromNode(node, code, references, context) {
1575
+ if (!node)
1576
+ return;
1577
+ switch (node.type) {
1578
+ // Simple type reference: `TypeName`
1579
+ case "type_identifier": {
1580
+ const name = getText(node, code);
1581
+ // Skip built-in types
1582
+ if (!isTSBuiltinType(name)) {
1583
+ references.push({
1584
+ name,
1585
+ context,
1586
+ line: node.startPosition.row + 1,
1587
+ });
1588
+ }
1589
+ break;
1590
+ }
1591
+ // Generic type: `Array<T>`, `Promise<T>`, `Map<K, V>`
1592
+ case "generic_type": {
1593
+ const nameNode = node.childForFieldName("name");
1594
+ if (nameNode) {
1595
+ const name = getText(nameNode, code);
1596
+ if (!isTSBuiltinType(name)) {
1597
+ references.push({
1598
+ name,
1599
+ context,
1600
+ line: node.startPosition.row + 1,
1601
+ });
1602
+ }
1603
+ }
1604
+ // Also extract type arguments
1605
+ const argsNode = node.childForFieldName("type_arguments");
1606
+ if (argsNode) {
1607
+ extractTypeNamesFromNode(argsNode, code, references, "genericParam");
1608
+ }
1609
+ break;
1610
+ }
1611
+ // Nested member expression type: `Namespace.TypeName`
1612
+ case "nested_type_identifier": {
1613
+ // Get the full qualified name
1614
+ const name = getText(node, code);
1615
+ references.push({
1616
+ name,
1617
+ context,
1618
+ line: node.startPosition.row + 1,
1619
+ });
1620
+ break;
1621
+ }
1622
+ // Union type: `TypeA | TypeB`
1623
+ case "union_type": {
1624
+ for (const child of node.children) {
1625
+ if (child && child.type !== "|") {
1626
+ extractTypeNamesFromNode(child, code, references, context);
1627
+ }
1628
+ }
1629
+ break;
1630
+ }
1631
+ // Intersection type: `TypeA & TypeB`
1632
+ case "intersection_type": {
1633
+ for (const child of node.children) {
1634
+ if (child && child.type !== "&") {
1635
+ extractTypeNamesFromNode(child, code, references, context);
1636
+ }
1637
+ }
1638
+ break;
1639
+ }
1640
+ // Array type: `TypeName[]`
1641
+ case "array_type": {
1642
+ const elementType = node.children[0];
1643
+ if (elementType) {
1644
+ extractTypeNamesFromNode(elementType, code, references, context);
1645
+ }
1646
+ break;
1647
+ }
1648
+ // Parenthesized type: `(TypeName)`
1649
+ case "parenthesized_type": {
1650
+ for (const child of node.children) {
1651
+ if (child && child.type !== "(" && child.type !== ")") {
1652
+ extractTypeNamesFromNode(child, code, references, context);
1653
+ }
1654
+ }
1655
+ break;
1656
+ }
1657
+ // Type arguments: `<TypeA, TypeB>`
1658
+ case "type_arguments": {
1659
+ for (const child of node.children) {
1660
+ if (child &&
1661
+ child.type !== "<" &&
1662
+ child.type !== ">" &&
1663
+ child.type !== ",") {
1664
+ extractTypeNamesFromNode(child, code, references, "genericParam");
1665
+ }
1666
+ }
1667
+ break;
1668
+ }
1669
+ // Conditional type: `T extends U ? X : Y`
1670
+ case "conditional_type": {
1671
+ for (const child of node.children) {
1672
+ if (child) {
1673
+ extractTypeNamesFromNode(child, code, references, context);
1674
+ }
1675
+ }
1676
+ break;
1677
+ }
1678
+ // Indexed access type: `T[K]`
1679
+ case "indexed_access_type": {
1680
+ const objectType = node.childForFieldName("object_type");
1681
+ const indexType = node.childForFieldName("index_type");
1682
+ if (objectType) {
1683
+ extractTypeNamesFromNode(objectType, code, references, context);
1684
+ }
1685
+ if (indexType) {
1686
+ extractTypeNamesFromNode(indexType, code, references, context);
1687
+ }
1688
+ break;
1689
+ }
1690
+ // Mapped type: `{ [K in keyof T]: V }`
1691
+ case "mapped_type_clause": {
1692
+ for (const child of node.children) {
1693
+ if (child) {
1694
+ extractTypeNamesFromNode(child, code, references, context);
1695
+ }
1696
+ }
1697
+ break;
1698
+ }
1699
+ // Object type / type literal: `{ prop: Type }`
1700
+ case "object_type": {
1701
+ for (const child of node.children) {
1702
+ if (child) {
1703
+ extractJSTypeReferences(child, code, references);
1704
+ }
1705
+ }
1706
+ break;
1707
+ }
1708
+ // Function type: `(param: Type) => ReturnType`
1709
+ case "function_type": {
1710
+ const paramsNode = node.childForFieldName("parameters");
1711
+ const returnNode = node.childForFieldName("return_type");
1712
+ if (paramsNode) {
1713
+ for (const child of paramsNode.children) {
1714
+ if (child) {
1715
+ extractJSTypeReferences(child, code, references);
1716
+ }
1717
+ }
1718
+ }
1719
+ if (returnNode) {
1720
+ extractTypeNamesFromNode(returnNode, code, references, "returnType");
1721
+ }
1722
+ break;
1723
+ }
1724
+ // Typeof type: `typeof SomeValue`
1725
+ case "typeof_type": {
1726
+ // Skip - this references a value, not a type
1727
+ break;
1728
+ }
1729
+ // Keyof type: `keyof T`
1730
+ case "keyof_type": {
1731
+ const typeArg = node.children.find((c) => c && c.type !== "keyof");
1732
+ if (typeArg) {
1733
+ extractTypeNamesFromNode(typeArg, code, references, context);
1734
+ }
1735
+ break;
1736
+ }
1737
+ default: {
1738
+ // For other node types, recurse into children
1739
+ for (const child of node.children) {
1740
+ if (child) {
1741
+ extractTypeNamesFromNode(child, code, references, context);
1742
+ }
1743
+ }
1744
+ }
1745
+ }
1746
+ }
1747
+ // ============================================================================
1748
+ // Helper Functions
1749
+ // ============================================================================
1750
+ /**
1751
+ * Extract text from an AST node using pre-computed indices (faster than substring on each call)
1752
+ */
1753
+ function getText(node, code) {
1754
+ return code.slice(node.startIndex, node.endIndex);
1755
+ }
1756
+ /**
1757
+ * Get a specific line from code
1758
+ * Uses simple split - caching would require a Map with string keys which has memory implications
1759
+ */
1760
+ function getLineText(code, lineIndex) {
1761
+ const lines = code.split("\n");
1762
+ return lines[lineIndex]?.trim() || "";
1763
+ }
1764
+ /**
1765
+ * Count arguments in a function call
1766
+ */
1767
+ function countArgs(argsNode) {
1768
+ if (!argsNode)
1769
+ return 0;
1770
+ let count = 0;
1771
+ for (const child of argsNode.children) {
1772
+ if (child.type !== "(" && child.type !== ")" && child.type !== ",") {
1773
+ count++;
1774
+ }
1775
+ }
1776
+ return count;
1777
+ }
1778
+ /**
1779
+ * Extract the root object identifier from a complex expression.
1780
+ * Handles cases like:
1781
+ * - (err as Type) -> err (unwraps parenthesized expressions and type assertions)
1782
+ * - obj.property -> obj
1783
+ * - arr[index] -> arr
1784
+ * - identifier -> identifier
1785
+ *
1786
+ * @param node - The AST node representing the object in a member expression
1787
+ * @param code - The source code string
1788
+ * @returns The root object text (variable name)
1789
+ */
1790
+ function getRootObject(node, code) {
1791
+ if (!node)
1792
+ return "";
1793
+ // Handle parenthesized expressions: (expr)
1794
+ // This includes type assertions like (err as Error) or (err satisfies Error)
1795
+ if (node.type === "parenthesized_expression") {
1796
+ // Find the inner expression (skip the parentheses)
1797
+ for (const child of node.children) {
1798
+ if (child.type !== "(" && child.type !== ")") {
1799
+ return getRootObject(child, code);
1800
+ }
1801
+ }
1802
+ }
1803
+ // Handle type assertion expressions: expr as Type, expr satisfies Type
1804
+ if (node.type === "as_expression" || node.type === "satisfies_expression") {
1805
+ const leftNode = node.childForFieldName("left") || node.children[0];
1806
+ if (leftNode) {
1807
+ return getRootObject(leftNode, code);
1808
+ }
1809
+ }
1810
+ // Handle non-null assertion expressions: expr!
1811
+ if (node.type === "non_null_expression") {
1812
+ const expression = node.children[0];
1813
+ if (expression) {
1814
+ return getRootObject(expression, code);
1815
+ }
1816
+ }
1817
+ // Handle call expressions: fn() - extract the function name
1818
+ if (node.type === "call_expression" || node.type === "new_expression") {
1819
+ const funcNode = node.childForFieldName("function") || node.childForFieldName("constructor");
1820
+ if (funcNode) {
1821
+ return getRootObject(funcNode, code);
1822
+ }
1823
+ }
1824
+ // Handle member expressions: obj.prop - recurse into the object to get the root identifier
1825
+ // This correctly handles multiline chains like db\n.select().from() → "db"
1826
+ // Previously returned getText which included newlines, causing FPs in validation
1827
+ if (node.type === "member_expression") {
1828
+ const objNode = node.childForFieldName("object");
1829
+ if (objNode) {
1830
+ return getRootObject(objNode, code);
1831
+ }
1832
+ return getText(node, code);
1833
+ }
1834
+ // Handle subscript expressions: arr[index] - return the full text
1835
+ if (node.type === "subscript_expression") {
1836
+ return getText(node, code);
1837
+ }
1838
+ // Base case: identifier or simple expression
1839
+ return getText(node, code);
1840
+ }
1841
+ //# sourceMappingURL=javascript.js.map