@optave/codegraph 3.8.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +9 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +95 -87
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/watch.d.ts.map +1 -1
  28. package/dist/cli/commands/watch.js +16 -2
  29. package/dist/cli/commands/watch.js.map +1 -1
  30. package/dist/db/connection.d.ts.map +1 -1
  31. package/dist/db/connection.js +29 -26
  32. package/dist/db/connection.js.map +1 -1
  33. package/dist/db/query-builder.d.ts.map +1 -1
  34. package/dist/db/query-builder.js +16 -5
  35. package/dist/db/query-builder.js.map +1 -1
  36. package/dist/db/repository/base.d.ts +10 -0
  37. package/dist/db/repository/base.d.ts.map +1 -1
  38. package/dist/db/repository/base.js +17 -0
  39. package/dist/db/repository/base.js.map +1 -1
  40. package/dist/db/repository/native-repository.d.ts +6 -1
  41. package/dist/db/repository/native-repository.d.ts.map +1 -1
  42. package/dist/db/repository/native-repository.js +77 -1
  43. package/dist/db/repository/native-repository.js.map +1 -1
  44. package/dist/db/repository/nodes.d.ts.map +1 -1
  45. package/dist/db/repository/nodes.js +8 -4
  46. package/dist/db/repository/nodes.js.map +1 -1
  47. package/dist/db/repository/sqlite-repository.d.ts +3 -0
  48. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  49. package/dist/db/repository/sqlite-repository.js +26 -0
  50. package/dist/db/repository/sqlite-repository.js.map +1 -1
  51. package/dist/domain/analysis/brief.d.ts.map +1 -1
  52. package/dist/domain/analysis/brief.js +13 -17
  53. package/dist/domain/analysis/brief.js.map +1 -1
  54. package/dist/domain/analysis/context.d.ts.map +1 -1
  55. package/dist/domain/analysis/context.js +14 -11
  56. package/dist/domain/analysis/context.js.map +1 -1
  57. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  58. package/dist/domain/analysis/dependencies.js +53 -52
  59. package/dist/domain/analysis/dependencies.js.map +1 -1
  60. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  61. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  62. package/dist/domain/analysis/fn-impact.js +33 -31
  63. package/dist/domain/analysis/fn-impact.js.map +1 -1
  64. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  65. package/dist/domain/analysis/implementations.js +11 -19
  66. package/dist/domain/analysis/implementations.js.map +1 -1
  67. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  68. package/dist/domain/analysis/module-map.js +55 -76
  69. package/dist/domain/analysis/module-map.js.map +1 -1
  70. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  71. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  72. package/dist/domain/analysis/query-helpers.js +15 -1
  73. package/dist/domain/analysis/query-helpers.js.map +1 -1
  74. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  75. package/dist/domain/graph/builder/pipeline.js +255 -105
  76. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  77. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  78. package/dist/domain/graph/builder/stages/build-edges.js +22 -17
  79. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  80. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  81. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  82. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  83. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  84. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  85. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  86. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  87. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  88. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  89. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  90. package/dist/domain/graph/cycles.d.ts +6 -0
  91. package/dist/domain/graph/cycles.d.ts.map +1 -1
  92. package/dist/domain/graph/cycles.js +114 -22
  93. package/dist/domain/graph/cycles.js.map +1 -1
  94. package/dist/domain/graph/resolve.js +1 -1
  95. package/dist/domain/graph/resolve.js.map +1 -1
  96. package/dist/domain/graph/watcher.d.ts +2 -0
  97. package/dist/domain/graph/watcher.d.ts.map +1 -1
  98. package/dist/domain/graph/watcher.js +170 -75
  99. package/dist/domain/graph/watcher.js.map +1 -1
  100. package/dist/domain/parser.d.ts +0 -5
  101. package/dist/domain/parser.d.ts.map +1 -1
  102. package/dist/domain/parser.js +13 -28
  103. package/dist/domain/parser.js.map +1 -1
  104. package/dist/domain/search/generator.js +1 -1
  105. package/dist/domain/search/generator.js.map +1 -1
  106. package/dist/domain/search/models.d.ts +4 -3
  107. package/dist/domain/search/models.d.ts.map +1 -1
  108. package/dist/domain/search/models.js +18 -5
  109. package/dist/domain/search/models.js.map +1 -1
  110. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  111. package/dist/domain/search/search/hybrid.js +29 -18
  112. package/dist/domain/search/search/hybrid.js.map +1 -1
  113. package/dist/extractors/go.js +36 -33
  114. package/dist/extractors/go.js.map +1 -1
  115. package/dist/extractors/helpers.d.ts.map +1 -1
  116. package/dist/extractors/helpers.js +40 -29
  117. package/dist/extractors/helpers.js.map +1 -1
  118. package/dist/extractors/java.js +58 -46
  119. package/dist/extractors/java.js.map +1 -1
  120. package/dist/extractors/javascript.js +46 -45
  121. package/dist/extractors/javascript.js.map +1 -1
  122. package/dist/extractors/kotlin.js +84 -78
  123. package/dist/extractors/kotlin.js.map +1 -1
  124. package/dist/extractors/python.js +29 -24
  125. package/dist/extractors/python.js.map +1 -1
  126. package/dist/extractors/rust.js +41 -32
  127. package/dist/extractors/rust.js.map +1 -1
  128. package/dist/extractors/solidity.js +58 -67
  129. package/dist/extractors/solidity.js.map +1 -1
  130. package/dist/extractors/swift.js +83 -81
  131. package/dist/extractors/swift.js.map +1 -1
  132. package/dist/extractors/zig.js +58 -60
  133. package/dist/extractors/zig.js.map +1 -1
  134. package/dist/features/ast.d.ts +16 -14
  135. package/dist/features/ast.d.ts.map +1 -1
  136. package/dist/features/ast.js +83 -81
  137. package/dist/features/ast.js.map +1 -1
  138. package/dist/features/audit.d.ts.map +1 -1
  139. package/dist/features/audit.js +8 -6
  140. package/dist/features/audit.js.map +1 -1
  141. package/dist/features/branch-compare.d.ts.map +1 -1
  142. package/dist/features/branch-compare.js +69 -72
  143. package/dist/features/branch-compare.js.map +1 -1
  144. package/dist/features/communities.d.ts.map +1 -1
  145. package/dist/features/communities.js +19 -7
  146. package/dist/features/communities.js.map +1 -1
  147. package/dist/features/complexity.d.ts.map +1 -1
  148. package/dist/features/complexity.js +120 -125
  149. package/dist/features/complexity.js.map +1 -1
  150. package/dist/features/dataflow.d.ts.map +1 -1
  151. package/dist/features/dataflow.js +136 -137
  152. package/dist/features/dataflow.js.map +1 -1
  153. package/dist/features/flow.d.ts.map +1 -1
  154. package/dist/features/flow.js +84 -79
  155. package/dist/features/flow.js.map +1 -1
  156. package/dist/features/structure-query.d.ts.map +1 -1
  157. package/dist/features/structure-query.js +69 -65
  158. package/dist/features/structure-query.js.map +1 -1
  159. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  160. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  161. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  162. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  163. package/dist/graph/algorithms/leiden/partition.js +288 -266
  164. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  165. package/dist/graph/model.d.ts.map +1 -1
  166. package/dist/graph/model.js +5 -1
  167. package/dist/graph/model.js.map +1 -1
  168. package/dist/infrastructure/config.d.ts.map +1 -1
  169. package/dist/infrastructure/config.js +6 -4
  170. package/dist/infrastructure/config.js.map +1 -1
  171. package/dist/infrastructure/suppress.d.ts +25 -0
  172. package/dist/infrastructure/suppress.d.ts.map +1 -0
  173. package/dist/infrastructure/suppress.js +43 -0
  174. package/dist/infrastructure/suppress.js.map +1 -0
  175. package/dist/mcp/server.d.ts.map +1 -1
  176. package/dist/mcp/server.js +29 -24
  177. package/dist/mcp/server.js.map +1 -1
  178. package/dist/presentation/dataflow.d.ts.map +1 -1
  179. package/dist/presentation/dataflow.js +47 -38
  180. package/dist/presentation/dataflow.js.map +1 -1
  181. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  182. package/dist/presentation/diff-impact-mermaid.js +60 -51
  183. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  184. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  185. package/dist/presentation/queries-cli/exports.js +20 -14
  186. package/dist/presentation/queries-cli/exports.js.map +1 -1
  187. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  188. package/dist/presentation/queries-cli/impact.js +15 -13
  189. package/dist/presentation/queries-cli/impact.js.map +1 -1
  190. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  191. package/dist/presentation/queries-cli/inspect.js +101 -79
  192. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  193. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/overview.js +25 -16
  195. package/dist/presentation/queries-cli/overview.js.map +1 -1
  196. package/dist/presentation/queries-cli/path.js +26 -20
  197. package/dist/presentation/queries-cli/path.js.map +1 -1
  198. package/dist/presentation/result-formatter.d.ts +10 -0
  199. package/dist/presentation/result-formatter.d.ts.map +1 -1
  200. package/dist/presentation/result-formatter.js +16 -1
  201. package/dist/presentation/result-formatter.js.map +1 -1
  202. package/dist/presentation/viewer.d.ts.map +1 -1
  203. package/dist/presentation/viewer.js +18 -12
  204. package/dist/presentation/viewer.js.map +1 -1
  205. package/dist/shared/errors.d.ts +5 -0
  206. package/dist/shared/errors.d.ts.map +1 -1
  207. package/dist/shared/errors.js +5 -0
  208. package/dist/shared/errors.js.map +1 -1
  209. package/dist/shared/hierarchy.d.ts +8 -2
  210. package/dist/shared/hierarchy.d.ts.map +1 -1
  211. package/dist/shared/hierarchy.js +42 -1
  212. package/dist/shared/hierarchy.js.map +1 -1
  213. package/dist/shared/normalize.d.ts +6 -1
  214. package/dist/shared/normalize.d.ts.map +1 -1
  215. package/dist/shared/normalize.js +20 -12
  216. package/dist/shared/normalize.js.map +1 -1
  217. package/dist/shared/paginate.d.ts +0 -9
  218. package/dist/shared/paginate.d.ts.map +1 -1
  219. package/dist/shared/paginate.js +0 -15
  220. package/dist/shared/paginate.js.map +1 -1
  221. package/dist/types.d.ts +10 -4
  222. package/dist/types.d.ts.map +1 -1
  223. package/package.json +7 -7
  224. package/src/ast-analysis/engine.ts +126 -105
  225. package/src/ast-analysis/metrics.ts +33 -11
  226. package/src/ast-analysis/shared.ts +33 -24
  227. package/src/ast-analysis/visitor-utils.ts +52 -32
  228. package/src/ast-analysis/visitor.ts +132 -71
  229. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  230. package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
  231. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  232. package/src/cli/commands/watch.ts +16 -2
  233. package/src/db/connection.ts +29 -28
  234. package/src/db/query-builder.ts +15 -3
  235. package/src/db/repository/base.ts +20 -0
  236. package/src/db/repository/native-repository.ts +79 -1
  237. package/src/db/repository/nodes.ts +13 -8
  238. package/src/db/repository/sqlite-repository.ts +29 -0
  239. package/src/domain/analysis/brief.ts +15 -25
  240. package/src/domain/analysis/context.ts +17 -10
  241. package/src/domain/analysis/dependencies.ts +67 -76
  242. package/src/domain/analysis/fn-impact.ts +36 -43
  243. package/src/domain/analysis/implementations.ts +11 -17
  244. package/src/domain/analysis/module-map.ts +58 -92
  245. package/src/domain/analysis/query-helpers.ts +18 -1
  246. package/src/domain/graph/builder/pipeline.ts +286 -97
  247. package/src/domain/graph/builder/stages/build-edges.ts +22 -18
  248. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  249. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  250. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  251. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  252. package/src/domain/graph/cycles.ts +110 -23
  253. package/src/domain/graph/resolve.ts +1 -1
  254. package/src/domain/graph/watcher.ts +202 -96
  255. package/src/domain/parser.ts +14 -26
  256. package/src/domain/search/generator.ts +1 -1
  257. package/src/domain/search/models.ts +17 -4
  258. package/src/domain/search/search/hybrid.ts +69 -51
  259. package/src/extractors/go.ts +43 -33
  260. package/src/extractors/helpers.ts +37 -23
  261. package/src/extractors/java.ts +66 -47
  262. package/src/extractors/javascript.ts +45 -46
  263. package/src/extractors/kotlin.ts +84 -77
  264. package/src/extractors/python.ts +31 -25
  265. package/src/extractors/rust.ts +37 -29
  266. package/src/extractors/solidity.ts +57 -61
  267. package/src/extractors/swift.ts +81 -80
  268. package/src/extractors/zig.ts +58 -61
  269. package/src/features/ast.ts +130 -110
  270. package/src/features/audit.ts +8 -6
  271. package/src/features/branch-compare.ts +105 -79
  272. package/src/features/communities.ts +25 -10
  273. package/src/features/complexity.ts +171 -134
  274. package/src/features/dataflow.ts +165 -175
  275. package/src/features/flow.ts +129 -92
  276. package/src/features/structure-query.ts +79 -64
  277. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  278. package/src/graph/algorithms/leiden/partition.ts +359 -294
  279. package/src/graph/model.ts +6 -1
  280. package/src/infrastructure/config.ts +6 -4
  281. package/src/infrastructure/suppress.ts +47 -0
  282. package/src/mcp/server.ts +53 -37
  283. package/src/presentation/dataflow.ts +50 -44
  284. package/src/presentation/diff-impact-mermaid.ts +104 -62
  285. package/src/presentation/queries-cli/exports.ts +21 -13
  286. package/src/presentation/queries-cli/impact.ts +15 -13
  287. package/src/presentation/queries-cli/inspect.ts +100 -81
  288. package/src/presentation/queries-cli/overview.ts +26 -16
  289. package/src/presentation/queries-cli/path.ts +33 -25
  290. package/src/presentation/result-formatter.ts +19 -1
  291. package/src/presentation/viewer.ts +42 -14
  292. package/src/shared/errors.ts +6 -0
  293. package/src/shared/hierarchy.ts +50 -2
  294. package/src/shared/normalize.ts +31 -12
  295. package/src/shared/paginate.ts +0 -17
  296. package/src/types.ts +24 -4
