@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.
- package/README.md +9 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +95 -87
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/metrics.d.ts +0 -3
- package/dist/ast-analysis/metrics.d.ts.map +1 -1
- package/dist/ast-analysis/metrics.js +30 -13
- package/dist/ast-analysis/metrics.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +24 -19
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +55 -39
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +91 -70
- package/dist/ast-analysis/visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +16 -2
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +29 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +16 -5
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/base.d.ts +10 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +17 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +6 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +77 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +8 -4
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +3 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +26 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +13 -17
- package/dist/domain/analysis/brief.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +14 -11
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +53 -52
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +2 -7
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +33 -31
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts.map +1 -1
- package/dist/domain/analysis/implementations.js +11 -19
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +55 -76
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +7 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
- package/dist/domain/analysis/query-helpers.js +15 -1
- package/dist/domain/analysis/query-helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +255 -105
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +22 -17
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +2 -2
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -0
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +114 -22
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.js +1 -1
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +2 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +170 -75
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +0 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +13 -28
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.js +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +4 -3
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +18 -5
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +29 -18
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -29
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +46 -45
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.js +58 -67
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +58 -60
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/ast.d.ts +16 -14
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +83 -81
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +8 -6
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +69 -72
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +19 -7
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +120 -125
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +136 -137
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +84 -79
- package/dist/features/flow.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +69 -65
- package/dist/features/structure-query.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +70 -55
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +288 -266
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +5 -1
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +6 -4
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/suppress.d.ts +25 -0
- package/dist/infrastructure/suppress.d.ts.map +1 -0
- package/dist/infrastructure/suppress.js +43 -0
- package/dist/infrastructure/suppress.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/dataflow.d.ts.map +1 -1
- package/dist/presentation/dataflow.js +47 -38
- package/dist/presentation/dataflow.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
- package/dist/presentation/diff-impact-mermaid.js +60 -51
- package/dist/presentation/diff-impact-mermaid.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +20 -14
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +15 -13
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +101 -79
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +25 -16
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli/path.js +26 -20
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts +10 -0
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +16 -1
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +18 -12
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/errors.d.ts +5 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +5 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +8 -2
- package/dist/shared/hierarchy.d.ts.map +1 -1
- package/dist/shared/hierarchy.js +42 -1
- package/dist/shared/hierarchy.js.map +1 -1
- package/dist/shared/normalize.d.ts +6 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +20 -12
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +0 -9
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js +0 -15
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +10 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ast-analysis/engine.ts +126 -105
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/shared.ts +33 -24
- package/src/ast-analysis/visitor-utils.ts +52 -32
- package/src/ast-analysis/visitor.ts +132 -71
- package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
- package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/watch.ts +16 -2
- package/src/db/connection.ts +29 -28
- package/src/db/query-builder.ts +15 -3
- package/src/db/repository/base.ts +20 -0
- package/src/db/repository/native-repository.ts +79 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +29 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +67 -76
- package/src/domain/analysis/fn-impact.ts +36 -43
- package/src/domain/analysis/implementations.ts +11 -17
- package/src/domain/analysis/module-map.ts +58 -92
- package/src/domain/analysis/query-helpers.ts +18 -1
- package/src/domain/graph/builder/pipeline.ts +286 -97
- package/src/domain/graph/builder/stages/build-edges.ts +22 -18
- package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
- package/src/domain/graph/builder/stages/finalize.ts +2 -2
- package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
- package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
- package/src/domain/graph/cycles.ts +110 -23
- package/src/domain/graph/resolve.ts +1 -1
- package/src/domain/graph/watcher.ts +202 -96
- package/src/domain/parser.ts +14 -26
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +17 -4
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +45 -46
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/python.ts +31 -25
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +57 -61
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +130 -110
- package/src/features/audit.ts +8 -6
- package/src/features/branch-compare.ts +105 -79
- package/src/features/communities.ts +25 -10
- package/src/features/complexity.ts +171 -134
- package/src/features/dataflow.ts +165 -175
- package/src/features/flow.ts +129 -92
- package/src/features/structure-query.ts +79 -64
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/model.ts +6 -1
- package/src/infrastructure/config.ts +6 -4
- package/src/infrastructure/suppress.ts +47 -0
- package/src/mcp/server.ts +53 -37
- package/src/presentation/dataflow.ts +50 -44
- package/src/presentation/diff-impact-mermaid.ts +104 -62
- package/src/presentation/queries-cli/exports.ts +21 -13
- package/src/presentation/queries-cli/impact.ts +15 -13
- package/src/presentation/queries-cli/inspect.ts +100 -81
- package/src/presentation/queries-cli/overview.ts +26 -16
- package/src/presentation/queries-cli/path.ts +33 -25
- package/src/presentation/result-formatter.ts +19 -1
- package/src/presentation/viewer.ts +42 -14
- package/src/shared/errors.ts +6 -0
- package/src/shared/hierarchy.ts +50 -2
- package/src/shared/normalize.ts +31 -12
- package/src/shared/paginate.ts +0 -17
- 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.
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
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;
|
|
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
|
|
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
|
-
|
|
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 (
|
|
252
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
429
|
+
function setupCfgVisitorForFile(defs: Definition[], langId: string): Visitor | null {
|
|
400
430
|
const cfgRulesForLang = CFG_RULES.get(langId);
|
|
401
|
-
if (!cfgRulesForLang
|
|
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
|
|
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 &&
|
|
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 (
|
|
514
|
-
def
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
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 /
|
|
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
|
-
*
|
|
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 =
|
|
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 +=
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|