driftdetect-core 0.4.1 → 0.4.2

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 (237) hide show
  1. package/dist/boundaries/boundary-scanner.d.ts +76 -0
  2. package/dist/boundaries/boundary-scanner.d.ts.map +1 -0
  3. package/dist/boundaries/boundary-scanner.js +801 -0
  4. package/dist/boundaries/boundary-scanner.js.map +1 -0
  5. package/dist/boundaries/data-access-learner.d.ts +126 -0
  6. package/dist/boundaries/data-access-learner.d.ts.map +1 -0
  7. package/dist/boundaries/data-access-learner.js +486 -0
  8. package/dist/boundaries/data-access-learner.js.map +1 -0
  9. package/dist/boundaries/index.d.ts +6 -0
  10. package/dist/boundaries/index.d.ts.map +1 -1
  11. package/dist/boundaries/index.js +6 -0
  12. package/dist/boundaries/index.js.map +1 -1
  13. package/dist/boundaries/security-prioritizer.d.ts +118 -0
  14. package/dist/boundaries/security-prioritizer.d.ts.map +1 -0
  15. package/dist/boundaries/security-prioritizer.js +316 -0
  16. package/dist/boundaries/security-prioritizer.js.map +1 -0
  17. package/dist/call-graph/analysis/coverage-analyzer.d.ts +201 -0
  18. package/dist/call-graph/analysis/coverage-analyzer.d.ts.map +1 -0
  19. package/dist/call-graph/analysis/coverage-analyzer.js +553 -0
  20. package/dist/call-graph/analysis/coverage-analyzer.js.map +1 -0
  21. package/dist/call-graph/analysis/dead-code-detector.d.ts +145 -0
  22. package/dist/call-graph/analysis/dead-code-detector.d.ts.map +1 -0
  23. package/dist/call-graph/analysis/dead-code-detector.js +391 -0
  24. package/dist/call-graph/analysis/dead-code-detector.js.map +1 -0
  25. package/dist/call-graph/analysis/graph-builder.d.ts +142 -0
  26. package/dist/call-graph/analysis/graph-builder.d.ts.map +1 -0
  27. package/dist/call-graph/analysis/graph-builder.js +624 -0
  28. package/dist/call-graph/analysis/graph-builder.js.map +1 -0
  29. package/dist/call-graph/analysis/impact-analyzer.d.ts +150 -0
  30. package/dist/call-graph/analysis/impact-analyzer.d.ts.map +1 -0
  31. package/dist/call-graph/analysis/impact-analyzer.js +329 -0
  32. package/dist/call-graph/analysis/impact-analyzer.js.map +1 -0
  33. package/dist/call-graph/analysis/index.d.ts +11 -0
  34. package/dist/call-graph/analysis/index.d.ts.map +1 -0
  35. package/dist/call-graph/analysis/index.js +9 -0
  36. package/dist/call-graph/analysis/index.js.map +1 -0
  37. package/dist/call-graph/analysis/path-finder.d.ts +117 -0
  38. package/dist/call-graph/analysis/path-finder.d.ts.map +1 -0
  39. package/dist/call-graph/analysis/path-finder.js +360 -0
  40. package/dist/call-graph/analysis/path-finder.js.map +1 -0
  41. package/dist/call-graph/analysis/reachability.d.ts +56 -0
  42. package/dist/call-graph/analysis/reachability.d.ts.map +1 -0
  43. package/dist/call-graph/analysis/reachability.js +357 -0
  44. package/dist/call-graph/analysis/reachability.js.map +1 -0
  45. package/dist/call-graph/demo.d.ts +11 -0
  46. package/dist/call-graph/demo.d.ts.map +1 -0
  47. package/dist/call-graph/demo.js +339 -0
  48. package/dist/call-graph/demo.js.map +1 -0
  49. package/dist/call-graph/enrichment/enrichment-engine.d.ts +126 -0
  50. package/dist/call-graph/enrichment/enrichment-engine.d.ts.map +1 -0
  51. package/dist/call-graph/enrichment/enrichment-engine.js +760 -0
  52. package/dist/call-graph/enrichment/enrichment-engine.js.map +1 -0
  53. package/dist/call-graph/enrichment/impact-scorer.d.ts +59 -0
  54. package/dist/call-graph/enrichment/impact-scorer.d.ts.map +1 -0
  55. package/dist/call-graph/enrichment/impact-scorer.js +328 -0
  56. package/dist/call-graph/enrichment/impact-scorer.js.map +1 -0
  57. package/dist/call-graph/enrichment/index.d.ts +12 -0
  58. package/dist/call-graph/enrichment/index.d.ts.map +1 -0
  59. package/dist/call-graph/enrichment/index.js +15 -0
  60. package/dist/call-graph/enrichment/index.js.map +1 -0
  61. package/dist/call-graph/enrichment/remediation-generator.d.ts +41 -0
  62. package/dist/call-graph/enrichment/remediation-generator.d.ts.map +1 -0
  63. package/dist/call-graph/enrichment/remediation-generator.js +609 -0
  64. package/dist/call-graph/enrichment/remediation-generator.js.map +1 -0
  65. package/dist/call-graph/enrichment/sensitivity-classifier.d.ts +71 -0
  66. package/dist/call-graph/enrichment/sensitivity-classifier.d.ts.map +1 -0
  67. package/dist/call-graph/enrichment/sensitivity-classifier.js +454 -0
  68. package/dist/call-graph/enrichment/sensitivity-classifier.js.map +1 -0
  69. package/dist/call-graph/enrichment/types.d.ts +402 -0
  70. package/dist/call-graph/enrichment/types.d.ts.map +1 -0
  71. package/dist/call-graph/enrichment/types.js +9 -0
  72. package/dist/call-graph/enrichment/types.js.map +1 -0
  73. package/dist/call-graph/extractors/base-extractor.d.ts +112 -0
  74. package/dist/call-graph/extractors/base-extractor.d.ts.map +1 -0
  75. package/dist/call-graph/extractors/base-extractor.js +140 -0
  76. package/dist/call-graph/extractors/base-extractor.js.map +1 -0
  77. package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +76 -0
  78. package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -0
  79. package/dist/call-graph/extractors/csharp-data-access-extractor.js +387 -0
  80. package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -0
  81. package/dist/call-graph/extractors/csharp-extractor.d.ts +87 -0
  82. package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -0
  83. package/dist/call-graph/extractors/csharp-extractor.js +470 -0
  84. package/dist/call-graph/extractors/csharp-extractor.js.map +1 -0
  85. package/dist/call-graph/extractors/data-access-extractor.d.ts +76 -0
  86. package/dist/call-graph/extractors/data-access-extractor.d.ts.map +1 -0
  87. package/dist/call-graph/extractors/data-access-extractor.js +234 -0
  88. package/dist/call-graph/extractors/data-access-extractor.js.map +1 -0
  89. package/dist/call-graph/extractors/index.d.ts +26 -0
  90. package/dist/call-graph/extractors/index.d.ts.map +1 -0
  91. package/dist/call-graph/extractors/index.js +36 -0
  92. package/dist/call-graph/extractors/index.js.map +1 -0
  93. package/dist/call-graph/extractors/java-data-access-extractor.d.ts +101 -0
  94. package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -0
  95. package/dist/call-graph/extractors/java-data-access-extractor.js +611 -0
  96. package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -0
  97. package/dist/call-graph/extractors/java-extractor.d.ts +87 -0
  98. package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -0
  99. package/dist/call-graph/extractors/java-extractor.js +510 -0
  100. package/dist/call-graph/extractors/java-extractor.js.map +1 -0
  101. package/dist/call-graph/extractors/php-data-access-extractor.d.ts +93 -0
  102. package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -0
  103. package/dist/call-graph/extractors/php-data-access-extractor.js +589 -0
  104. package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -0
  105. package/dist/call-graph/extractors/php-extractor.d.ts +104 -0
  106. package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -0
  107. package/dist/call-graph/extractors/php-extractor.js +619 -0
  108. package/dist/call-graph/extractors/php-extractor.js.map +1 -0
  109. package/dist/call-graph/extractors/python-data-access-extractor.d.ts +90 -0
  110. package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -0
  111. package/dist/call-graph/extractors/python-data-access-extractor.js +537 -0
  112. package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -0
  113. package/dist/call-graph/extractors/python-extractor.d.ts +98 -0
  114. package/dist/call-graph/extractors/python-extractor.d.ts.map +1 -0
  115. package/dist/call-graph/extractors/python-extractor.js +681 -0
  116. package/dist/call-graph/extractors/python-extractor.js.map +1 -0
  117. package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +91 -0
  118. package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -0
  119. package/dist/call-graph/extractors/semantic-data-access-scanner.js +498 -0
  120. package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -0
  121. package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +122 -0
  122. package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -0
  123. package/dist/call-graph/extractors/typescript-data-access-extractor.js +788 -0
  124. package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -0
  125. package/dist/call-graph/extractors/typescript-extractor.d.ts +145 -0
  126. package/dist/call-graph/extractors/typescript-extractor.d.ts.map +1 -0
  127. package/dist/call-graph/extractors/typescript-extractor.js +904 -0
  128. package/dist/call-graph/extractors/typescript-extractor.js.map +1 -0
  129. package/dist/call-graph/index.d.ts +127 -0
  130. package/dist/call-graph/index.d.ts.map +1 -0
  131. package/dist/call-graph/index.js +247 -0
  132. package/dist/call-graph/index.js.map +1 -0
  133. package/dist/call-graph/store/call-graph-store.d.ts +70 -0
  134. package/dist/call-graph/store/call-graph-store.d.ts.map +1 -0
  135. package/dist/call-graph/store/call-graph-store.js +210 -0
  136. package/dist/call-graph/store/call-graph-store.js.map +1 -0
  137. package/dist/call-graph/store/index.d.ts +7 -0
  138. package/dist/call-graph/store/index.d.ts.map +1 -0
  139. package/dist/call-graph/store/index.js +7 -0
  140. package/dist/call-graph/store/index.js.map +1 -0
  141. package/dist/call-graph/types.d.ts +376 -0
  142. package/dist/call-graph/types.d.ts.map +1 -0
  143. package/dist/call-graph/types.js +8 -0
  144. package/dist/call-graph/types.js.map +1 -0
  145. package/dist/index.d.ts +8 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +12 -0
  148. package/dist/index.js.map +1 -1
  149. package/dist/lake/callgraph-shard-store.d.ts +168 -0
  150. package/dist/lake/callgraph-shard-store.d.ts.map +1 -0
  151. package/dist/lake/callgraph-shard-store.js +466 -0
  152. package/dist/lake/callgraph-shard-store.js.map +1 -0
  153. package/dist/lake/examples-store.d.ts +127 -0
  154. package/dist/lake/examples-store.d.ts.map +1 -0
  155. package/dist/lake/examples-store.js +389 -0
  156. package/dist/lake/examples-store.js.map +1 -0
  157. package/dist/lake/index-store.d.ts +82 -0
  158. package/dist/lake/index-store.d.ts.map +1 -0
  159. package/dist/lake/index-store.js +359 -0
  160. package/dist/lake/index-store.js.map +1 -0
  161. package/dist/lake/index.d.ts +93 -0
  162. package/dist/lake/index.d.ts.map +1 -0
  163. package/dist/lake/index.js +138 -0
  164. package/dist/lake/index.js.map +1 -0
  165. package/dist/lake/lake.bak/index-store.d.ts +82 -0
  166. package/dist/lake/lake.bak/index-store.d.ts.map +1 -0
  167. package/dist/lake/lake.bak/index-store.js +357 -0
  168. package/dist/lake/lake.bak/index-store.js.map +1 -0
  169. package/dist/lake/lake.bak/index.d.ts +81 -0
  170. package/dist/lake/lake.bak/index.d.ts.map +1 -0
  171. package/dist/lake/lake.bak/index.js +114 -0
  172. package/dist/lake/lake.bak/index.js.map +1 -0
  173. package/dist/lake/lake.bak/manifest-store.d.ts +51 -0
  174. package/dist/lake/lake.bak/manifest-store.d.ts.map +1 -0
  175. package/dist/lake/lake.bak/manifest-store.js +347 -0
  176. package/dist/lake/lake.bak/manifest-store.js.map +1 -0
  177. package/dist/lake/lake.bak/query-engine.d.ts +112 -0
  178. package/dist/lake/lake.bak/query-engine.d.ts.map +1 -0
  179. package/dist/lake/lake.bak/query-engine.js +370 -0
  180. package/dist/lake/lake.bak/query-engine.js.map +1 -0
  181. package/dist/lake/lake.bak/types.d.ts +428 -0
  182. package/dist/lake/lake.bak/types.d.ts.map +1 -0
  183. package/dist/lake/lake.bak/types.js +46 -0
  184. package/dist/lake/lake.bak/types.js.map +1 -0
  185. package/dist/lake/lake.bak/view-materializer.d.ts +70 -0
  186. package/dist/lake/lake.bak/view-materializer.d.ts.map +1 -0
  187. package/dist/lake/lake.bak/view-materializer.js +314 -0
  188. package/dist/lake/lake.bak/view-materializer.js.map +1 -0
  189. package/dist/lake/lake.bak/view-store.d.ts +57 -0
  190. package/dist/lake/lake.bak/view-store.d.ts.map +1 -0
  191. package/dist/lake/lake.bak/view-store.js +348 -0
  192. package/dist/lake/lake.bak/view-store.js.map +1 -0
  193. package/dist/lake/manifest-store.d.ts +51 -0
  194. package/dist/lake/manifest-store.d.ts.map +1 -0
  195. package/dist/lake/manifest-store.js +348 -0
  196. package/dist/lake/manifest-store.js.map +1 -0
  197. package/dist/lake/pattern-shard-store.d.ts +87 -0
  198. package/dist/lake/pattern-shard-store.d.ts.map +1 -0
  199. package/dist/lake/pattern-shard-store.js +347 -0
  200. package/dist/lake/pattern-shard-store.js.map +1 -0
  201. package/dist/lake/query-engine.d.ts +124 -0
  202. package/dist/lake/query-engine.d.ts.map +1 -0
  203. package/dist/lake/query-engine.js +453 -0
  204. package/dist/lake/query-engine.js.map +1 -0
  205. package/dist/lake/security-shard-store.d.ts +156 -0
  206. package/dist/lake/security-shard-store.d.ts.map +1 -0
  207. package/dist/lake/security-shard-store.js +498 -0
  208. package/dist/lake/security-shard-store.js.map +1 -0
  209. package/dist/lake/types.d.ts +428 -0
  210. package/dist/lake/types.d.ts.map +1 -0
  211. package/dist/lake/types.js +46 -0
  212. package/dist/lake/types.js.map +1 -0
  213. package/dist/lake/view-materializer.d.ts +70 -0
  214. package/dist/lake/view-materializer.d.ts.map +1 -0
  215. package/dist/lake/view-materializer.js +314 -0
  216. package/dist/lake/view-materializer.js.map +1 -0
  217. package/dist/lake/view-store.d.ts +57 -0
  218. package/dist/lake/view-store.d.ts.map +1 -0
  219. package/dist/lake/view-store.js +348 -0
  220. package/dist/lake/view-store.js.map +1 -0
  221. package/dist/parsers/tree-sitter/index.d.ts +1 -0
  222. package/dist/parsers/tree-sitter/index.d.ts.map +1 -1
  223. package/dist/parsers/tree-sitter/index.js +4 -0
  224. package/dist/parsers/tree-sitter/index.js.map +1 -1
  225. package/dist/parsers/tree-sitter/typescript-loader.d.ts +58 -0
  226. package/dist/parsers/tree-sitter/typescript-loader.d.ts.map +1 -0
  227. package/dist/parsers/tree-sitter/typescript-loader.js +250 -0
  228. package/dist/parsers/tree-sitter/typescript-loader.js.map +1 -0
  229. package/dist/store/project-config.d.ts +154 -0
  230. package/dist/store/project-config.d.ts.map +1 -0
  231. package/dist/store/project-config.js +235 -0
  232. package/dist/store/project-config.js.map +1 -0
  233. package/dist/store/project-registry.d.ts +241 -0
  234. package/dist/store/project-registry.d.ts.map +1 -0
  235. package/dist/store/project-registry.js +557 -0
  236. package/dist/store/project-registry.js.map +1 -0
  237. package/package.json +4 -2