@@ -21,6 +21,7 @@ import { performance } from 'node:perf_hooks';
21
21
  import { bulkNodeIdsByFile } from '../db/index.js';
22
22
  import { debug } from '../infrastructure/logger.js';
23
23
  import { loadNative } from '../infrastructure/native.js';
24
+ import { toErrorMessage } from '../shared/errors.js';
24
25
  import type {
25
26
  AnalysisOpts,
26
27
  AnalysisTiming,
@@ -102,12 +103,83 @@ async function getParserModule(): Promise<typeof import('../domain/parser.js')>
102
103
 
103
104
  // ─── Native standalone analysis ─────────────────────────────────────────
104
105
 
106
+ interface NativeAnalysisNeeds {
107
+ complexity: boolean;
108
+ cfg: boolean;
109
+ dataflow: boolean;
110
+ }
111
+
105
112
  /**
106
113
  * Try native Rust analysis for files missing complexity/CFG/dataflow data.
107
114
  * Reads source from disk, calls the native standalone functions, and stores
108
- * results directly on definitions/symbols. Returns the set of files that
109
- * were fully handled (no remaining gaps except possibly AST store).
115
+ * results directly on definitions/symbols.
110
116
  */
117
+
118
+ /** Determine which native analyses a file still needs. */
119
+ function detectNativeNeeds(
120
+ symbols: ExtractorOutput,
121
+ ext: string,
122
+ langId: string,
123
+ opts: { doComplexity: boolean; doCfg: boolean; doDataflow: boolean },
124
+ ): NativeAnalysisNeeds {
125
+ const defs = symbols.definitions || [];
126
+ const langSupportsComplexity = COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(langId);
127
+ const langSupportsCfg = CFG_EXTENSIONS.has(ext) || CFG_RULES.has(langId);
128
+ const langSupportsDataflow = DATAFLOW_EXTENSIONS.has(ext) || DATAFLOW_RULES.has(langId);
129
+
130
+ return {
131
+ complexity:
132
+ opts.doComplexity &&
133
+ langSupportsComplexity &&
134
+ defs.some((d) => hasFuncBody(d) && !d.complexity),
135
+ cfg:
136
+ opts.doCfg &&
137
+ langSupportsCfg &&
138
+ defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks)),
139
+ dataflow: opts.doDataflow && !symbols.dataflow && langSupportsDataflow,
140
+ };
141
+ }
142
+
143
+ /** Run native analysis passes for a single file. */
144
+ function runNativeFileAnalysis(
145
+ native: NativeAddon,
146
+ source: string,
147
+ absPath: string,
148
+ relPath: string,
149
+ langId: string,
150
+ symbols: ExtractorOutput,
151
+ needs: NativeAnalysisNeeds,
152
+ ): void {
153
+ const defs = symbols.definitions || [];
154
+
155
+ if (needs.complexity && native.analyzeComplexity) {
156
+ try {
157
+ const results = native.analyzeComplexity(source, absPath, langId);
158
+ storeNativeComplexityResults(results, defs);
159
+ } catch (err: unknown) {
160
+ debug(`native analyzeComplexity failed for ${relPath}: ${toErrorMessage(err)}`);
161
+ }
162
+ }
163
+
164
+ if (needs.cfg && native.buildCfgAnalysis) {
165
+ try {
166
+ const results = native.buildCfgAnalysis(source, absPath, langId);
167
+ storeNativeCfgResults(results, defs);
168
+ } catch (err: unknown) {
169
+ debug(`native buildCfgAnalysis failed for ${relPath}: ${toErrorMessage(err)}`);
170
+ }
171
+ }
172
+
173
+ if (needs.dataflow && native.extractDataflowAnalysis) {
174
+ try {
175
+ const result = native.extractDataflowAnalysis(source, absPath, langId);
176
+ if (result) symbols.dataflow = result;
177
+ } catch (err: unknown) {
178
+ debug(`native extractDataflowAnalysis failed for ${relPath}: ${toErrorMessage(err)}`);
179
+ }
180
+ }
181
+ }
182
+
111
183
  function runNativeAnalysis(
112
184
  native: NativeAddon,
113
185
  fileSymbols: Map<string, ExtractorOutput>,
@@ -115,68 +187,31 @@ function runNativeAnalysis(
115
187
  opts: AnalysisOpts,
116
188
  extToLang: Map<string, string>,
117
189
  ): void {
118
- const doComplexity = opts.complexity !== false;
119
- const doCfg = opts.cfg !== false;
120
- const doDataflow = opts.dataflow !== false;
190
+ const optsFlags = {
191
+ doComplexity: opts.complexity !== false,
192
+ doCfg: opts.cfg !== false,
193
+ doDataflow: opts.dataflow !== false,
194
+ };
121
195
 
122
196
  for (const [relPath, symbols] of fileSymbols) {
123
- if (symbols._tree) continue; // already has WASM tree, skip native
197
+ if (symbols._tree) continue;
124
198
  const ext = path.extname(relPath).toLowerCase();
125
199
  const langId = symbols._langId || extToLang.get(ext);
126
200
  if (!langId) continue;
127
201
 
128
- const defs = symbols.definitions || [];
129
-
130
- const needsComplexity =
131
- doComplexity &&
132
- COMPLEXITY_EXTENSIONS.has(ext) &&
133
- defs.some((d) => hasFuncBody(d) && !d.complexity);
134
- const needsCfg =
135
- doCfg &&
136
- CFG_EXTENSIONS.has(ext) &&
137
- defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks));
138
- const needsDataflow = doDataflow && !symbols.dataflow && DATAFLOW_EXTENSIONS.has(ext);
139
-
140
- if (!needsComplexity && !needsCfg && !needsDataflow) continue;
202
+ const needs = detectNativeNeeds(symbols, ext, langId, optsFlags);
203
+ if (!needs.complexity && !needs.cfg && !needs.dataflow) continue;
141
204
 
142
- // Read source from disk
143
205
  const absPath = path.join(rootDir, relPath);
144
206
  let source: string;
145
207
  try {
146
208
  source = fs.readFileSync(absPath, 'utf-8');
147
- } catch {
209
+ } catch (e) {
210
+ debug(`runNativeAnalysis: failed to read ${relPath}: ${toErrorMessage(e)}`);
148
211
  continue;
149
212
  }
150
213
 
151
- // Complexity
152
- if (needsComplexity && native.analyzeComplexity) {
153
- try {
154
- const results = native.analyzeComplexity(source, absPath);
155
- storeNativeComplexityResults(results, defs);
156
- } catch (err: unknown) {
157
- debug(`native analyzeComplexity failed for ${relPath}: ${(err as Error).message}`);
158
- }
159
- }
160
-
161
- // CFG
162
- if (needsCfg && native.buildCfgAnalysis) {
163
- try {
164
- const results = native.buildCfgAnalysis(source, absPath);
165
- storeNativeCfgResults(results, defs);
166
- } catch (err: unknown) {
167
- debug(`native buildCfgAnalysis failed for ${relPath}: ${(err as Error).message}`);
168
- }
169
- }
170
-
171
- // Dataflow
172
- if (needsDataflow && native.extractDataflowAnalysis) {
173
- try {
174
- const result = native.extractDataflowAnalysis(source, absPath);
175
- if (result) symbols.dataflow = result;
176
- } catch (err: unknown) {
177
- debug(`native extractDataflowAnalysis failed for ${relPath}: ${(err as Error).message}`);
178
- }
179
- }
214
+ runNativeFileAnalysis(native, source, absPath, relPath, langId, symbols, needs);
180
215
  }
