@optave/codegraph 3.1.4 → 3.2.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 (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -76,249 +76,262 @@ function extractPhpEnumCases(enumNode) {
76
76
  * Extract symbols from PHP files.
77
77
  */
78
78
  export function extractPHPSymbols(tree, _filePath) {
79
- const definitions = [];
80
- const calls = [];
81
- const imports = [];
82
- const classes = [];
83
- const exports = [];
79
+ const ctx = {
80
+ definitions: [],
81
+ calls: [],
82
+ imports: [],
83
+ classes: [],
84
+ exports: [],
85
+ };
84
86
 
85
- function findPHPParentClass(node) {
86
- let current = node.parent;
87
- while (current) {
88
- if (
89
- current.type === 'class_declaration' ||
90
- current.type === 'trait_declaration' ||
91
- current.type === 'enum_declaration'
92
- ) {
93
- const nameNode = current.childForFieldName('name');
94
- return nameNode ? nameNode.text : null;
95
- }
96
- current = current.parent;
97
- }
98
- return null;
87
+ walkPhpNode(tree.rootNode, ctx);
88
+ return ctx;
89
+ }
90
+
91
+ function walkPhpNode(node, ctx) {
92
+ switch (node.type) {
93
+ case 'function_definition':
94
+ handlePhpFuncDef(node, ctx);
95
+ break;
96
+ case 'class_declaration':
97
+ handlePhpClassDecl(node, ctx);
98
+ break;
99
+ case 'interface_declaration':
100
+ handlePhpInterfaceDecl(node, ctx);
101
+ break;
102
+ case 'trait_declaration':
103
+ handlePhpTraitDecl(node, ctx);
104
+ break;
105
+ case 'enum_declaration':
106
+ handlePhpEnumDecl(node, ctx);
107
+ break;
108
+ case 'method_declaration':
109
+ handlePhpMethodDecl(node, ctx);
110
+ break;
111
+ case 'namespace_use_declaration':
112
+ handlePhpNamespaceUse(node, ctx);
113
+ break;
114
+ case 'function_call_expression':
115
+ handlePhpFuncCall(node, ctx);
116
+ break;
117
+ case 'member_call_expression':
118
+ handlePhpMemberCall(node, ctx);
119
+ break;
120
+ case 'scoped_call_expression':
121
+ handlePhpScopedCall(node, ctx);
122
+ break;
123
+ case 'object_creation_expression':
124
+ handlePhpObjectCreation(node, ctx);
125
+ break;
99
126
  }
100
127
 
101
- function walkPhpNode(node) {
102
- switch (node.type) {
103
- case 'function_definition': {
104
- const nameNode = node.childForFieldName('name');
105
- if (nameNode) {
106
- const params = extractPhpParameters(node);
107
- definitions.push({
108
- name: nameNode.text,
109
- kind: 'function',
110
- line: node.startPosition.row + 1,
111
- endLine: nodeEndLine(node),
112
- children: params.length > 0 ? params : undefined,
113
- });
114
- }
115
- break;
116
- }
128
+ for (let i = 0; i < node.childCount; i++) walkPhpNode(node.child(i), ctx);
129
+ }
117
130
 
118
- case 'class_declaration': {
119
- const nameNode = node.childForFieldName('name');
120
- if (nameNode) {
121
- const classChildren = extractPhpClassChildren(node);
122
- definitions.push({
123
- name: nameNode.text,
124
- kind: 'class',
125
- line: node.startPosition.row + 1,
126
- endLine: nodeEndLine(node),
127
- children: classChildren.length > 0 ? classChildren : undefined,
128
- });
131
+ // ── Walk-path per-node-type handlers ────────────────────────────────────────
129
132
 
130
- // Check base clause (extends)
131
- const baseClause =
132
- node.childForFieldName('base_clause') || findChild(node, 'base_clause');
133
- if (baseClause) {
134
- for (let i = 0; i < baseClause.childCount; i++) {
135
- const child = baseClause.child(i);
136
- if (child && (child.type === 'name' || child.type === 'qualified_name')) {
137
- classes.push({
138
- name: nameNode.text,
139
- extends: child.text,
140
- line: node.startPosition.row + 1,
141
- });
142
- break;
143
- }
144
- }
145
- }
133
+ function handlePhpFuncDef(node, ctx) {
134
+ const nameNode = node.childForFieldName('name');
135
+ if (!nameNode) return;
136
+ const params = extractPhpParameters(node);
137
+ ctx.definitions.push({
138
+ name: nameNode.text,
139
+ kind: 'function',
140
+ line: node.startPosition.row + 1,
141
+ endLine: nodeEndLine(node),
142
+ children: params.length > 0 ? params : undefined,
143
+ });
144
+ }
146
145
 
147
- // Check class interface clause (implements)
148
- const interfaceClause = findChild(node, 'class_interface_clause');
149
- if (interfaceClause) {
150
- for (let i = 0; i < interfaceClause.childCount; i++) {
151
- const child = interfaceClause.child(i);
152
- if (child && (child.type === 'name' || child.type === 'qualified_name')) {
153
- classes.push({
154
- name: nameNode.text,
155
- implements: child.text,
156
- line: node.startPosition.row + 1,
157
- });
158
- }
159
- }
160
- }
161
- }
146
+ function handlePhpClassDecl(node, ctx) {
147
+ const nameNode = node.childForFieldName('name');
148
+ if (!nameNode) return;
149
+ const classChildren = extractPhpClassChildren(node);
150
+ ctx.definitions.push({
151
+ name: nameNode.text,
152
+ kind: 'class',
153
+ line: node.startPosition.row + 1,
154
+ endLine: nodeEndLine(node),
155
+ children: classChildren.length > 0 ? classChildren : undefined,
156
+ });
157
+ const baseClause = node.childForFieldName('base_clause') || findChild(node, 'base_clause');
158
+ if (baseClause) {
159
+ for (let i = 0; i < baseClause.childCount; i++) {
160
+ const child = baseClause.child(i);
161
+ if (child && (child.type === 'name' || child.type === 'qualified_name')) {
162
+ ctx.classes.push({
163
+ name: nameNode.text,
164
+ extends: child.text,
165
+ line: node.startPosition.row + 1,
166
+ });
162
167
  break;
163
168
  }
164
-
165
- case 'interface_declaration': {
166
- const nameNode = node.childForFieldName('name');
167
- if (nameNode) {
168
- definitions.push({
169
- name: nameNode.text,
170
- kind: 'interface',
171
- line: node.startPosition.row + 1,
172
- endLine: nodeEndLine(node),
173
- });
174
- const body = node.childForFieldName('body');
175
- if (body) {
176
- for (let i = 0; i < body.childCount; i++) {
177
- const child = body.child(i);
178
- if (child && child.type === 'method_declaration') {
179
- const methName = child.childForFieldName('name');
180
- if (methName) {
181
- definitions.push({
182
- name: `${nameNode.text}.${methName.text}`,
183
- kind: 'method',
184
- line: child.startPosition.row + 1,
185
- endLine: child.endPosition.row + 1,
186
- });
187
- }
188
- }
189
- }
190
- }
191
- }
192
- break;
169
+ }
170
+ }
171
+ const interfaceClause = findChild(node, 'class_interface_clause');
172
+ if (interfaceClause) {
173
+ for (let i = 0; i < interfaceClause.childCount; i++) {
174
+ const child = interfaceClause.child(i);
175
+ if (child && (child.type === 'name' || child.type === 'qualified_name')) {
176
+ ctx.classes.push({
177
+ name: nameNode.text,
178
+ implements: child.text,
179
+ line: node.startPosition.row + 1,
180
+ });
193
181
  }
182
+ }
183
+ }
184
+ }
194
185
 
195
- case 'trait_declaration': {
196
- const nameNode = node.childForFieldName('name');
197
- if (nameNode) {
198
- definitions.push({
199
- name: nameNode.text,
200
- kind: 'trait',
201
- line: node.startPosition.row + 1,
202
- endLine: nodeEndLine(node),
186
+ function handlePhpInterfaceDecl(node, ctx) {
187
+ const nameNode = node.childForFieldName('name');
188
+ if (!nameNode) return;
189
+ ctx.definitions.push({
190
+ name: nameNode.text,
191
+ kind: 'interface',
192
+ line: node.startPosition.row + 1,
193
+ endLine: nodeEndLine(node),
194
+ });
195
+ const body = node.childForFieldName('body');
196
+ if (body) {
197
+ for (let i = 0; i < body.childCount; i++) {
198
+ const child = body.child(i);
199
+ if (child && child.type === 'method_declaration') {
200
+ const methName = child.childForFieldName('name');
201
+ if (methName) {
202
+ ctx.definitions.push({
203
+ name: `${nameNode.text}.${methName.text}`,
204
+ kind: 'method',
205
+ line: child.startPosition.row + 1,
206
+ endLine: child.endPosition.row + 1,
203
207
  });
204
208
  }
205
- break;
206
209
  }
210
+ }
211
+ }
212
+ }
207
213
 
208
- case 'enum_declaration': {
209
- const nameNode = node.childForFieldName('name');
210
- if (nameNode) {
211
- const enumChildren = extractPhpEnumCases(node);
212
- definitions.push({
213
- name: nameNode.text,
214
- kind: 'enum',
215
- line: node.startPosition.row + 1,
216
- endLine: nodeEndLine(node),
217
- children: enumChildren.length > 0 ? enumChildren : undefined,
218
- });
219
- }
220
- break;
221
- }
214
+ function handlePhpTraitDecl(node, ctx) {
215
+ const nameNode = node.childForFieldName('name');
216
+ if (!nameNode) return;
217
+ ctx.definitions.push({
218
+ name: nameNode.text,
219
+ kind: 'trait',
220
+ line: node.startPosition.row + 1,
221
+ endLine: nodeEndLine(node),
222
+ });
223
+ }
222
224
 
223
- case 'method_declaration': {
224
- const nameNode = node.childForFieldName('name');
225
- if (nameNode) {
226
- const parentClass = findPHPParentClass(node);
227
- const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
228
- const params = extractPhpParameters(node);
229
- definitions.push({
230
- name: fullName,
231
- kind: 'method',
232
- line: node.startPosition.row + 1,
233
- endLine: nodeEndLine(node),
234
- children: params.length > 0 ? params : undefined,
235
- visibility: extractModifierVisibility(node),
236
- });
237
- }
238
- break;
239
- }
225
+ function handlePhpEnumDecl(node, ctx) {
226
+ const nameNode = node.childForFieldName('name');
227
+ if (!nameNode) return;
228
+ const enumChildren = extractPhpEnumCases(node);
229
+ ctx.definitions.push({
230
+ name: nameNode.text,
231
+ kind: 'enum',
232
+ line: node.startPosition.row + 1,
233
+ endLine: nodeEndLine(node),
234
+ children: enumChildren.length > 0 ? enumChildren : undefined,
235
+ });
236
+ }
240
237
 
241
- case 'namespace_use_declaration': {
242
- // use App\Models\User;
243
- for (let i = 0; i < node.childCount; i++) {
244
- const child = node.child(i);
245
- if (child && child.type === 'namespace_use_clause') {
246
- const nameNode = findChild(child, 'qualified_name') || findChild(child, 'name');
247
- if (nameNode) {
248
- const fullPath = nameNode.text;
249
- const lastName = fullPath.split('\\').pop();
250
- const alias = child.childForFieldName('alias');
251
- imports.push({
252
- source: fullPath,
253
- names: [alias ? alias.text : lastName],
254
- line: node.startPosition.row + 1,
255
- phpUse: true,
256
- });
257
- }
258
- }
259
- // Single use clause without wrapper
260
- if (child && (child.type === 'qualified_name' || child.type === 'name')) {
261
- const fullPath = child.text;
262
- const lastName = fullPath.split('\\').pop();
263
- imports.push({
264
- source: fullPath,
265
- names: [lastName],
266
- line: node.startPosition.row + 1,
267
- phpUse: true,
268
- });
269
- }
270
- }
271
- break;
272
- }
238
+ function handlePhpMethodDecl(node, ctx) {
239
+ // Skip interface methods already emitted by handlePhpInterfaceDecl
240
+ if (node.parent?.parent?.type === 'interface_declaration') return;
241
+ const nameNode = node.childForFieldName('name');
242
+ if (!nameNode) return;
243
+ const parentClass = findPHPParentClass(node);
244
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
245
+ const params = extractPhpParameters(node);
246
+ ctx.definitions.push({
247
+ name: fullName,
248
+ kind: 'method',
249
+ line: node.startPosition.row + 1,
250
+ endLine: nodeEndLine(node),
251
+ children: params.length > 0 ? params : undefined,
252
+ visibility: extractModifierVisibility(node),
253
+ });
254
+ }
273
255
 
274
- case 'function_call_expression': {
275
- const fn = node.childForFieldName('function') || node.child(0);
276
- if (fn) {
277
- if (fn.type === 'name' || fn.type === 'identifier') {
278
- calls.push({ name: fn.text, line: node.startPosition.row + 1 });
279
- } else if (fn.type === 'qualified_name') {
280
- const parts = fn.text.split('\\');
281
- calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
282
- }
283
- }
284
- break;
256
+ function handlePhpNamespaceUse(node, ctx) {
257
+ for (let i = 0; i < node.childCount; i++) {
258
+ const child = node.child(i);
259
+ if (child && child.type === 'namespace_use_clause') {
260
+ const nameNode = findChild(child, 'qualified_name') || findChild(child, 'name');
261
+ if (nameNode) {
262
+ const fullPath = nameNode.text;
263
+ const lastName = fullPath.split('\\').pop();
264
+ const alias = child.childForFieldName('alias');
265
+ ctx.imports.push({
266
+ source: fullPath,
267
+ names: [alias ? alias.text : lastName],
268
+ line: node.startPosition.row + 1,
269
+ phpUse: true,
270
+ });
285
271
  }
272
+ }
273
+ if (child && (child.type === 'qualified_name' || child.type === 'name')) {
274
+ const fullPath = child.text;
275
+ const lastName = fullPath.split('\\').pop();
276
+ ctx.imports.push({
277
+ source: fullPath,
278
+ names: [lastName],
279
+ line: node.startPosition.row + 1,
280
+ phpUse: true,
281
+ });
282
+ }
283
+ }
284
+ }
286
285
 
287
- case 'member_call_expression': {
288
- const name = node.childForFieldName('name');
289
- if (name) {
290
- const obj = node.childForFieldName('object');
291
- const call = { name: name.text, line: node.startPosition.row + 1 };
292
- if (obj) call.receiver = obj.text;
293
- calls.push(call);
294
- }
295
- break;
296
- }
286
+ function handlePhpFuncCall(node, ctx) {
287
+ const fn = node.childForFieldName('function') || node.child(0);
288
+ if (!fn) return;
289
+ if (fn.type === 'name' || fn.type === 'identifier') {
290
+ ctx.calls.push({ name: fn.text, line: node.startPosition.row + 1 });
291
+ } else if (fn.type === 'qualified_name') {
292
+ const parts = fn.text.split('\\');
293
+ ctx.calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
294
+ }
295
+ }
297
296
 
298
- case 'scoped_call_expression': {
299
- const name = node.childForFieldName('name');
300
- if (name) {
301
- const scope = node.childForFieldName('scope');
302
- const call = { name: name.text, line: node.startPosition.row + 1 };
303
- if (scope) call.receiver = scope.text;
304
- calls.push(call);
305
- }
306
- break;
307
- }
297
+ function handlePhpMemberCall(node, ctx) {
298
+ const name = node.childForFieldName('name');
299
+ if (!name) return;
300
+ const obj = node.childForFieldName('object');
301
+ const call = { name: name.text, line: node.startPosition.row + 1 };
302
+ if (obj) call.receiver = obj.text;
303
+ ctx.calls.push(call);
304
+ }
308
305
 
309
- case 'object_creation_expression': {
310
- const classNode = node.child(1); // skip 'new' keyword
311
- if (classNode && (classNode.type === 'name' || classNode.type === 'qualified_name')) {
312
- const parts = classNode.text.split('\\');
313
- calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
314
- }
315
- break;
316
- }
317
- }
306
+ function handlePhpScopedCall(node, ctx) {
307
+ const name = node.childForFieldName('name');
308
+ if (!name) return;
309
+ const scope = node.childForFieldName('scope');
310
+ const call = { name: name.text, line: node.startPosition.row + 1 };
311
+ if (scope) call.receiver = scope.text;
312
+ ctx.calls.push(call);
313
+ }
318
314
 
319
- for (let i = 0; i < node.childCount; i++) walkPhpNode(node.child(i));
315
+ function handlePhpObjectCreation(node, ctx) {
316
+ const classNode = node.child(1);
317
+ if (classNode && (classNode.type === 'name' || classNode.type === 'qualified_name')) {
318
+ const parts = classNode.text.split('\\');
319
+ ctx.calls.push({ name: parts[parts.length - 1], line: node.startPosition.row + 1 });
320
320
  }
321
+ }
321
322
 
322
- walkPhpNode(tree.rootNode);
323
- return { definitions, calls, imports, classes, exports };
323
+ function findPHPParentClass(node) {
324
+ let current = node.parent;
325
+ while (current) {
326
+ if (
327
+ current.type === 'class_declaration' ||
328
+ current.type === 'trait_declaration' ||
329
+ current.type === 'enum_declaration'
330
+ ) {
331
+ const nameNode = current.childForFieldName('name');
332
+ return nameNode ? nameNode.text : null;
333
+ }
334
+ current = current.parent;
335
+ }
336
+ return null;
324
337
  }