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,799 @@
1
+ /**
2
+ * Python Extractor Module - Python-specific AST Extraction
3
+ *
4
+ * This module extracts symbols, usages, imports, and type references from Python code
5
+ * using Tree-sitter AST parsing. It handles Python-specific syntax including:
6
+ * - Function and class definitions
7
+ * - Method definitions with class context
8
+ * - Decorators
9
+ * - Async functions
10
+ * - Import statements (import and from...import)
11
+ * - Type hints and annotations
12
+ * - Class inheritance
13
+ *
14
+ * @format
15
+ */
16
+ import { isPythonBuiltin, isPythonBuiltinType } from "../builtins.js";
17
+ // Common project-internal import prefixes for Python
18
+ const INTERNAL_PREFIXES = [
19
+ "app.", "src.", "tests.", "test.", "lib.", "core.", "api.",
20
+ "models.", "services.", "utils.", "helpers.", "config.",
21
+ "schemas.", "routers.", "routes.", "views.", "controllers.",
22
+ "handlers.", "middleware.", "database.", "db.",
23
+ ];
24
+ // ============================================================================
25
+ // Main Extraction Functions
26
+ // ============================================================================
27
+ /**
28
+ * Extract all symbol definitions from Python AST.
29
+ * Recursively traverses the AST to find function definitions, class definitions,
30
+ * method definitions, and module-level variable assignments.
31
+ *
32
+ * @param node - The AST node to extract symbols from
33
+ * @param code - The source code string
34
+ * @param filePath - Path to the file being analyzed
35
+ * @param symbols - Array to accumulate extracted symbols
36
+ * @param currentClass - Name of the current class context (for methods), or null
37
+ */
38
+ export function extractPythonSymbols(node, code, filePath, symbols, currentClass) {
39
+ if (!node)
40
+ return;
41
+ switch (node.type) {
42
+ case "function_definition": {
43
+ const nameNode = node.childForFieldName("name");
44
+ const paramsNode = node.childForFieldName("parameters");
45
+ const decorators = getDecorators(node, code);
46
+ if (nameNode) {
47
+ const name = getText(nameNode, code);
48
+ const params = extractPythonParams(paramsNode, code);
49
+ const isMethod = currentClass !== null;
50
+ const isAsync = node.children.some((c) => c.type === "async" || getText(c, code) === "async");
51
+ // Detect API Routing (Semantic Bridge)
52
+ const routingDecorators = decorators.filter(d => d.includes(".route") ||
53
+ d.includes(".get") ||
54
+ d.includes(".post") ||
55
+ d.includes(".put") ||
56
+ d.includes(".delete") ||
57
+ d.includes(".patch"));
58
+ if (routingDecorators.length > 0) {
59
+ for (const d of routingDecorators) {
60
+ // Extract route pattern: @app.route("/api/user") -> /api/user
61
+ const routeMatch = d.match(/["']([^"']+)["']/);
62
+ if (routeMatch) {
63
+ symbols.push({
64
+ name: routeMatch[1],
65
+ type: "route",
66
+ file: filePath,
67
+ line: node.startPosition.row + 1,
68
+ column: node.startPosition.column,
69
+ scope: name, // Associate with the function handling it
70
+ });
71
+ }
72
+ }
73
+ }
74
+ symbols.push({
75
+ name,
76
+ type: isMethod ? "method" : "function",
77
+ file: filePath,
78
+ line: node.startPosition.row + 1,
79
+ column: node.startPosition.column,
80
+ params,
81
+ paramCount: params.length,
82
+ scope: currentClass || undefined,
83
+ isAsync,
84
+ decorators,
85
+ });
86
+ }
87
+ break;
88
+ }
89
+ case "class_definition": {
90
+ const nameNode = node.childForFieldName("name");
91
+ const decorators = getDecorators(node, code);
92
+ if (nameNode) {
93
+ const className = getText(nameNode, code);
94
+ symbols.push({
95
+ name: className,
96
+ type: "class",
97
+ file: filePath,
98
+ line: node.startPosition.row + 1,
99
+ column: node.startPosition.column,
100
+ decorators,
101
+ });
102
+ // Process class body with class context
103
+ const bodyNode = node.childForFieldName("body");
104
+ if (bodyNode) {
105
+ for (const child of bodyNode.children) {
106
+ extractPythonSymbols(child, code, filePath, symbols, className);
107
+ }
108
+ }
109
+ return; // Don't recurse normally, we handled the body
110
+ }
111
+ break;
112
+ }
113
+ case "assignment": {
114
+ // Top-level assignments (module variables)
115
+ // In tree-sitter-python, module-level assignments are:
116
+ // module > expression_statement > assignment
117
+ const isModuleLevel = node.parent?.type === "module" ||
118
+ (node.parent?.type === "expression_statement" &&
119
+ node.parent?.parent?.type === "module");
120
+ // Class-level assignments (class attributes)
121
+ const isClassLevel = !isModuleLevel &&
122
+ currentClass !== null &&
123
+ (node.parent?.type === "expression_statement" &&
124
+ node.parent?.parent?.type === "block" &&
125
+ node.parent?.parent?.parent?.type === "class_definition");
126
+ if (isModuleLevel || isClassLevel) {
127
+ const leftNode = node.childForFieldName("left");
128
+ if (leftNode?.type === "identifier") {
129
+ const name = getText(leftNode, code);
130
+ symbols.push({
131
+ name,
132
+ type: "variable",
133
+ file: filePath,
134
+ line: node.startPosition.row + 1,
135
+ column: node.startPosition.column,
136
+ isExported: isModuleLevel,
137
+ scope: isClassLevel ? currentClass || undefined : undefined,
138
+ });
139
+ }
140
+ }
141
+ break;
142
+ }
143
+ }
144
+ // Recurse into children
145
+ for (const child of node.children) {
146
+ extractPythonSymbols(child, code, filePath, symbols, currentClass);
147
+ }
148
+ }
149
+ /**
150
+ * Collect all locally-defined identifier names in a Python AST.
151
+ * This pre-pass collects assignment targets, function parameters, for-loop variables,
152
+ * with-as targets, except-as targets, comprehension variables, and function/class names.
153
+ * Used to prevent false positives when these names appear as references later in the code.
154
+ *
155
+ * @param node - The root AST node to walk
156
+ * @param code - The source code string
157
+ * @param definitions - Set to accumulate locally-defined names
158
+ */
159
+ export function collectPythonLocalDefinitions(node, code, definitions) {
160
+ if (!node)
161
+ return;
162
+ switch (node.type) {
163
+ case "identifier": {
164
+ const name = getText(node, code);
165
+ const parent = node.parent;
166
+ if (!parent)
167
+ break;
168
+ // Assignment target: x = ...
169
+ if (parent.type === "assignment" && parent.childForFieldName("left")?.id === node.id) {
170
+ definitions.add(name);
171
+ }
172
+ // Augmented assignment: x += ...
173
+ if (parent.type === "augmented_assignment" && parent.childForFieldName("left")?.id === node.id) {
174
+ definitions.add(name);
175
+ }
176
+ // Function/class definition name
177
+ if ((parent.type === "function_definition" || parent.type === "class_definition") &&
178
+ parent.childForFieldName("name")?.id === node.id) {
179
+ definitions.add(name);
180
+ }
181
+ // Function parameters (all types)
182
+ if (parent.type === "parameters" || parent.type === "typed_parameter" ||
183
+ parent.type === "default_parameter" || parent.type === "typed_default_parameter" ||
184
+ parent.type === "list_splat_pattern" || parent.type === "dictionary_splat_pattern") {
185
+ definitions.add(name);
186
+ }
187
+ // Lambda parameters
188
+ if (parent.type === "lambda_parameters") {
189
+ definitions.add(name);
190
+ }
191
+ // For-loop variable: for x in ...
192
+ if (parent.type === "for_statement" && parent.childForFieldName("left")?.id === node.id) {
193
+ definitions.add(name);
194
+ }
195
+ // Tuple/pattern unpacking targets
196
+ if (parent.type === "pattern_list" || parent.type === "tuple_pattern") {
197
+ definitions.add(name);
198
+ }
199
+ // With-as / except-as target
200
+ if (parent.type === "as_pattern_target") {
201
+ definitions.add(name);
202
+ }
203
+ // Comprehension variable
204
+ if (parent.type === "for_in_clause" && parent.childForFieldName("left")?.id === node.id) {
205
+ definitions.add(name);
206
+ }
207
+ // Walrus operator target
208
+ if (parent.type === "named_expression" && parent.childForFieldName("name")?.id === node.id) {
209
+ definitions.add(name);
210
+ }
211
+ // Global/nonlocal declarations
212
+ if (parent.type === "global_statement" || parent.type === "nonlocal_statement") {
213
+ definitions.add(name);
214
+ }
215
+ // Unpacking target in assignment
216
+ if (isUnpackingTarget(node)) {
217
+ definitions.add(name);
218
+ }
219
+ break;
220
+ }
221
+ }
222
+ for (const child of node.children) {
223
+ if (child) {
224
+ collectPythonLocalDefinitions(child, code, definitions);
225
+ }
226
+ }
227
+ }
228
+ /**
229
+ * Extract all symbol usages from Python AST.
230
+ * Finds function calls, method calls, and attribute access.
231
+ * Skips imported symbols, built-in functions, and locally-defined variables to avoid false positives.
232
+ *
233
+ * @param node - The AST node to extract usages from
234
+ * @param code - The source code string
235
+ * @param usages - Array to accumulate extracted usages
236
+ * @param externalSymbols - Set of imported symbol names to skip
237
+ * @param localDefinitions - Set of locally-defined names to skip (from collectPythonLocalDefinitions)
238
+ */
239
+ export function extractPythonUsages(node, code, usages, externalSymbols, localDefinitions, isRoot = true) {
240
+ if (!node)
241
+ return;
242
+ // If localDefinitions weren't provided (direct extractor usage), collect them once.
243
+ // This mirrors the behavior of the unified extractor wrapper and prevents false
244
+ // positives for local variables (with-as targets, except-as targets, params, etc.).
245
+ if (isRoot && !localDefinitions) {
246
+ localDefinitions = new Set();
247
+ collectPythonLocalDefinitions(node, code, localDefinitions);
248
+ }
249
+ switch (node.type) {
250
+ case "call": {
251
+ const funcNode = node.childForFieldName("function");
252
+ const argsNode = node.childForFieldName("arguments");
253
+ const argCount = countArgs(argsNode);
254
+ if (funcNode) {
255
+ if (funcNode.type === "identifier") {
256
+ // Simple function call: func()
257
+ const name = getText(funcNode, code);
258
+ if (isPythonBuiltin(name))
259
+ break;
260
+ if (localDefinitions?.has(name))
261
+ break;
262
+ usages.push({
263
+ name,
264
+ type: "call",
265
+ line: node.startPosition.row + 1,
266
+ column: node.startPosition.column,
267
+ code: getLineText(code, node.startPosition.row),
268
+ argCount,
269
+ });
270
+ }
271
+ else if (funcNode.type === "attribute") {
272
+ // Method call: obj.method()
273
+ const objNode = funcNode.childForFieldName("object");
274
+ const attrNode = funcNode.childForFieldName("attribute");
275
+ if (objNode && attrNode) {
276
+ const obj = getText(objNode, code);
277
+ const method = getText(attrNode, code);
278
+ // Only track method calls with simple object references (identifier, attribute, subscript)
279
+ // Skip complex expressions like (end - start).total_seconds() where the "object"
280
+ // is a binary expression — these produce meaningless object names for validation
281
+ const simpleObjTypes = new Set([
282
+ "identifier", "attribute", "subscript", "call",
283
+ "parenthesized_expression",
284
+ ]);
285
+ const isSimpleObj = simpleObjTypes.has(objNode.type) &&
286
+ (objNode.type !== "parenthesized_expression" ||
287
+ objNode.namedChildren[0]?.type === "identifier");
288
+ if (isSimpleObj) {
289
+ if (isPythonBuiltin(method))
290
+ break;
291
+ usages.push({
292
+ name: method,
293
+ type: "methodCall",
294
+ object: obj,
295
+ line: node.startPosition.row + 1,
296
+ column: node.startPosition.column,
297
+ code: getLineText(code, node.startPosition.row),
298
+ argCount,
299
+ });
300
+ }
301
+ }
302
+ }
303
+ }
304
+ break;
305
+ }
306
+ case "identifier": {
307
+ const name = getText(node, code);
308
+ // Builtins are skipped to reduce false positives, BUT imported names should still
309
+ // be allowed through so we can correctly track import usage (prevents unusedImport
310
+ // false positives for patterns like: `from x import settings; settings.env`).
311
+ if (isPythonBuiltin(name) && !externalSymbols.has(name))
312
+ break;
313
+ const parent = node.parent;
314
+ if (!parent)
315
+ break;
316
+ // Skip decorator identifiers: @staticmethod, @app.route(...), etc.
317
+ // These frequently cause false positives when treated as normal references.
318
+ // Decorator metadata is captured separately via symbol extraction (see getDecorators).
319
+ let decoratorAncestor = parent;
320
+ while (decoratorAncestor) {
321
+ if (decoratorAncestor.type === "decorator") {
322
+ return;
323
+ }
324
+ decoratorAncestor = decoratorAncestor.parent;
325
+ }
326
+ // Skip if it's an attribute name (obj.NAME) — handled by methodCall extraction
327
+ if (parent.type === "attribute" && parent.childForFieldName("attribute")?.id === node.id)
328
+ break;
329
+ // Skip if it's a function/class definition name
330
+ if ((parent.type === "function_definition" || parent.type === "class_definition") &&
331
+ parent.childForFieldName("name")?.id === node.id)
332
+ break;
333
+ // Skip if it's a parameter definition (function params, lambda params)
334
+ if (parent.type === "parameters" || parent.type === "typed_parameter" || parent.type === "default_parameter" ||
335
+ parent.type === "typed_default_parameter" || parent.type === "list_splat_pattern" || parent.type === "dictionary_splat_pattern" ||
336
+ parent.type === "lambda_parameters")
337
+ break;
338
+ // Skip if it's part of an import statement
339
+ if (parent.type === "dotted_name" || parent.type === "aliased_import" ||
340
+ parent.type === "import_statement" || parent.type === "import_from_statement")
341
+ break;
342
+ // Skip assignment targets (left side of =) — these are definitions, not usages
343
+ if (parent.type === "assignment" && parent.childForFieldName("left")?.id === node.id)
344
+ break;
345
+ if (parent.type === "augmented_assignment" && parent.childForFieldName("left")?.id === node.id)
346
+ break;
347
+ // Skip if it's the target in a for loop (for x in items)
348
+ if (parent.type === "for_statement" && parent.childForFieldName("left")?.id === node.id)
349
+ break;
350
+ // Also handle tuple unpacking in for loops: for k, v in items
351
+ if (parent.type === "pattern_list" || parent.type === "tuple_pattern")
352
+ break;
353
+ // Skip if it's the variable in a with statement (with open() as f)
354
+ // or except clause (except Exception as e)
355
+ // Tree-sitter wraps the alias identifier in an as_pattern_target node
356
+ if (parent.type === "as_pattern_target")
357
+ break;
358
+ if (parent.type === "as_pattern" && parent.childForFieldName("alias")?.id === node.id)
359
+ break;
360
+ if (parent.type === "except_clause")
361
+ break;
362
+ // Skip keyword argument names (func(key=value) — skip "key")
363
+ if (parent.type === "keyword_argument" && parent.childForFieldName("name")?.id === node.id)
364
+ break;
365
+ // Skip comprehension variables (x for x in items)
366
+ if (parent.type === "for_in_clause" && parent.childForFieldName("left")?.id === node.id)
367
+ break;
368
+ // Decorators are intentionally skipped above.
369
+ // Skip if it's the left side of an annotated assignment (x: int = 5)
370
+ if (parent.type === "type" && parent.parent?.type === "assignment")
371
+ break;
372
+ // Skip walrus operator target (:= )
373
+ if (parent.type === "named_expression" && parent.childForFieldName("name")?.id === node.id)
374
+ break;
375
+ // Skip global/nonlocal declarations
376
+ if (parent.type === "global_statement" || parent.type === "nonlocal_statement")
377
+ break;
378
+ // Skip if it's a tuple/list unpacking target in assignment
379
+ if (isUnpackingTarget(node))
380
+ break;
381
+ // Skip locally-defined variables (function params, assignment targets, loop vars, etc.)
382
+ // These are local scope — CodeGuardian only validates project-level symbols
383
+ if (localDefinitions?.has(name))
384
+ break;
385
+ // If we reach here, it's a genuine reference usage
386
+ const exists = usages.some(u => u.line === node.startPosition.row + 1 && u.column === node.startPosition.column);
387
+ if (!exists) {
388
+ usages.push({
389
+ name,
390
+ type: "reference",
391
+ line: node.startPosition.row + 1,
392
+ column: node.startPosition.column,
393
+ code: getLineText(code, node.startPosition.row),
394
+ });
395
+ }
396
+ break;
397
+ }
398
+ }
399
+ for (const child of node.children) {
400
+ if (child) {
401
+ extractPythonUsages(child, code, usages, externalSymbols, localDefinitions, false);
402
+ }
403
+ }
404
+ }
405
+ /**
406
+ * Extract all import statements from Python AST.
407
+ * Handles both "import module" and "from module import name" statements.
408
+ * Determines if imports are external (third-party) or internal (project files).
409
+ *
410
+ * @param node - The AST node to extract imports from
411
+ * @param code - The source code string
412
+ * @param imports - Array to accumulate extracted imports
413
+ */
414
+ export function extractPythonImports(node, code, imports) {
415
+ if (!node)
416
+ return;
417
+ switch (node.type) {
418
+ case "import_statement": {
419
+ // import module OR import module as alias
420
+ for (const child of node.children) {
421
+ if (!child)
422
+ continue;
423
+ if (child.type === "dotted_name") {
424
+ // Simple: import json, import app.models
425
+ const module = getText(child, code);
426
+ const isInternal = module.startsWith(".") ||
427
+ INTERNAL_PREFIXES.some((prefix) => module.startsWith(prefix) || module === prefix.slice(0, -1));
428
+ imports.push({
429
+ module,
430
+ names: [{ imported: module, local: module }],
431
+ isExternal: !isInternal,
432
+ line: node.startPosition.row + 1,
433
+ });
434
+ }
435
+ else if (child.type === "aliased_import") {
436
+ // Aliased: import sqlalchemy as sa
437
+ const nameNode = child.childForFieldName("name");
438
+ const aliasNode = child.childForFieldName("alias");
439
+ if (nameNode) {
440
+ const module = getText(nameNode, code);
441
+ const local = aliasNode ? getText(aliasNode, code) : module;
442
+ const isInternal = module.startsWith(".") ||
443
+ INTERNAL_PREFIXES.some((prefix) => module.startsWith(prefix) || module === prefix.slice(0, -1));
444
+ imports.push({
445
+ module,
446
+ names: [{ imported: module, local }],
447
+ isExternal: !isInternal,
448
+ line: node.startPosition.row + 1,
449
+ });
450
+ }
451
+ }
452
+ }
453
+ break;
454
+ }
455
+ case "import_from_statement": {
456
+ // from module import x, y
457
+ const moduleNode = node.childForFieldName("module_name");
458
+ if (moduleNode) {
459
+ const module = getText(moduleNode, code);
460
+ const names = [];
461
+ for (const child of node.children) {
462
+ if (!child)
463
+ continue;
464
+ if (child.type === "dotted_name" && child !== moduleNode) {
465
+ const name = getText(child, code);
466
+ names.push({ imported: name, local: name });
467
+ }
468
+ else if (child.type === "aliased_import") {
469
+ const nameNode = child.childForFieldName("name");
470
+ const aliasNode = child.childForFieldName("alias");
471
+ if (nameNode) {
472
+ const imported = getText(nameNode, code);
473
+ const local = aliasNode ? getText(aliasNode, code) : imported;
474
+ names.push({ imported, local });
475
+ }
476
+ }
477
+ }
478
+ // Determine if import is external (third-party package) or internal (project file)
479
+ // Internal imports in Python:
480
+ // - Relative imports: from . import x, from .. import x
481
+ // - Common project prefixes: app., src., tests., lib., core., api., models., services., etc.
482
+ const isInternal = module.startsWith(".") ||
483
+ INTERNAL_PREFIXES.some((prefix) => module.startsWith(prefix) || module === prefix.slice(0, -1));
484
+ const isExternal = !isInternal;
485
+ imports.push({
486
+ module,
487
+ names,
488
+ isExternal,
489
+ line: node.startPosition.row + 1,
490
+ });
491
+ }
492
+ break;
493
+ }
494
+ }
495
+ for (const child of node.children) {
496
+ if (child) {
497
+ extractPythonImports(child, code, imports);
498
+ }
499
+ }
500
+ }
501
+ /**
502
+ * Extract type references from Python type hints.
503
+ * Handles function parameter annotations, return types, variable annotations,
504
+ * and class inheritance.
505
+ *
506
+ * @param node - The AST node to extract type references from
507
+ * @param code - The source code string
508
+ * @param references - Array to accumulate extracted type references
509
+ */
510
+ export function extractPythonTypeReferences(node, code, references) {
511
+ if (!node)
512
+ return;
513
+ switch (node.type) {
514
+ // Function parameter type annotation: def func(x: TypeName)
515
+ case "typed_parameter": {
516
+ const typeNode = node.childForFieldName("type");
517
+ if (typeNode) {
518
+ extractPythonTypeNamesFromNode(typeNode, code, references, "typeAnnotation");
519
+ }
520
+ break;
521
+ }
522
+ // Function return type: def func() -> ReturnType
523
+ case "function_definition": {
524
+ const returnTypeNode = node.childForFieldName("return_type");
525
+ if (returnTypeNode) {
526
+ extractPythonTypeNamesFromNode(returnTypeNode, code, references, "returnType");
527
+ }
528
+ break;
529
+ }
530
+ // Variable annotation: x: TypeName = value
531
+ case "type": {
532
+ // This is the type node in an annotated assignment
533
+ extractPythonTypeNamesFromNode(node, code, references, "typeAnnotation");
534
+ break;
535
+ }
536
+ // Class inheritance: class Foo(BaseClass)
537
+ case "argument_list": {
538
+ const parent = node.parent;
539
+ if (parent?.type === "class_definition") {
540
+ for (const child of node.children) {
541
+ if (child && child.type === "identifier") {
542
+ const name = getText(child, code);
543
+ if (!isPythonBuiltinType(name)) {
544
+ references.push({
545
+ name,
546
+ context: "extends",
547
+ line: child.startPosition.row + 1,
548
+ });
549
+ }
550
+ }
551
+ }
552
+ }
553
+ break;
554
+ }
555
+ }
556
+ // Recurse into children
557
+ for (const child of node.children) {
558
+ if (child) {
559
+ extractPythonTypeReferences(child, code, references);
560
+ }
561
+ }
562
+ }
563
+ // ============================================================================
564
+ // Helper Functions
565
+ // ============================================================================
566
+ /**
567
+ * Extract parameter names from a Python function parameters node.
568
+ * Filters out 'self' and 'cls' parameters which are implicit in Python methods.
569
+ *
570
+ * @param paramsNode - The parameters node from the function definition
571
+ * @param code - The source code string
572
+ * @returns Array of parameter names (excluding self/cls)
573
+ */
574
+ export function extractPythonParams(paramsNode, code) {
575
+ if (!paramsNode)
576
+ return [];
577
+ const params = [];
578
+ for (const child of paramsNode.children) {
579
+ if (child.type === "identifier") {
580
+ const name = getText(child, code);
581
+ if (name !== "self" && name !== "cls") {
582
+ params.push(name);
583
+ }
584
+ }
585
+ else if (child.type === "typed_parameter" ||
586
+ child.type === "default_parameter") {
587
+ const nameNode = child.childForFieldName("name") || child.children[0];
588
+ if (nameNode) {
589
+ const name = getText(nameNode, code);
590
+ if (name !== "self" && name !== "cls") {
591
+ params.push(name);
592
+ }
593
+ }
594
+ }
595
+ }
596
+ return params;
597
+ }
598
+ /**
599
+ * Extract type names from Python type hint nodes.
600
+ * Handles simple types, generic types, union types, and attribute access.
601
+ * Recursively processes complex type expressions.
602
+ *
603
+ * @param node - The type hint node to extract names from
604
+ * @param code - The source code string
605
+ * @param references - Array to accumulate extracted type references
606
+ * @param context - The context in which the type appears
607
+ */
608
+ export function extractPythonTypeNamesFromNode(node, code, references, context) {
609
+ if (!node)
610
+ return;
611
+ switch (node.type) {
612
+ // Simple type: TypeName
613
+ case "identifier": {
614
+ const name = getText(node, code);
615
+ if (!isPythonBuiltinType(name)) {
616
+ references.push({
617
+ name,
618
+ context,
619
+ line: node.startPosition.row + 1,
620
+ });
621
+ }
622
+ break;
623
+ }
624
+ // Generic type: List[TypeName], Dict[K, V], Optional[T]
625
+ case "subscript": {
626
+ const valueNode = node.childForFieldName("value");
627
+ const subscriptNode = node.childForFieldName("subscript");
628
+ if (valueNode) {
629
+ const typeName = getText(valueNode, code);
630
+ if (!isPythonBuiltinType(typeName)) {
631
+ references.push({
632
+ name: typeName,
633
+ context,
634
+ line: valueNode.startPosition.row + 1,
635
+ });
636
+ }
637
+ }
638
+ // Extract type arguments
639
+ if (subscriptNode) {
640
+ extractPythonTypeNamesFromNode(subscriptNode, code, references, "genericParam");
641
+ }
642
+ break;
643
+ }
644
+ // Union type (Python 3.10+): TypeA | TypeB
645
+ case "binary_operator": {
646
+ const operator = node.children.find((c) => c && getText(c, code) === "|");
647
+ if (operator) {
648
+ const left = node.childForFieldName("left");
649
+ const right = node.childForFieldName("right");
650
+ if (left)
651
+ extractPythonTypeNamesFromNode(left, code, references, context);
652
+ if (right)
653
+ extractPythonTypeNamesFromNode(right, code, references, context);
654
+ }
655
+ break;
656
+ }
657
+ // Tuple of types in subscript: Dict[str, int] -> the "str, int" part
658
+ case "expression_list": {
659
+ for (const child of node.children) {
660
+ if (child && child.type !== ",") {
661
+ extractPythonTypeNamesFromNode(child, code, references, context);
662
+ }
663
+ }
664
+ break;
665
+ }
666
+ // Attribute access: module.TypeName
667
+ case "attribute": {
668
+ const name = getText(node, code);
669
+ references.push({
670
+ name,
671
+ context,
672
+ line: node.startPosition.row + 1,
673
+ });
674
+ break;
675
+ }
676
+ // None type
677
+ case "none": {
678
+ // Skip - it's a builtin
679
+ break;
680
+ }
681
+ default: {
682
+ // For other node types, recurse into children
683
+ for (const child of node.children) {
684
+ if (child) {
685
+ extractPythonTypeNamesFromNode(child, code, references, context);
686
+ }
687
+ }
688
+ }
689
+ }
690
+ }
691
+ // ============================================================================
692
+ // Internal Helper Functions
693
+ // ============================================================================
694
+ /**
695
+ * Extract text from an AST node using pre-computed indices.
696
+ * This is faster than using substring on each call.
697
+ *
698
+ * @param node - The AST node to extract text from
699
+ * @param code - The source code string
700
+ * @returns The text content of the node
701
+ */
702
+ function getText(node, code) {
703
+ return code.slice(node.startIndex, node.endIndex);
704
+ }
705
+ /**
706
+ * Get a specific line from code.
707
+ * Uses simple split - caching would require a Map with string keys which has memory implications.
708
+ *
709
+ * @param code - The source code string
710
+ * @param lineIndex - The zero-based line index
711
+ * @returns The trimmed line text, or empty string if line doesn't exist
712
+ */
713
+ function getLineText(code, lineIndex) {
714
+ const lines = code.split("\n");
715
+ return lines[lineIndex]?.trim() || "";
716
+ }
717
+ /**
718
+ * Count the number of arguments in a function call.
719
+ * Excludes parentheses and commas from the count.
720
+ *
721
+ * @param argsNode - The arguments node from a call expression
722
+ * @returns The number of arguments
723
+ */
724
+ function countArgs(argsNode) {
725
+ if (!argsNode)
726
+ return 0;
727
+ let count = 0;
728
+ for (const child of argsNode.children) {
729
+ if (child.type !== "(" && child.type !== ")" && child.type !== ",") {
730
+ count++;
731
+ }
732
+ }
733
+ return count;
734
+ }
735
+ /**
736
+ * Check if an identifier node is an unpacking target in an assignment.
737
+ * Handles: a, b = func() / [x, y] = items / (a, b) = items
738
+ * Also handles nested unpacking in for loops: for (a, b) in items
739
+ *
740
+ * @param node - The identifier node to check
741
+ * @returns true if the identifier is an unpacking target (definition, not usage)
742
+ */
743
+ function isUnpackingTarget(node) {
744
+ let current = node.parent;
745
+ while (current) {
746
+ // If we hit a tuple/list that is the left side of an assignment, it's an unpacking target
747
+ if (current.type === "pattern_list" || current.type === "tuple_pattern" || current.type === "list_pattern") {
748
+ const grandparent = current.parent;
749
+ if (grandparent) {
750
+ if (grandparent.type === "assignment" && grandparent.childForFieldName("left")?.id === current.id)
751
+ return true;
752
+ if (grandparent.type === "for_statement" && grandparent.childForFieldName("left")?.id === current.id)
753
+ return true;
754
+ if (grandparent.type === "for_in_clause" && grandparent.childForFieldName("left")?.id === current.id)
755
+ return true;
756
+ }
757
+ }
758
+ // Also check expression_list (Python uses this for tuple unpacking without parens)
759
+ if (current.type === "expression_list") {
760
+ const grandparent = current.parent;
761
+ if (grandparent) {
762
+ if (grandparent.type === "assignment" && grandparent.childForFieldName("left")?.id === current.id)
763
+ return true;
764
+ if (grandparent.type === "for_statement" && grandparent.childForFieldName("left")?.id === current.id)
765
+ return true;
766
+ if (grandparent.type === "for_in_clause" && grandparent.childForFieldName("left")?.id === current.id)
767
+ return true;
768
+ }
769
+ }
770
+ // Don't traverse too far up
771
+ if (current.type === "assignment" || current.type === "for_statement" || current.type === "for_in_clause" ||
772
+ current.type === "function_definition" || current.type === "class_definition" || current.type === "module") {
773
+ break;
774
+ }
775
+ current = current.parent;
776
+ }
777
+ return false;
778
+ }
779
+ /**
780
+ * Get decorators applied to a function or class definition.
781
+ * Looks for a decorated_definition parent node and extracts all decorator children.
782
+ *
783
+ * @param node - The function or class definition node
784
+ * @param code - The source code string
785
+ * @returns Array of decorator strings (including the @ symbol)
786
+ */
787
+ function getDecorators(node, code) {
788
+ const decorators = [];
789
+ const parent = node.parent;
790
+ if (parent?.type === "decorated_definition") {
791
+ for (const child of parent.children) {
792
+ if (child.type === "decorator") {
793
+ decorators.push(getText(child, code));
794
+ }
795
+ }
796
+ }
797
+ return decorators;
798
+ }
799
+ //# sourceMappingURL=python.js.map