181
216
  }
182
217
 
@@ -222,6 +257,25 @@ function storeNativeComplexityResults(
222
257
  }
223
258
  }
224
259
 
260
+ /** Override a definition's cyclomatic complexity with a CFG-derived value and recompute MI. */
261
+ function overrideCyclomaticFromCfg(def: Definition, cfgCyclomatic: number): void {
262
+ if (!def.complexity) return;
263
+ if (cfgCyclomatic <= 0) {
264
+ debug(`overrideCyclomaticFromCfg: skipping ${def.name} — cfgCyclomatic=${cfgCyclomatic}`);
265
+ return;
266
+ }
267
+ def.complexity.cyclomatic = cfgCyclomatic;
268
+ const { loc, halstead } = def.complexity;
269
+ const volume = halstead ? halstead.volume : 0;
270
+ const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
271
+ def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
272
+ volume,
273
+ cfgCyclomatic,
274
+ loc?.sloc ?? 0,
275
+ commentRatio,
276
+ );
277
+ }
278
+
225
279
  /** Store native CFG results on definitions, matched by line number. */
226
280
  function storeNativeCfgResults(results: NativeFunctionCfgResult[], defs: Definition[]): void {
227
281
  const byLine = new Map<number, NativeFunctionCfgResult[]>();
@@ -248,20 +302,8 @@ function storeNativeCfgResults(results: NativeFunctionCfgResult[], defs: Definit
248
302
 
249
303
  // Override complexity cyclomatic with CFG-derived value
250
304
  const { edges, blocks } = match.cfg;
251
- if (def.complexity && edges && blocks) {
252
- const cfgCyclomatic = edges.length - blocks.length + 2;
253
- if (cfgCyclomatic > 0) {
254
- def.complexity.cyclomatic = cfgCyclomatic;
255
- const { loc, halstead } = def.complexity;
256
- const volume = halstead ? halstead.volume : 0;
257
- const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
258
- def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
259
- volume,
260
- cfgCyclomatic,
261
- loc?.sloc ?? 0,
262
- commentRatio,
263
- );
264
- }
305
+ if (edges && blocks) {
306
+ overrideCyclomaticFromCfg(def, edges.length - blocks.length + 2);
265
307
  }
266
308
  }
267
309
  }