@@ -0,0 +1,681 @@
1
+ /**
2
+ * Python Call Graph Extractor
3
+ *
4
+ * Extracts functions, calls, imports, and exports from Python
5
+ * using tree-sitter for AST parsing.
6
+ *
7
+ * Handles:
8
+ * - Function definitions (def, async def)
9
+ * - Nested functions (closures)
10
+ * - Class methods and static methods
11
+ * - Module-level calls (top-level code)
12
+ * - Callback patterns (functions passed as arguments)
13
+ * - Dependency injection (FastAPI Depends, etc.)
14
+ * - Lambda expressions
15
+ */
16
+ import { BaseCallGraphExtractor } from './base-extractor.js';
17
+ import { isTreeSitterAvailable, createPythonParser } from '../../parsers/tree-sitter/loader.js';
18
+ /**
19
+ * Python call graph extractor using tree-sitter
20
+ */
21
+ export class PythonCallGraphExtractor extends BaseCallGraphExtractor {
22
+ language = 'python';
23
+ extensions = ['.py', '.pyw', '.pyi'];
24
+ parser = null;
25
+ /**
26
+ * Check if tree-sitter is available
27
+ */
28
+ static isAvailable() {
29
+ return isTreeSitterAvailable();
30
+ }
31
+ /**
32
+ * Extract call graph information from Python source
33
+ */
34
+ extract(source, filePath) {
35
+ const result = this.createEmptyResult(filePath);
36
+ if (!isTreeSitterAvailable()) {
37
+ result.errors.push('Tree-sitter not available for Python parsing');
38
+ return result;
39
+ }
40
+ try {
41
+ if (!this.parser) {
42
+ this.parser = createPythonParser();
43
+ }
44
+ const tree = this.parser.parse(source);
45
+ this.visitNode(tree.rootNode, result, source, null, null);
46
+ // Extract module-level calls (top-level code not inside any function)
47
+ this.extractModuleLevelCalls(tree.rootNode, result, source);
48
+ }
49
+ catch (error) {
50
+ result.errors.push(error instanceof Error ? error.message : 'Unknown parse error');
51
+ }
52
+ return result;
53
+ }
54
+ /**
55
+ * Extract calls at module level (not inside any function or class)
56
+ * This catches patterns like: app = FastAPI(), if __name__ == "__main__": main()
57
+ */
58
+ extractModuleLevelCalls(rootNode, result, source) {
59
+ const moduleCalls = [];
60
+ for (const child of rootNode.children) {
61
+ // Skip function and class definitions
62
+ if (child.type === 'function_definition' || child.type === 'class_definition') {
63
+ continue;
64
+ }
65
+ // Skip import statements
66
+ if (child.type === 'import_statement' || child.type === 'import_from_statement') {
67
+ continue;
68
+ }
69
+ // Extract calls from expression statements, assignments, if statements, etc.
70
+ this.extractCallsRecursive(child, moduleCalls, source);
71
+ }
72
+ // Add module-level calls to result
73
+ result.calls.push(...moduleCalls);
74
+ // If there are module-level calls, create a synthetic module function
75
+ if (moduleCalls.length > 0) {
76
+ const moduleFunc = this.createFunction({
77
+ name: '__module__',
78
+ startLine: 1,
79
+ endLine: rootNode.endPosition.row + 1,
80
+ startColumn: 0,
81
+ endColumn: 0,
82
+ parameters: [],
83
+ isMethod: false,
84
+ isStatic: false,
85
+ isExported: false,
86
+ isConstructor: false,
87
+ isAsync: false,
88
+ decorators: [],
89
+ bodyStartLine: 1,
90
+ bodyEndLine: rootNode.endPosition.row + 1,
91
+ });
92
+ result.functions.push(moduleFunc);
93
+ }
94
+ }
95
+ /**
96
+ * Recursively extract calls from a node (for module-level code)
97
+ */
98
+ extractCallsRecursive(node, calls, source) {
99
+ if (node.type === 'call') {
100
+ const call = this.extractCallExpressionToArray(node, source);
101
+ if (call) {
102
+ calls.push(...call);
103
+ }
104
+ }
105
+ // Don't recurse into function or class definitions
106
+ if (node.type === 'function_definition' || node.type === 'class_definition') {
107
+ return;
108
+ }
109
+ for (const child of node.children) {
110
+ this.extractCallsRecursive(child, calls, source);
111
+ }
112
+ }
113
+ /**
114
+ * Visit a tree-sitter node and extract information
115
+ * @param parentFunction - The name of the containing function (for nested functions)
116
+ */
117
+ visitNode(node, result, source, currentClass, parentFunction) {
118
+ switch (node.type) {
119
+ case 'function_definition':
120
+ this.extractFunctionDefinition(node, result, source, currentClass, parentFunction);
121
+ break;
122
+ case 'class_definition':
123
+ this.extractClassDefinition(node, result, source);
124
+ break;
125
+ case 'import_statement':
126
+ this.extractImportStatement(node, result);
127
+ break;
128
+ case 'import_from_statement':
129
+ this.extractImportFromStatement(node, result);
130
+ break;
131
+ case 'call':
132
+ this.extractCallExpression(node, result, source);
133
+ break;
134
+ default:
135
+ // Recurse into children
136
+ for (const child of node.children) {
137
+ this.visitNode(child, result, source, currentClass, parentFunction);
138
+ }
139
+ }
140
+ }
141
+ /**
142
+ * Extract a function definition
143
+ */
144
+ extractFunctionDefinition(node, result, source, currentClass, parentFunction) {
145
+ const nameNode = node.childForFieldName('name');
146
+ if (!nameNode)
147
+ return;
148
+ const name = nameNode.text;
149
+ const isMethod = currentClass !== null;
150
+ const isConstructor = name === '__init__';
151
+ // Get decorators
152
+ const decorators = [];
153
+ let prevSibling = node.previousNamedSibling;
154
+ while (prevSibling && prevSibling.type === 'decorator') {
155
+ decorators.unshift(prevSibling.text);
156
+ prevSibling = prevSibling.previousNamedSibling;
157
+ }
158
+ // Check for async
159
+ const isAsync = node.children.some(c => c.type === 'async');
160
+ // Check for staticmethod/classmethod decorators
161
+ const isStatic = decorators.some(d => d.includes('@staticmethod') || d.includes('@classmethod'));
162
+ // Extract parameters
163
+ const parametersNode = node.childForFieldName('parameters');
164
+ const parameters = parametersNode ? this.extractParameters(parametersNode) : [];
165
+ // Get return type
166
+ const returnTypeNode = node.childForFieldName('return_type');
167
+ const returnType = returnTypeNode?.text;
168
+ // Get body for line range
169
+ const bodyNode = node.childForFieldName('body');
170
+ // Build qualified name including parent function for nested functions
171
+ let qualifiedName = name;
172
+ if (parentFunction) {
173
+ qualifiedName = `${parentFunction}.${name}`;
174
+ }
175
+ else if (currentClass) {
176
+ qualifiedName = `${currentClass}.${name}`;
177
+ }
178
+ const func = this.createFunction({
179
+ name,
180
+ qualifiedName,
181
+ startLine: node.startPosition.row + 1,
182
+ endLine: node.endPosition.row + 1,
183
+ startColumn: node.startPosition.column,
184
+ endColumn: node.endPosition.column,
185
+ parameters,
186
+ returnType,
187
+ isMethod,
188
+ isStatic,
189
+ isExported: parentFunction ? false : (!name.startsWith('_') || name.startsWith('__') && name.endsWith('__')),
190
+ isConstructor,
191
+ isAsync,
192
+ className: currentClass ?? undefined,
193
+ decorators,
194
+ bodyStartLine: bodyNode ? bodyNode.startPosition.row + 1 : node.startPosition.row + 1,
195
+ bodyEndLine: bodyNode ? bodyNode.endPosition.row + 1 : node.endPosition.row + 1,
196
+ });
197
+ result.functions.push(func);
198
+ // Extract calls from parameter default values (for DI patterns like Depends(get_current_user))
199
+ if (parametersNode) {
200
+ this.extractCallsFromParameters(parametersNode, result, source);
201
+ }
202
+ // Extract calls from function body and nested functions
203
+ if (bodyNode) {
204
+ this.extractCallsFromBody(bodyNode, result, source);
205
+ // Extract nested functions
206
+ this.extractNestedFunctions(bodyNode, result, source, currentClass, qualifiedName);
207
+ }
208
+ }
209
+ /**
210
+ * Extract nested functions from a function body
211
+ */
212
+ extractNestedFunctions(bodyNode, result, source, currentClass, parentFunctionName) {
213
+ for (const child of bodyNode.children) {
214
+ if (child.type === 'function_definition') {
215
+ this.extractFunctionDefinition(child, result, source, currentClass, parentFunctionName);
216
+ }
217
+ // Also check for nested functions in if/else/for/while/with blocks
218
+ else if (['if_statement', 'for_statement', 'while_statement', 'with_statement', 'try_statement'].includes(child.type)) {
219
+ this.extractNestedFunctionsRecursive(child, result, source, currentClass, parentFunctionName);
220
+ }
221
+ }
222
+ }
223
+ /**
224
+ * Recursively extract nested functions from control flow blocks
225
+ */
226
+ extractNestedFunctionsRecursive(node, result, source, currentClass, parentFunctionName) {
227
+ for (const child of node.children) {
228
+ if (child.type === 'function_definition') {
229
+ this.extractFunctionDefinition(child, result, source, currentClass, parentFunctionName);
230
+ }
231
+ else if (child.type === 'block') {
232
+ this.extractNestedFunctions(child, result, source, currentClass, parentFunctionName);
233
+ }
234
+ else {
235
+ this.extractNestedFunctionsRecursive(child, result, source, currentClass, parentFunctionName);
236
+ }
237
+ }
238
+ }
239
+ /**
240
+ * Extract calls from parameter default values (for DI patterns)
241
+ */
242
+ extractCallsFromParameters(node, result, source) {
243
+ const visit = (n) => {
244
+ if (n.type === 'call') {
245
+ this.extractCallExpression(n, result, source);
246
+ }
247
+ for (const child of n.children) {
248
+ visit(child);
249
+ }
250
+ };
251
+ for (const child of node.children) {
252
+ // Look for default_parameter and typed_default_parameter
253
+ if (child.type === 'default_parameter' || child.type === 'typed_default_parameter') {
254
+ const valueNode = child.childForFieldName('value');
255
+ if (valueNode) {
256
+ visit(valueNode);
257
+ }
258
+ }
259
+ }
260
+ }
261
+ /**
262
+ * Extract a call expression and return as array (for module-level calls)
263
+ */
264
+ extractCallExpressionToArray(node, _source) {
265
+ const funcNode = node.childForFieldName('function');
266
+ if (!funcNode)
267
+ return null;
268
+ const calls = [];
269
+ let calleeName;
270
+ let receiver;
271
+ let fullExpression = funcNode.text;
272
+ let isConstructorCall = false;
273
+ // Attribute access: obj.method() or module.func()
274
+ if (funcNode.type === 'attribute') {
275
+ const objectNode = funcNode.childForFieldName('object');
276
+ const attrNode = funcNode.childForFieldName('attribute');
277
+ if (objectNode && attrNode) {
278
+ receiver = objectNode.text;
279
+ calleeName = attrNode.text;
280
+ }
281
+ else {
282
+ calleeName = funcNode.text;
283
+ }
284
+ }
285
+ // Direct call: func()
286
+ else if (funcNode.type === 'identifier') {
287
+ calleeName = funcNode.text;
288
+ isConstructorCall = /^[A-Z]/.test(calleeName);
289
+ }
290
+ else {
291
+ calleeName = funcNode.text;
292
+ }
293
+ fullExpression = node.text;
294
+ const argsNode = node.childForFieldName('arguments');
295
+ let argumentCount = 0;
296
+ const functionRefs = [];
297
+ if (argsNode) {
298
+ for (const child of argsNode.children) {
299
+ if (child.type !== '(' && child.type !== ')' && child.type !== ',') {
300
+ argumentCount++;
301
+ if (child.type === 'identifier') {
302
+ const argName = child.text;
303
+ if (!['True', 'False', 'None', 'self', 'cls'].includes(argName)) {
304
+ functionRefs.push(argName);
305
+ }
306
+ }
307
+ else if (child.type === 'keyword_argument') {
308
+ const valueNode = child.childForFieldName('value');
309
+ if (valueNode?.type === 'identifier') {
310
+ const argName = valueNode.text;
311
+ if (!['True', 'False', 'None', 'self', 'cls'].includes(argName)) {
312
+ functionRefs.push(argName);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ calls.push(this.createCall({
320
+ calleeName,
321
+ receiver,
322
+ fullExpression,
323
+ line: node.startPosition.row + 1,
324
+ column: node.startPosition.column,
325
+ argumentCount,
326
+ isMethodCall: !!receiver,
327
+ isConstructorCall,
328
+ }));
329
+ // For DI patterns, also create implicit calls
330
+ if (calleeName === 'Depends' || calleeName === 'inject' || calleeName === 'Inject') {
331
+ for (const funcRef of functionRefs) {
332
+ calls.push(this.createCall({
333
+ calleeName: funcRef,
334
+ receiver: undefined,
335
+ fullExpression: `${calleeName}(${funcRef})`,
336
+ line: node.startPosition.row + 1,
337
+ column: node.startPosition.column,
338
+ argumentCount: 0,
339
+ isMethodCall: false,
340
+ isConstructorCall: /^[A-Z]/.test(funcRef),
341
+ }));
342
+ }
343
+ }
344
+ return calls;
345
+ }
346
+ /**
347
+ * Extract calls from a function body
348
+ * Also extracts callback patterns where functions are passed as arguments
349
+ */
350
+ extractCallsFromBody(node, result, source) {
351
+ const visit = (n) => {
352
+ if (n.type === 'call') {
353
+ this.extractCallExpression(n, result, source);
354
+ // Also extract callback references
355
+ this.extractCallbackReferences(n, result, source);
356
+ }
357
+ // Don't recurse into nested function definitions (they're handled separately)
358
+ if (n.type !== 'function_definition' && n.type !== 'class_definition') {
359
+ for (const child of n.children) {
360
+ visit(child);
361
+ }
362
+ }
363
+ };
364
+ for (const child of node.children) {
365
+ visit(child);
366
+ }
367
+ }
368
+ /**
369
+ * Extract callback references from function call arguments
370
+ * Handles patterns like: map(process_item, items), threading.Thread(target=my_func)
371
+ */
372
+ extractCallbackReferences(node, result, _source) {
373
+ const argsNode = node.childForFieldName('arguments');
374
+ if (!argsNode)
375
+ return;
376
+ for (const child of argsNode.children) {
377
+ // Direct function reference passed as argument
378
+ if (child.type === 'identifier') {
379
+ const argName = child.text;
380
+ // Skip common non-function arguments
381
+ if (!['True', 'False', 'None', 'self', 'cls'].includes(argName) &&
382
+ !/^[A-Z]/.test(argName)) { // Skip class names (handled as constructor calls)
383
+ result.calls.push(this.createCall({
384
+ calleeName: argName,
385
+ receiver: undefined,
386
+ fullExpression: argName,
387
+ line: child.startPosition.row + 1,
388
+ column: child.startPosition.column,
389
+ argumentCount: 0,
390
+ isMethodCall: false,
391
+ isConstructorCall: false,
392
+ }));
393
+ }
394
+ }
395
+ // Keyword argument with function reference: target=my_func
396
+ else if (child.type === 'keyword_argument') {
397
+ const keyNode = child.childForFieldName('name');
398
+ const valueNode = child.childForFieldName('value');
399
+ // Common callback keyword arguments
400
+ const callbackKeywords = ['target', 'callback', 'func', 'function', 'handler', 'key', 'default'];
401
+ if (keyNode && valueNode?.type === 'identifier' &&
402
+ callbackKeywords.includes(keyNode.text)) {
403
+ const argName = valueNode.text;
404
+ if (!['True', 'False', 'None', 'self', 'cls'].includes(argName)) {
405
+ result.calls.push(this.createCall({
406
+ calleeName: argName,
407
+ receiver: undefined,
408
+ fullExpression: `${keyNode.text}=${argName}`,
409
+ line: valueNode.startPosition.row + 1,
410
+ column: valueNode.startPosition.column,
411
+ argumentCount: 0,
412
+ isMethodCall: false,
413
+ isConstructorCall: false,
414
+ }));
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+ /**
421
+ * Extract a call expression
422
+ */
423
+ extractCallExpression(node, result, _source) {
424
+ const funcNode = node.childForFieldName('function');
425
+ if (!funcNode)
426
+ return;
427
+ let calleeName;
428
+ let receiver;
429
+ let fullExpression = funcNode.text;
430
+ let isConstructorCall = false;
431
+ // Attribute access: obj.method() or module.func()
432
+ if (funcNode.type === 'attribute') {
433
+ const objectNode = funcNode.childForFieldName('object');
434
+ const attrNode = funcNode.childForFieldName('attribute');
435
+ if (objectNode && attrNode) {
436
+ receiver = objectNode.text;
437
+ calleeName = attrNode.text;
438
+ }
439
+ else {
440
+ calleeName = funcNode.text;
441
+ }
442
+ }
443
+ // Direct call: func()
444
+ else if (funcNode.type === 'identifier') {
445
+ calleeName = funcNode.text;
446
+ // Check if it looks like a class instantiation (PascalCase)
447
+ isConstructorCall = /^[A-Z]/.test(calleeName);
448
+ }
449
+ // Other (subscript, call result, etc.)
450
+ else {
451
+ calleeName = funcNode.text;
452
+ }
453
+ // Get full expression including arguments for DI pattern detection
454
+ fullExpression = node.text;
455
+ // Count arguments and extract function references
456
+ const argsNode = node.childForFieldName('arguments');
457
+ let argumentCount = 0;
458
+ const functionRefs = [];
459
+ if (argsNode) {
460
+ for (const child of argsNode.children) {
461
+ if (child.type !== '(' && child.type !== ')' && child.type !== ',') {
462
+ argumentCount++;
463
+ // Check if argument is a function reference (identifier that's not a keyword)
464
+ if (child.type === 'identifier') {
465
+ const argName = child.text;
466
+ // Skip common non-function arguments
467
+ if (!['True', 'False', 'None', 'self', 'cls'].includes(argName)) {
468
+ functionRefs.push(argName);
469
+ }
470
+ }
471
+ // Check for keyword argument with function reference: Depends(get_current_user)
472
+ else if (child.type === 'keyword_argument') {
473
+ const valueNode = child.childForFieldName('value');
474
+ if (valueNode?.type === 'identifier') {
475
+ const argName = valueNode.text;
476
+ if (!['True', 'False', 'None', 'self', 'cls'].includes(argName)) {
477
+ functionRefs.push(argName);
478
+ }
479
+ }
480
+ }
481
+ }
482
+ }
483
+ }
484
+ const call = this.createCall({
485
+ calleeName,
486
+ receiver,
487
+ fullExpression,
488
+ line: node.startPosition.row + 1,
489
+ column: node.startPosition.column,
490
+ argumentCount,
491
+ isMethodCall: !!receiver,
492
+ isConstructorCall,
493
+ });
494
+ result.calls.push(call);
495
+ // For DI patterns like Depends(func), also create implicit calls to the referenced functions
496
+ // This helps the call graph understand that the route handler "calls" the dependency
497
+ if (calleeName === 'Depends' || calleeName === 'inject' || calleeName === 'Inject') {
498
+ for (const funcRef of functionRefs) {
499
+ const implicitCall = this.createCall({
500
+ calleeName: funcRef,
501
+ receiver: undefined,
502
+ fullExpression: `${calleeName}(${funcRef})`,
503
+ line: node.startPosition.row + 1,
504
+ column: node.startPosition.column,
505
+ argumentCount: 0,
506
+ isMethodCall: false,
507
+ isConstructorCall: /^[A-Z]/.test(funcRef),
508
+ });
509
+ result.calls.push(implicitCall);
510
+ }
511
+ }
512
+ }
513
+ /**
514
+ * Extract a class definition
515
+ */
516
+ extractClassDefinition(node, result, source) {
517
+ const nameNode = node.childForFieldName('name');
518
+ if (!nameNode)
519
+ return;
520
+ const name = nameNode.text;
521
+ // Get base classes
522
+ const baseClasses = [];
523
+ const superclassNode = node.childForFieldName('superclasses');
524
+ if (superclassNode) {
525
+ for (const child of superclassNode.children) {
526
+ if (child.type === 'identifier' || child.type === 'attribute') {
527
+ baseClasses.push(child.text);
528
+ }
529
+ }
530
+ }
531
+ // Get methods
532
+ const methods = [];
533
+ const bodyNode = node.childForFieldName('body');
534
+ if (bodyNode) {
535
+ for (const child of bodyNode.children) {
536
+ if (child.type === 'function_definition') {
537
+ const methodNameNode = child.childForFieldName('name');
538
+ if (methodNameNode) {
539
+ methods.push(methodNameNode.text);
540
+ }
541
+ }
542
+ }
543
+ }
544
+ const classInfo = this.createClass({
545
+ name,
546
+ startLine: node.startPosition.row + 1,
547
+ endLine: node.endPosition.row + 1,
548
+ baseClasses,
549
+ methods,
550
+ isExported: !name.startsWith('_'),
551
+ });
552
+ result.classes.push(classInfo);
553
+ // Visit class body with class context
554
+ if (bodyNode) {
555
+ for (const child of bodyNode.children) {
556
+ this.visitNode(child, result, source, name, null);
557
+ }
558
+ }
559
+ }
560
+ /**
561
+ * Extract an import statement (import foo, import foo.bar)
562
+ */
563
+ extractImportStatement(node, result) {
564
+ for (const child of node.children) {
565
+ if (child.type === 'dotted_name') {
566
+ const moduleName = child.text;
567
+ result.imports.push(this.createImport({
568
+ source: moduleName,
569
+ names: [{
570
+ imported: moduleName,
571
+ local: moduleName.split('.').pop() ?? moduleName,
572
+ }],
573
+ line: node.startPosition.row + 1,
574
+ }));
575
+ }
576
+ else if (child.type === 'aliased_import') {
577
+ const nameNode = child.childForFieldName('name');
578
+ const aliasNode = child.childForFieldName('alias');
579
+ if (nameNode) {
580
+ const moduleName = nameNode.text;
581
+ result.imports.push(this.createImport({
582
+ source: moduleName,
583
+ names: [{
584
+ imported: moduleName,
585
+ local: aliasNode?.text ?? moduleName.split('.').pop() ?? moduleName,
586
+ }],
587
+ line: node.startPosition.row + 1,
588
+ }));
589
+ }
590
+ }
591
+ }
592
+ }
593
+ /**
594
+ * Extract a from...import statement
595
+ */
596
+ extractImportFromStatement(node, result) {
597
+ const moduleNode = node.childForFieldName('module_name');
598
+ const moduleName = moduleNode?.text ?? '';
599
+ const names = [];
600
+ for (const child of node.children) {
601
+ if (child.type === 'dotted_name' && child !== moduleNode) {
602
+ names.push({
603
+ imported: child.text,
604
+ local: child.text,
605
+ isDefault: false,
606
+ isNamespace: false,
607
+ });
608
+ }
609
+ else if (child.type === 'aliased_import') {
610
+ const nameNode = child.childForFieldName('name');
611
+ const aliasNode = child.childForFieldName('alias');
612
+ if (nameNode) {
613
+ names.push({
614
+ imported: nameNode.text,
615
+ local: aliasNode?.text ?? nameNode.text,
616
+ isDefault: false,
617
+ isNamespace: false,
618
+ });
619
+ }
620
+ }
621
+ else if (child.type === 'wildcard_import') {
622
+ names.push({
623
+ imported: '*',
624
+ local: '*',
625
+ isDefault: false,
626
+ isNamespace: true,
627
+ });
628
+ }
629
+ }
630
+ if (names.length > 0) {
631
+ result.imports.push(this.createImport({
632
+ source: moduleName,
633
+ names,
634
+ line: node.startPosition.row + 1,
635
+ }));
636
+ }
637
+ }
638
+ /**
639
+ * Extract parameters from a parameters node
640
+ */
641
+ extractParameters(node) {
642
+ const params = [];
643
+ for (const child of node.children) {
644
+ if (child.type === 'identifier') {
645
+ // Skip 'self' and 'cls' for methods
646
+ if (child.text !== 'self' && child.text !== 'cls') {
647
+ params.push(this.parseParameter(child.text));
648
+ }
649
+ }
650
+ else if (child.type === 'typed_parameter') {
651
+ const nameNode = child.children.find(c => c.type === 'identifier');
652
+ const typeNode = child.childForFieldName('type');
653
+ if (nameNode && nameNode.text !== 'self' && nameNode.text !== 'cls') {
654
+ params.push(this.parseParameter(nameNode.text, typeNode?.text));
655
+ }
656
+ }
657
+ else if (child.type === 'default_parameter') {
658
+ const nameNode = child.childForFieldName('name');
659
+ const typeNode = child.childForFieldName('type');
660
+ if (nameNode && nameNode.text !== 'self' && nameNode.text !== 'cls') {
661
+ params.push(this.parseParameter(nameNode.text, typeNode?.text, true));
662
+ }
663
+ }
664
+ else if (child.type === 'typed_default_parameter') {
665
+ const nameNode = child.childForFieldName('name');
666
+ const typeNode = child.childForFieldName('type');
667
+ if (nameNode && nameNode.text !== 'self' && nameNode.text !== 'cls') {
668
+ params.push(this.parseParameter(nameNode.text, typeNode?.text, true));
669
+ }
670
+ }
671
+ else if (child.type === 'list_splat_pattern' || child.type === 'dictionary_splat_pattern') {
672
+ const nameNode = child.children.find(c => c.type === 'identifier');
673
+ if (nameNode) {
674
+ params.push(this.parseParameter(nameNode.text, undefined, false, true));
675
+ }
676
+ }
677
+ }
678
+ return params;
679
+ }
680
+ }
681
+ //# sourceMappingURL=python-extractor.js.map