@@ -287,34 +329,22 @@ async function ensureWasmTreesIfNeeded(
287
329
  const ext = path.extname(relPath).toLowerCase();
288
330
  const defs = symbols.definitions || [];
289
331
 
290
- // Only consider definitions with a real function body.
291
- // Interface/type property signatures are extracted as methods but correctly
292
- // lack complexity/CFG data from the native engine. Exclude them by:
293
- // 1. Single-line span (endLine === line) — type property on one line
294
- // 2. Dotted names (e.g. "Interface.prop") — child definitions of types
295
- const hasFuncBody = (d: {
296
- name: string;
297
- kind: string;
298
- line: number;
299
- endLine?: number | null;
300
- }) =>
301
- (d.kind === 'function' || d.kind === 'method') &&
302
- d.line > 0 &&
303
- d.endLine != null &&
304
- d.endLine > d.line &&
305
- !d.name.includes('.');
306
-
307
332
  // AST: need tree when native didn't provide non-call astNodes
308
- const needsAst = doAst && !Array.isArray(symbols.astNodes) && WALK_EXTENSIONS.has(ext);
333
+ const lid = symbols._langId || '';
334
+ const needsAst =
335
+ doAst &&
336
+ !Array.isArray(symbols.astNodes) &&
337
+ (WALK_EXTENSIONS.has(ext) || AST_TYPE_MAPS.has(lid));
309
338
  const needsComplexity =
310
339
  doComplexity &&
311
- COMPLEXITY_EXTENSIONS.has(ext) &&
340
+ (COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(lid)) &&
312
341
  defs.some((d) => hasFuncBody(d) && !d.complexity);
313
342
  const needsCfg =
314
343
  doCfg &&
315
- CFG_EXTENSIONS.has(ext) &&
344
+ (CFG_EXTENSIONS.has(ext) || CFG_RULES.has(lid)) &&
316
345
  defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks));
317
- const needsDataflow = doDataflow && !symbols.dataflow && DATAFLOW_EXTENSIONS.has(ext);
346
+ const needsDataflow =
347
+ doDataflow && !symbols.dataflow && (DATAFLOW_EXTENSIONS.has(ext) || DATAFLOW_RULES.has(lid));
318
348
 
319
349
  if (needsAst || needsComplexity || needsCfg || needsDataflow) {
320
350
  needsWasmTrees = true;
@@ -327,7 +357,7 @@ async function ensureWasmTreesIfNeeded(
327
357
  const { ensureWasmTrees } = await getParserModule();
328
358
  await ensureWasmTrees(fileSymbols, rootDir);
329
359
  } catch (err: unknown) {
330
- debug(`ensureWasmTrees failed: ${(err as Error).message}`);
360
+ debug(`ensureWasmTrees failed: ${toErrorMessage(err)}`);
331
361
  }
332
362
  }
333
363
  }
@@ -396,9 +426,9 @@ function setupComplexityVisitorForFile(
396
426
  }
397
427
 
398
428
  /** Set up CFG visitor if any definitions need WASM CFG analysis. */
399
- function setupCfgVisitorForFile(defs: Definition[], langId: string, ext: string): Visitor | null {
429
+ function setupCfgVisitorForFile(defs: Definition[], langId: string): Visitor | null {
400
430
  const cfgRulesForLang = CFG_RULES.get(langId);
401
- if (!cfgRulesForLang || !CFG_EXTENSIONS.has(ext)) return null;
431
+ if (!cfgRulesForLang) return null;
402
432
 
403
433
  const needsWasmCfg = defs.some(
404
434
  (d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks),
@@ -432,12 +462,12 @@ function setupVisitors(
432
462
  opts.complexity !== false ? setupComplexityVisitorForFile(defs, langId, walkerOpts) : null;
433
463
  if (complexityVisitor) visitors.push(complexityVisitor);
434
464
 
435
- const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId, ext) : null;
465
+ const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId) : null;
436
466
  if (cfgVisitor) visitors.push(cfgVisitor);
437
467
 
438
468
  let dataflowVisitor: Visitor | null = null;
439
469
  const dfRules = DATAFLOW_RULES.get(langId);
440
- if (opts.dataflow !== false && dfRules && DATAFLOW_EXTENSIONS.has(ext) && !symbols.dataflow) {
470
+ if (opts.dataflow !== false && dfRules && !symbols.dataflow) {
441
471
  dataflowVisitor = createDataflowVisitor(dfRules);
442
472
  visitors.push(dataflowVisitor);
443
473
  }
@@ -510,17 +540,8 @@ function storeCfgResults(results: WalkResults, defs: Definition[]): void {
510
540
  def.cfg = { blocks: cfgResult.blocks, edges: cfgResult.edges };
511
541
 
512
542
  // Override complexity's cyclomatic with CFG-derived value (single source of truth)
513
- if (def.complexity && cfgResult.cyclomatic != null) {
514
- def.complexity.cyclomatic = cfgResult.cyclomatic;
515
- const { loc, halstead } = def.complexity;
516
- const volume = halstead ? halstead.volume : 0;
517
- const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
518
- def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
519
- volume,
520
- cfgResult.cyclomatic,
521
- loc?.sloc ?? 0,
522
- commentRatio,
523
- );
543
+ if (cfgResult.cyclomatic != null) {
544
+ overrideCyclomaticFromCfg(def, cfgResult.cyclomatic);
524
545
  }
525
546
  }
526
547
  }
@@ -542,7 +563,7 @@ async function delegateToBuildFunctions(
542
563
  const { buildAstNodes } = await import('../features/ast.js');
543
564
  await buildAstNodes(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
544
565
  } catch (err: unknown) {
545
- debug(`buildAstNodes failed: ${(err as Error).message}`);
566
+ debug(`buildAstNodes failed: ${toErrorMessage(err)}`);
546
567
  }
547
568
  timing.astMs = performance.now() - t0;
548
569
  }
@@ -553,7 +574,7 @@ async function delegateToBuildFunctions(
553
574
  const { buildComplexityMetrics } = await import('../features/complexity.js');
554
575
  await buildComplexityMetrics(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
555
576
  } catch (err: unknown) {
556
- debug(`buildComplexityMetrics failed: ${(err as Error).message}`);
577
+ debug(`buildComplexityMetrics failed: ${toErrorMessage(err)}`);
557
578
  }
558
579
  timing.complexityMs = performance.now() - t0;
559
580
  }
@@ -564,7 +585,7 @@ async function delegateToBuildFunctions(
564
585
  const { buildCFGData } = await import('../features/cfg.js');
565
586
  await buildCFGData(db, fileSymbols, rootDir, engineOpts);
566
587
  } catch (err: unknown) {
567
- debug(`buildCFGData failed: ${(err as Error).message}`);
588
+ debug(`buildCFGData failed: ${toErrorMessage(err)}`);
568
589
  }
569
590
  timing.cfgMs = performance.now() - t0;
570
591
  }
@@ -575,7 +596,7 @@ async function delegateToBuildFunctions(
575
596
  const { buildDataflowEdges } = await import('../features/dataflow.js');
576
597
  await buildDataflowEdges(db, fileSymbols, rootDir, engineOpts);
577
598
  } catch (err: unknown) {
578
- debug(`buildDataflowEdges failed: ${(err as Error).message}`);
599
+ debug(`buildDataflowEdges failed: ${toErrorMessage(err)}`);
579
600
  }
580
601
  timing.dataflowMs = performance.now() - t0;
581
602
  }
@@ -9,6 +9,16 @@ import type { HalsteadDerivedMetrics, LOCMetrics, TreeSitterNode } from '../type
9
9
 
10
10
  // ─── Halstead Derived Metrics ─────────────────────────────────────────────
11
11
 
12
+ /** Halstead delivered-bugs denominator (industry standard: V / 3000). */
13
+ const HALSTEAD_BUGS_DIVISOR = 3000;
14
+
15
+ /** Sum all values in a count map. */
16
+ function sumCounts(map: Map<string, number>): number {
17
+ let total = 0;
18
+ for (const c of map.values()) total += c;
19
+ return total;
20
+ }
21
+
12
22
  /**
13
23
  * Compute Halstead derived metrics from raw operator/operand counts.
14
24
  *
@@ -22,17 +32,15 @@ export function computeHalsteadDerived(
22
32
  ): HalsteadDerivedMetrics {
23
33
  const n1 = operators.size;
24
34
  const n2 = operands.size;
25
- let bigN1 = 0;
26
- for (const c of operators.values()) bigN1 += c;
27
- let bigN2 = 0;
28
- for (const c of operands.values()) bigN2 += c;
35
+ const bigN1 = sumCounts(operators);
36
+ const bigN2 = sumCounts(operands);
29
37
 
30
38
  const vocabulary = n1 + n2;
31
39
  const length = bigN1 + bigN2;
32
40
  const volume = vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
33
41
  const difficulty = n2 > 0 ? (n1 / 2) * (bigN2 / n2) : 0;
34
42
  const effort = difficulty * volume;
35
- const bugs = volume / 3000;
43
+ const bugs = volume / HALSTEAD_BUGS_DIVISOR;
36
44
 
37
45
  return {
38
46
  n1,
@@ -97,10 +105,20 @@ export function computeLOCMetrics(functionNode: TreeSitterNode, language?: strin
97
105
  // ─── Maintainability Index ────────────────────────────────────────────────
98
106
 
99
107
  /**
100
- * Compute normalized Maintainability Index (0-100 scale).
101
- *
102
- * Original SEI formula: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
108
+ * SEI Maintainability Index formula coefficients.
109
+ * Original: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
103
110
  * Microsoft normalization: max(0, min(100, MI * 100/171))
111
+ */
112
+ const MI_BASE = 171;
113
+ const MI_VOLUME_COEFF = 5.2;
114
+ const MI_CYCLOMATIC_COEFF = 0.23;
115
+ const MI_LOC_COEFF = 16.2;
116
+ const MI_COMMENT_AMPLITUDE = 50;
117
+ const MI_COMMENT_SCALE = 2.4;
118
+ const MI_NORMALIZE_SCALE = 100;
119
+
120
+ /**
121
+ * Compute normalized Maintainability Index (0-100 scale).
104
122
  *
105
123
  * @param {number} volume - Halstead volume
106
124
  * @param {number} cyclomatic - Cyclomatic complexity
@@ -117,12 +135,16 @@ export function computeMaintainabilityIndex(
117
135
  const safeVolume = Math.max(volume, 1);
118
136
  const safeSLOC = Math.max(sloc, 1);
119
137
 
120
- let mi = 171 - 5.2 * Math.log(safeVolume) - 0.23 * cyclomatic - 16.2 * Math.log(safeSLOC);
138
+ let mi =
139
+ MI_BASE -
140
+ MI_VOLUME_COEFF * Math.log(safeVolume) -
141
+ MI_CYCLOMATIC_COEFF * cyclomatic -
142
+ MI_LOC_COEFF * Math.log(safeSLOC);
121
143
 
122
144
  if (commentRatio != null && commentRatio > 0) {
123
- mi += 50 * Math.sin(Math.sqrt(2.4 * commentRatio));
145
+ mi += MI_COMMENT_AMPLITUDE * Math.sin(Math.sqrt(MI_COMMENT_SCALE * commentRatio));
124
146
  }
125
147
 
126
- const normalized = Math.max(0, Math.min(100, (mi * 100) / 171));
148
+ const normalized = Math.max(0, Math.min(MI_NORMALIZE_SCALE, (mi * MI_NORMALIZE_SCALE) / MI_BASE));
127
149
  return +normalized.toFixed(1);
128
150
  }
@@ -150,6 +150,38 @@ export function makeDataflowRules(overrides: Partial<DataflowRulesConfig>): Data
150
150
 
151
151
  // ─── AST Helpers ──────────────────────────────────────────────────────────
152
152
 
153
+ /** Compute the span (row count) of a tree-sitter node. */
154
+ function nodeSpan(node: TreeSitterNode): number {
155
+ return node.endPosition.row - node.startPosition.row;
156
+ }
157
+
158
+ /**
159
+ * Recursively search for the narrowest function node at the target line.
160
+ */
161
+ function searchFunctionNode(
162
+ node: TreeSitterNode,
163
+ targetStart: number,
164
+ functionNodeTypes: Set<string>,
165
+ best: TreeSitterNode | null,
166
+ ): TreeSitterNode | null {
167
+ const nodeStart = node.startPosition.row;
168
+ const nodeEnd = node.endPosition.row;
169
+
170
+ // Prune branches outside range
171
+ if (nodeEnd < targetStart || nodeStart > targetStart + 1) return best;
172
+
173
+ if (functionNodeTypes.has(node.type) && nodeStart === targetStart) {
174
+ if (!best || nodeSpan(node) < nodeSpan(best)) {
175
+ best = node;
176
+ }
177
+ }
178
+
179
+ for (let i = 0; i < node.childCount; i++) {
180
+ best = searchFunctionNode(node.child(i)!, targetStart, functionNodeTypes, best);
181
+ }
182
+ return best;
183
+ }
184
+
153
185
  export function findFunctionNode(
154
186
  rootNode: TreeSitterNode,
155
187
  startLine: number,
@@ -158,30 +190,7 @@ export function findFunctionNode(
158
190
  ): TreeSitterNode | null {
159
191
  // tree-sitter lines are 0-indexed
160
192
  const targetStart = startLine - 1;
161
-
162
- let best: TreeSitterNode | null = null;
163
-
164
- function search(node: TreeSitterNode): void {
165
- const nodeStart = node.startPosition.row;
166
- const nodeEnd = node.endPosition.row;
167
-
168
- // Prune branches outside range
169
- if (nodeEnd < targetStart || nodeStart > targetStart + 1) return;
170
-
171
- if (rules.functionNodes.has(node.type) && nodeStart === targetStart) {
172
- // Found a function node at the right position — pick it
173
- if (!best || nodeEnd - nodeStart < best.endPosition.row - best.startPosition.row) {
174
- best = node;
175
- }
176
- }
177
-
178
- for (let i = 0; i < node.childCount; i++) {
179
- search(node.child(i)!);
180
- }
181
- }
182
-
183
- search(rootNode);
184
- return best;
193
+ return searchFunctionNode(rootNode, targetStart, rules.functionNodes, null);
185
194
  }
186
195
 
187
196
  // ─── Extension / Language Mapping ─────────────────────────────────────────
@@ -88,6 +88,41 @@ export function extractParams(
88
88
  return result;
89
89
  }
90
90
 
91
+ /** Extract names from a rest parameter (e.g. `...args`). */
92
+ function extractRestParamNames(node: TreeSitterNode, rules: LanguageRules): string[] {
93
+ const nameNode = node.childForFieldName('name');
94
+ if (nameNode) return [nameNode.text];
95
+ for (const child of node.namedChildren) {
96
+ if (child.type === rules.paramIdentifier) return [child.text];
97
+ }
98
+ return [];
99
+ }
100
+
101
+ /** Extract names from an object destructuring pattern (e.g. `{ a, b: c }`). */
102
+ function extractObjectDestructNames(node: TreeSitterNode, rules: LanguageRules): string[] {
103
+ const names: string[] = [];
104
+ for (const child of node.namedChildren) {
105
+ if (rules.shorthandPropPattern && child.type === rules.shorthandPropPattern) {
106
+ names.push(child.text);
107
+ } else if (rules.pairPatternType && child.type === rules.pairPatternType) {
108
+ const value = child.childForFieldName('value');
109
+ if (value) names.push(...extractParamNames(value, rules));
110
+ } else if (rules.restParamType && child.type === rules.restParamType) {
111
+ names.push(...extractParamNames(child, rules));
112
+ }
113
+ }
114
+ return names;
115
+ }
116
+
117
+ /** Extract names from an array destructuring pattern (e.g. `[a, b]`). */
118
+ function extractArrayDestructNames(node: TreeSitterNode, rules: LanguageRules): string[] {
119
+ const names: string[] = [];
120
+ for (const child of node.namedChildren) {
121
+ names.push(...extractParamNames(child, rules));
122
+ }
123
+ return names;
124
+ }
125
+
91
126
  /**
92
127
  * Extract parameter names from a single parameter node.
93
128
  */
@@ -113,35 +148,15 @@ export function extractParamNames(node: TreeSitterNode | null, rules: LanguageRu
113
148
  }
114
149
 
115
150
  if (rules.restParamType && t === rules.restParamType) {
116
- const nameNode = node.childForFieldName('name');
117
- if (nameNode) return [nameNode.text];
118
- for (const child of node.namedChildren) {
119
- if (child.type === rules.paramIdentifier) return [child.text];
120
- }
121
- return [];
151
+ return extractRestParamNames(node, rules);
122
152
  }
123
153
 
124
154
  if (rules.objectDestructType && t === rules.objectDestructType) {
125
- const names: string[] = [];
126
- for (const child of node.namedChildren) {
127
- if (rules.shorthandPropPattern && child.type === rules.shorthandPropPattern) {
128
- names.push(child.text);
129
- } else if (rules.pairPatternType && child.type === rules.pairPatternType) {
130
- const value = child.childForFieldName('value');
131
- if (value) names.push(...extractParamNames(value, rules));
132
- } else if (rules.restParamType && child.type === rules.restParamType) {
133
- names.push(...extractParamNames(child, rules));
134
- }
135
- }
136
- return names;
155
+ return extractObjectDestructNames(node, rules);
137
156
  }
138
157
 
139
158
  if (rules.arrayDestructType && t === rules.arrayDestructType) {
140
- const names: string[] = [];
141
- for (const child of node.namedChildren) {
142
- names.push(...extractParamNames(child, rules));
143
- }
144
- return names;
159
+ return extractArrayDestructNames(node, rules);
145
160
  }
146
161
 
147
162
  return [];
@@ -155,6 +170,19 @@ export function isIdent(nodeType: string, rules: LanguageRules): boolean {
155
170
  return rules.extraIdentifierTypes ? rules.extraIdentifierTypes.has(nodeType) : false;
156
171
  }
157
172
 
173
+ /** Resolve callee name from an optional chain node (e.g. `obj?.method()`). */
174
+ function resolveOptionalChainCallee(fn: TreeSitterNode, rules: LanguageRules): string | null {
175
+ const target = fn.namedChildren[0];
176
+ if (!target) return null;
177
+ if (target.type === rules.memberNode) {
178
+ const prop = target.childForFieldName(rules.memberPropertyField);
179
+ return prop ? prop.text : null;
180
+ }
181
+ if (target.type === 'identifier') return target.text;
182
+ const prop = fn.childForFieldName(rules.memberPropertyField);
183
+ return prop ? prop.text : null;
184
+ }
185
+
158
186
  /**
159
187
  * Resolve the name a call expression is calling using rules.
160
188
  */
@@ -170,15 +198,7 @@ export function resolveCalleeName(callNode: TreeSitterNode, rules: LanguageRules
170
198
  return prop ? prop.text : null;
171
199
  }
172
200
  if (rules.optionalChainNode && fn.type === rules.optionalChainNode) {
173
- const target = fn.namedChildren[0];
174
- if (!target) return null;
175
- if (target.type === rules.memberNode) {
176
- const prop = target.childForFieldName(rules.memberPropertyField);
177
- return prop ? prop.text : null;
178
- }
179
- if (target.type === 'identifier') return target.text;
180
- const prop = fn.childForFieldName(rules.memberPropertyField);
181
- return prop ? prop.text : null;
201
+ return resolveOptionalChainCallee(fn, rules);
182
202
  }
183
203
  return null;
184
204
  }