@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
|
@@ -20,6 +20,127 @@ import type {
|
|
|
20
20
|
WalkResults,
|
|
21
21
|
} from '../types.js';
|
|
22
22
|
|
|
23
|
+
/** Merge each visitor's custom functionNodeTypes into the master set. */
|
|
24
|
+
function mergeFunctionNodeTypes(visitors: Visitor[], base: Set<string>): Set<string> {
|
|
25
|
+
const merged = new Set(base);
|
|
26
|
+
for (const v of visitors) {
|
|
27
|
+
if (v.functionNodeTypes) {
|
|
28
|
+
for (const t of v.functionNodeTypes) merged.add(t);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return merged;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Initialize all visitors for a given language. */
|
|
35
|
+
function initVisitors(visitors: Visitor[], langId: string): void {
|
|
36
|
+
for (const v of visitors) {
|
|
37
|
+
if (v.init) v.init(langId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Check whether a visitor should be skipped at the current depth. */
|
|
42
|
+
function isSkipped(
|
|
43
|
+
skipDepths: Map<number, number>,
|
|
44
|
+
visitorIndex: number,
|
|
45
|
+
currentDepth: number,
|
|
46
|
+
): boolean {
|
|
47
|
+
const skipAt = skipDepths.get(visitorIndex);
|
|
48
|
+
// Skipped if skip was requested at a shallower (or equal) depth
|
|
49
|
+
// We skip descendants, not the node itself, so skip when currentDepth > skipAt
|
|
50
|
+
return skipAt !== undefined && currentDepth > skipAt;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Dispatch enterFunction hooks to all non-skipped visitors. */
|
|
54
|
+
function dispatchEnterFunction(
|
|
55
|
+
visitors: Visitor[],
|
|
56
|
+
skipDepths: Map<number, number>,
|
|
57
|
+
node: TreeSitterNode,
|
|
58
|
+
funcName: string | null,
|
|
59
|
+
context: VisitorContext,
|
|
60
|
+
depth: number,
|
|
61
|
+
): void {
|
|
62
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
63
|
+
const v = visitors[i]!;
|
|
64
|
+
if (v.enterFunction && !isSkipped(skipDepths, i, depth)) {
|
|
65
|
+
v.enterFunction(node, funcName, context);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Dispatch enterNode hooks and track skipChildren requests. */
|
|
71
|
+
function dispatchEnterNode(
|
|
72
|
+
visitors: Visitor[],
|
|
73
|
+
skipDepths: Map<number, number>,
|
|
74
|
+
node: TreeSitterNode,
|
|
75
|
+
context: VisitorContext,
|
|
76
|
+
depth: number,
|
|
77
|
+
): void {
|
|
78
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
79
|
+
const v = visitors[i]!;
|
|
80
|
+
if (v.enterNode && !isSkipped(skipDepths, i, depth)) {
|
|
81
|
+
const result = v.enterNode(node, context);
|
|
82
|
+
if (result?.skipChildren) {
|
|
83
|
+
skipDepths.set(i, depth);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Dispatch exitNode hooks to all non-skipped visitors. */
|
|
90
|
+
function dispatchExitNode(
|
|
91
|
+
visitors: Visitor[],
|
|
92
|
+
skipDepths: Map<number, number>,
|
|
93
|
+
node: TreeSitterNode,
|
|
94
|
+
context: VisitorContext,
|
|
95
|
+
depth: number,
|
|
96
|
+
): void {
|
|
97
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
98
|
+
const v = visitors[i]!;
|
|
99
|
+
if (v.exitNode && !isSkipped(skipDepths, i, depth)) {
|
|
100
|
+
v.exitNode(node, context);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Clear skip flags for visitors that started skipping at this depth. */
|
|
106
|
+
function clearSkipFlags(
|
|
107
|
+
skipDepths: Map<number, number>,
|
|
108
|
+
visitorCount: number,
|
|
109
|
+
depth: number,
|
|
110
|
+
): void {
|
|
111
|
+
for (let i = 0; i < visitorCount; i++) {
|
|
112
|
+
if (skipDepths.get(i) === depth) {
|
|
113
|
+
skipDepths.delete(i);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Dispatch exitFunction hooks to all non-skipped visitors. */
|
|
119
|
+
function dispatchExitFunction(
|
|
120
|
+
visitors: Visitor[],
|
|
121
|
+
skipDepths: Map<number, number>,
|
|
122
|
+
node: TreeSitterNode,
|
|
123
|
+
funcName: string | null,
|
|
124
|
+
context: VisitorContext,
|
|
125
|
+
depth: number,
|
|
126
|
+
): void {
|
|
127
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
128
|
+
const v = visitors[i]!;
|
|
129
|
+
if (v.exitFunction && !isSkipped(skipDepths, i, depth)) {
|
|
130
|
+
v.exitFunction(node, funcName, context);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Collect finish() results from all visitors into a name-keyed map. */
|
|
136
|
+
function collectResults(visitors: Visitor[]): WalkResults {
|
|
137
|
+
const results: WalkResults = {};
|
|
138
|
+
for (const v of visitors) {
|
|
139
|
+
results[v.name] = v.finish ? v.finish() : undefined;
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
|
|
23
144
|
/**
|
|
24
145
|
* Walk an AST root with multiple visitors in a single DFS pass.
|
|
25
146
|
*
|
|
@@ -44,18 +165,8 @@ export function walkWithVisitors(
|
|
|
44
165
|
getFunctionName = () => null,
|
|
45
166
|
} = options;
|
|
46
167
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
for (const v of visitors) {
|
|
50
|
-
if (v.functionNodeTypes) {
|
|
51
|
-
for (const t of v.functionNodeTypes) allFuncTypes.add(t);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Initialize visitors
|
|
56
|
-
for (const v of visitors) {
|
|
57
|
-
if (v.init) v.init(langId);
|
|
58
|
-
}
|
|
168
|
+
const allFuncTypes = mergeFunctionNodeTypes(visitors, functionNodeTypes);
|
|
169
|
+
initVisitors(visitors, langId);
|
|
59
170
|
|
|
60
171
|
// Shared context object (mutated during walk)
|
|
61
172
|
const scopeStack: ScopeEntry[] = [];
|
|
@@ -66,95 +177,45 @@ export function walkWithVisitors(
|
|
|
66
177
|
scopeStack,
|
|
67
178
|
};
|
|
68
179
|
|
|
69
|
-
// Track which visitors have requested skipChildren at each depth
|
|
70
|
-
// Key: visitor index, Value: depth at which skip was requested
|
|
71
180
|
const skipDepths = new Map<number, number>();
|
|
72
181
|
|
|
73
182
|
function walk(node: TreeSitterNode | null, depth: number): void {
|
|
74
183
|
if (!node) return;
|
|
75
184
|
|
|
76
185
|
const type = node.type;
|
|
77
|
-
const
|
|
186
|
+
const isFuncBoundary = allFuncTypes.has(type);
|
|
78
187
|
let funcName: string | null = null;
|
|
79
188
|
|
|
80
|
-
|
|
81
|
-
if (isFunction) {
|
|
189
|
+
if (isFuncBoundary) {
|
|
82
190
|
funcName = getFunctionName(node);
|
|
83
191
|
context.currentFunction = node;
|
|
84
192
|
scopeStack.push({ funcName, funcNode: node, params: new Map(), locals: new Map() });
|
|
85
|
-
|
|
86
|
-
const v = visitors[i]!;
|
|
87
|
-
if (v.enterFunction && !isSkipped(i, depth)) {
|
|
88
|
-
v.enterFunction(node, funcName, context);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
193
|
+
dispatchEnterFunction(visitors, skipDepths, node, funcName, context, depth);
|
|
91
194
|
}
|
|
92
195
|
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < visitors.length; i++) {
|
|
95
|
-
const v = visitors[i]!;
|
|
96
|
-
if (v.enterNode && !isSkipped(i, depth)) {
|
|
97
|
-
const result = v.enterNode(node, context);
|
|
98
|
-
if (result?.skipChildren) {
|
|
99
|
-
skipDepths.set(i, depth);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
196
|
+
dispatchEnterNode(visitors, skipDepths, node, context, depth);
|
|
103
197
|
|
|
104
|
-
// Nesting tracking
|
|
105
198
|
const addsNesting = nestingNodeTypes.has(type);
|
|
106
199
|
if (addsNesting) context.nestingLevel++;
|
|
107
200
|
|
|
108
|
-
// Recurse children using node.child(i) (all children, not just named)
|
|
109
201
|
for (let i = 0; i < node.childCount; i++) {
|
|
110
202
|
walk(node.child(i), depth + 1);
|
|
111
203
|
}
|
|
112
204
|
|
|
113
|
-
// Undo nesting
|
|
114
205
|
if (addsNesting) context.nestingLevel--;
|
|
115
206
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const v = visitors[i]!;
|
|
119
|
-
if (v.exitNode && !isSkipped(i, depth)) {
|
|
120
|
-
v.exitNode(node, context);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Clear skip for any visitor that started skipping at this depth
|
|
125
|
-
for (let i = 0; i < visitors.length; i++) {
|
|
126
|
-
if (skipDepths.get(i) === depth) {
|
|
127
|
-
skipDepths.delete(i);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
207
|
+
dispatchExitNode(visitors, skipDepths, node, context, depth);
|
|
208
|
+
clearSkipFlags(skipDepths, visitors.length, depth);
|
|
130
209
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
for (let i = 0; i < visitors.length; i++) {
|
|
134
|
-
const v = visitors[i]!;
|
|
135
|
-
if (v.exitFunction && !isSkipped(i, depth)) {
|
|
136
|
-
v.exitFunction(node, funcName, context);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
210
|
+
if (isFuncBoundary) {
|
|
211
|
+
dispatchExitFunction(visitors, skipDepths, node, funcName, context, depth);
|
|
139
212
|
scopeStack.pop();
|
|
140
213
|
context.currentFunction =
|
|
141
214
|
scopeStack.length > 0 ? scopeStack[scopeStack.length - 1]!.funcNode : null;
|
|
142
215
|
}
|
|
143
216
|
}
|
|
144
217
|
|
|
145
|
-
function isSkipped(visitorIndex: number, currentDepth: number): boolean {
|
|
146
|
-
const skipAt = skipDepths.get(visitorIndex);
|
|
147
|
-
// Skipped if skip was requested at a shallower (or equal) depth
|
|
148
|
-
// We skip descendants, not the node itself, so skip when currentDepth > skipAt
|
|
149
|
-
return skipAt !== undefined && currentDepth > skipAt;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
218
|
walk(rootNode, 0);
|
|
153
219
|
|
|
154
|
-
|
|
155
|
-
const results: WalkResults = {};
|
|
156
|
-
for (const v of visitors) {
|
|
157
|
-
results[v.name] = v.finish ? v.finish() : undefined;
|
|
158
|
-
}
|
|
159
|
-
return results;
|
|
220
|
+
return collectResults(visitors);
|
|
160
221
|
}
|
|
@@ -44,33 +44,33 @@ function extractExpressionText(node: TreeSitterNode): string | null {
|
|
|
44
44
|
return truncate(node.text);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
if (child.type === 'identifier') return child.text;
|
|
47
|
+
/** Extract the name from a throw statement's child nodes. */
|
|
48
|
+
function extractThrowName(node: TreeSitterNode): string | null {
|
|
49
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
50
|
+
const child = node.child(i);
|
|
51
|
+
if (!child) continue;
|
|
52
|
+
if (child.type === 'new_expression') return extractNewName(child);
|
|
53
|
+
if (child.type === 'call_expression') {
|
|
54
|
+
const fn = child.childForFieldName('function');
|
|
55
|
+
return fn ? fn.text : child.text?.split('(')[0] || '?';
|
|
58
56
|
}
|
|
59
|
-
|
|
57
|
+
if (child.type === 'identifier') return child.text;
|
|
60
58
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
return truncate(node.text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Extract the name from an await expression's child nodes. */
|
|
63
|
+
function extractAwaitName(node: TreeSitterNode): string | null {
|
|
64
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
65
|
+
const child = node.child(i);
|
|
66
|
+
if (!child) continue;
|
|
67
|
+
if (child.type === 'call_expression') {
|
|
68
|
+
const fn = child.childForFieldName('function');
|
|
69
|
+
return fn ? fn.text : child.text?.split('(')[0] || '?';
|
|
70
|
+
}
|
|
71
|
+
if (child.type === 'identifier' || child.type === 'member_expression') {
|
|
72
|
+
return child.text;
|
|
72
73
|
}
|
|
73
|
-
return truncate(node.text);
|
|
74
74
|
}
|
|
75
75
|
return truncate(node.text);
|
|
76
76
|
}
|
|
@@ -102,40 +102,43 @@ export function createAstStoreVisitor(
|
|
|
102
102
|
return nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
function resolveNameAndText(
|
|
106
|
+
node: TreeSitterNode,
|
|
107
|
+
kind: string,
|
|
108
|
+
): { name: string | null | undefined; text: string | null; skip?: boolean } {
|
|
109
|
+
switch (kind) {
|
|
110
|
+
case 'new':
|
|
111
|
+
return { name: extractNewName(node), text: truncate(node.text) };
|
|
112
|
+
case 'throw':
|
|
113
|
+
return { name: extractThrowName(node), text: extractExpressionText(node) };
|
|
114
|
+
case 'await':
|
|
115
|
+
return { name: extractAwaitName(node), text: extractExpressionText(node) };
|
|
116
|
+
case 'string': {
|
|
117
|
+
const content = node.text?.replace(/^['"`]|['"`]$/g, '') || '';
|
|
118
|
+
if (content.length < 2) return { name: null, text: null, skip: true };
|
|
119
|
+
return { name: truncate(content, 100), text: truncate(node.text) };
|
|
120
|
+
}
|
|
121
|
+
case 'regex':
|
|
122
|
+
return { name: node.text || '?', text: truncate(node.text) };
|
|
123
|
+
default:
|
|
124
|
+
return { name: undefined, text: null };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
105
128
|
function collectNode(node: TreeSitterNode, kind: string): void {
|
|
106
129
|
if (matched.has(node.id)) return;
|
|
107
130
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
let text: string | null = null;
|
|
111
|
-
|
|
112
|
-
if (kind === 'new') {
|
|
113
|
-
name = extractNewName(node);
|
|
114
|
-
text = truncate(node.text);
|
|
115
|
-
} else if (kind === 'throw') {
|
|
116
|
-
name = extractName('throw', node);
|
|
117
|
-
text = extractExpressionText(node);
|
|
118
|
-
} else if (kind === 'await') {
|
|
119
|
-
name = extractName('await', node);
|
|
120
|
-
text = extractExpressionText(node);
|
|
121
|
-
} else if (kind === 'string') {
|
|
122
|
-
const content = node.text?.replace(/^['"`]|['"`]$/g, '') || '';
|
|
123
|
-
if (content.length < 2) return;
|
|
124
|
-
name = truncate(content, 100);
|
|
125
|
-
text = truncate(node.text);
|
|
126
|
-
} else if (kind === 'regex') {
|
|
127
|
-
name = node.text || '?';
|
|
128
|
-
text = truncate(node.text);
|
|
129
|
-
}
|
|
131
|
+
const resolved = resolveNameAndText(node, kind);
|
|
132
|
+
if (resolved.skip) return;
|
|
130
133
|
|
|
131
134
|
rows.push({
|
|
132
135
|
file: relPath,
|
|
133
|
-
line,
|
|
136
|
+
line: node.startPosition.row + 1,
|
|
134
137
|
kind,
|
|
135
|
-
name,
|
|
136
|
-
text,
|
|
138
|
+
name: resolved.name,
|
|
139
|
+
text: resolved.text,
|
|
137
140
|
receiver: null,
|
|
138
|
-
parentNodeId: resolveParentNodeId(
|
|
141
|
+
parentNodeId: resolveParentNodeId(node.startPosition.row + 1),
|
|
139
142
|
});
|
|
140
143
|
|
|
141
144
|
matched.add(node.id);
|
|
@@ -156,6 +156,27 @@ function resetAccumulators(hRules: AnyRules | null | undefined): ComplexityAcc {
|
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/** Classify a single node for all complexity metrics (Halstead, branching, logical ops, etc.). */
|
|
160
|
+
function classifyNode(
|
|
161
|
+
node: TreeSitterNode,
|
|
162
|
+
nestingLevel: number,
|
|
163
|
+
cRules: AnyRules,
|
|
164
|
+
hRules: AnyRules | null | undefined,
|
|
165
|
+
acc: ComplexityAcc,
|
|
166
|
+
): void {
|
|
167
|
+
const type = node.type;
|
|
168
|
+
|
|
169
|
+
if (hRules) classifyHalstead(node, hRules, acc);
|
|
170
|
+
if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
|
|
171
|
+
if (type === cRules.logicalNodeType) classifyLogicalOp(node, cRules, acc);
|
|
172
|
+
if (type === cRules.optionalChainType) acc.cyclomatic++;
|
|
173
|
+
if (cRules.branchNodes.has(type) && node.childCount > 0) {
|
|
174
|
+
classifyBranchNode(node, type, nestingLevel, cRules, acc);
|
|
175
|
+
}
|
|
176
|
+
classifyPlainElse(node, type, cRules, acc);
|
|
177
|
+
if (cRules.caseNodes.has(type) && node.childCount > 0) acc.cyclomatic++;
|
|
178
|
+
}
|
|
179
|
+
|
|
159
180
|
export function createComplexityVisitor(
|
|
160
181
|
cRules: AnyRules,
|
|
161
182
|
hRules?: AnyRules | null,
|
|
@@ -178,15 +199,11 @@ export function createComplexityVisitor(
|
|
|
178
199
|
funcName: string | null,
|
|
179
200
|
_context: VisitorContext,
|
|
180
201
|
): void {
|
|
181
|
-
if (fileLevelWalk) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
funcDepth = 0;
|
|
187
|
-
} else {
|
|
188
|
-
funcDepth++;
|
|
189
|
-
}
|
|
202
|
+
if (fileLevelWalk && !activeFuncNode) {
|
|
203
|
+
acc = resetAccumulators(hRules);
|
|
204
|
+
activeFuncNode = funcNode;
|
|
205
|
+
activeFuncName = funcName;
|
|
206
|
+
funcDepth = 0;
|
|
190
207
|
} else {
|
|
191
208
|
funcDepth++;
|
|
192
209
|
}
|
|
@@ -197,18 +214,14 @@ export function createComplexityVisitor(
|
|
|
197
214
|
_funcName: string | null,
|
|
198
215
|
_context: VisitorContext,
|
|
199
216
|
): void {
|
|
200
|
-
if (fileLevelWalk) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
activeFuncName = null;
|
|
209
|
-
} else {
|
|
210
|
-
funcDepth--;
|
|
211
|
-
}
|
|
217
|
+
if (fileLevelWalk && funcNode === activeFuncNode) {
|
|
218
|
+
results.push({
|
|
219
|
+
funcNode,
|
|
220
|
+
funcName: activeFuncName,
|
|
221
|
+
metrics: collectResult(funcNode, acc, hRules, langId),
|
|
222
|
+
});
|
|
223
|
+
activeFuncNode = null;
|
|
224
|
+
activeFuncName = null;
|
|
212
225
|
} else {
|
|
213
226
|
funcDepth--;
|
|
214
227
|
}
|
|
@@ -217,26 +230,8 @@ export function createComplexityVisitor(
|
|
|
217
230
|
enterNode(node: TreeSitterNode, context: VisitorContext): EnterNodeResult | undefined {
|
|
218
231
|
if (fileLevelWalk && !activeFuncNode) return;
|
|
219
232
|
|
|
220
|
-
const type = node.type;
|
|
221
233
|
const nestingLevel = fileLevelWalk ? context.nestingLevel + funcDepth : context.nestingLevel;
|
|
222
|
-
|
|
223
|
-
if (hRules) classifyHalstead(node, hRules, acc);
|
|
224
|
-
|
|
225
|
-
if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
|
|
226
|
-
|
|
227
|
-
if (type === cRules.logicalNodeType) {
|
|
228
|
-
classifyLogicalOp(node, cRules, acc);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (type === cRules.optionalChainType) acc.cyclomatic++;
|
|
232
|
-
|
|
233
|
-
if (cRules.branchNodes.has(type) && node.childCount > 0) {
|
|
234
|
-
classifyBranchNode(node, type, nestingLevel, cRules, acc);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
classifyPlainElse(node, type, cRules, acc);
|
|
238
|
-
|
|
239
|
-
if (cRules.caseNodes.has(type) && node.childCount > 0) acc.cyclomatic++;
|
|
234
|
+
classifyNode(node, nestingLevel, cRules, hRules, acc);
|
|
240
235
|
},
|
|
241
236
|
|
|
242
237
|
exitNode(node: TreeSitterNode): void {
|
|
@@ -116,14 +116,13 @@ function isCall(node: TreeSitterNode | null, isCallNode: (t: string) => boolean)
|
|
|
116
116
|
return node != null && isCallNode(node.type);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
/** Resolve the value node from a variable declarator, trying multiple strategies. */
|
|
120
|
+
function resolveValueNode(
|
|
120
121
|
node: TreeSitterNode,
|
|
122
|
+
nameNode: TreeSitterNode | null,
|
|
121
123
|
rules: AnyRules,
|
|
122
|
-
scopeStack: ScopeEntry[],
|
|
123
|
-
assignments: DataflowAssignment[],
|
|
124
124
|
isCallNode: (t: string) => boolean,
|
|
125
|
-
):
|
|
126
|
-
let nameNode = node.childForFieldName(rules.varNameField);
|
|
125
|
+
): TreeSitterNode | null {
|
|
127
126
|
let valueNode: TreeSitterNode | null = rules.varValueField
|
|
128
127
|
? node.childForFieldName(rules.varValueField)
|
|
129
128
|
: null;
|
|
@@ -146,52 +145,97 @@ function handleVarDeclarator(
|
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
return valueNode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Unwrap expression-list wrappers from name/value nodes. */
|
|
152
|
+
function unwrapExpressionList(
|
|
153
|
+
nameNode: TreeSitterNode | null,
|
|
154
|
+
valueNode: TreeSitterNode | null,
|
|
155
|
+
rules: AnyRules,
|
|
156
|
+
): { name: TreeSitterNode | null; value: TreeSitterNode | null } {
|
|
157
|
+
if (!rules.expressionListType) return { name: nameNode, value: valueNode };
|
|
158
|
+
const name =
|
|
159
|
+
nameNode && nameNode.type === rules.expressionListType
|
|
160
|
+
? (nameNode.namedChildren[0] ?? null)
|
|
161
|
+
: nameNode;
|
|
162
|
+
const value =
|
|
163
|
+
valueNode && valueNode.type === rules.expressionListType
|
|
164
|
+
? (valueNode.namedChildren[0] ?? null)
|
|
165
|
+
: valueNode;
|
|
166
|
+
return { name, value };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Record a destructured call assignment (object or array destructuring). */
|
|
170
|
+
function recordDestructuredAssignment(
|
|
171
|
+
nameNode: TreeSitterNode,
|
|
172
|
+
node: TreeSitterNode,
|
|
173
|
+
callee: string,
|
|
174
|
+
scope: ScopeEntry,
|
|
175
|
+
assignments: DataflowAssignment[],
|
|
176
|
+
rules: AnyRules,
|
|
177
|
+
): void {
|
|
178
|
+
const names = extractParamNames(nameNode, rules);
|
|
179
|
+
for (const n of names) {
|
|
180
|
+
assignments.push({
|
|
181
|
+
varName: n,
|
|
182
|
+
callerFunc: scope.funcName!,
|
|
183
|
+
sourceCallName: callee,
|
|
184
|
+
expression: truncate(node.text),
|
|
185
|
+
line: node.startPosition.row + 1,
|
|
186
|
+
});
|
|
187
|
+
scope.locals.set(n, { type: 'destructured', callee });
|
|
154
188
|
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Record a simple (non-destructured) call assignment. */
|
|
192
|
+
function recordSimpleAssignment(
|
|
193
|
+
nameNode: TreeSitterNode,
|
|
194
|
+
node: TreeSitterNode,
|
|
195
|
+
callee: string,
|
|
196
|
+
scope: ScopeEntry,
|
|
197
|
+
assignments: DataflowAssignment[],
|
|
198
|
+
): void {
|
|
199
|
+
const varName = nameNode.text;
|
|
200
|
+
assignments.push({
|
|
201
|
+
varName,
|
|
202
|
+
callerFunc: scope.funcName!,
|
|
203
|
+
sourceCallName: callee,
|
|
204
|
+
expression: truncate(node.text),
|
|
205
|
+
line: node.startPosition.row + 1,
|
|
206
|
+
});
|
|
207
|
+
scope.locals.set(varName, { type: 'call_return', callee });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function handleVarDeclarator(
|
|
211
|
+
node: TreeSitterNode,
|
|
212
|
+
rules: AnyRules,
|
|
213
|
+
scopeStack: ScopeEntry[],
|
|
214
|
+
assignments: DataflowAssignment[],
|
|
215
|
+
isCallNode: (t: string) => boolean,
|
|
216
|
+
): void {
|
|
217
|
+
const rawName = node.childForFieldName(rules.varNameField);
|
|
218
|
+
const rawValue = resolveValueNode(node, rawName, rules, isCallNode);
|
|
219
|
+
const { name: nameNode, value: valueNode } = unwrapExpressionList(rawName, rawValue, rules);
|
|
155
220
|
|
|
156
221
|
const scope = currentScope(scopeStack);
|
|
157
222
|
if (!nameNode || !valueNode || !scope) return;
|
|
158
223
|
|
|
159
224
|
const unwrapped = unwrapAwait(valueNode, rules);
|
|
160
225
|
const callExpr = isCall(unwrapped, isCallNode) ? unwrapped : null;
|
|
226
|
+
if (!callExpr) return;
|
|
161
227
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
callerFunc: scope.funcName,
|
|
174
|
-
sourceCallName: callee,
|
|
175
|
-
expression: truncate(node.text),
|
|
176
|
-
line: node.startPosition.row + 1,
|
|
177
|
-
});
|
|
178
|
-
scope.locals.set(n, { type: 'destructured', callee });
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
const varName =
|
|
182
|
-
nameNode.type === 'identifier' || nameNode.type === rules.paramIdentifier
|
|
183
|
-
? nameNode.text
|
|
184
|
-
: nameNode.text;
|
|
185
|
-
assignments.push({
|
|
186
|
-
varName,
|
|
187
|
-
callerFunc: scope.funcName,
|
|
188
|
-
sourceCallName: callee,
|
|
189
|
-
expression: truncate(node.text),
|
|
190
|
-
line: node.startPosition.row + 1,
|
|
191
|
-
});
|
|
192
|
-
scope.locals.set(varName, { type: 'call_return', callee });
|
|
193
|
-
}
|
|
194
|
-
}
|
|
228
|
+
const callee = resolveCalleeName(callExpr, rules);
|
|
229
|
+
if (!callee || !scope.funcName) return;
|
|
230
|
+
|
|
231
|
+
const isDestructured =
|
|
232
|
+
(rules.objectDestructType && nameNode.type === rules.objectDestructType) ||
|
|
233
|
+
(rules.arrayDestructType && nameNode.type === rules.arrayDestructType);
|
|
234
|
+
|
|
235
|
+
if (isDestructured) {
|
|
236
|
+
recordDestructuredAssignment(nameNode, node, callee, scope, assignments, rules);
|
|
237
|
+
} else {
|
|
238
|
+
recordSimpleAssignment(nameNode, node, callee, scope, assignments);
|
|
195
239
|
}
|
|
196
240
|
}
|
|
197
241
|
|
|
@@ -5,9 +5,23 @@ import type { CommandDefinition } from '../types.js';
|
|
|
5
5
|
export const command: CommandDefinition = {
|
|
6
6
|
name: 'watch [dir]',
|
|
7
7
|
description: 'Watch project for file changes and incrementally update the graph',
|
|
8
|
-
|
|
8
|
+
options: [
|
|
9
|
+
['--poll', 'Use stat-based polling (default on Windows to avoid ReFS/Dev Drive crashes)'],
|
|
10
|
+
['--native', 'Force native OS file watchers instead of polling'],
|
|
11
|
+
['--poll-interval <ms>', 'Polling interval in milliseconds (default: 2000)'],
|
|
12
|
+
],
|
|
13
|
+
async execute([dir], opts, ctx) {
|
|
9
14
|
const root = path.resolve(dir || '.');
|
|
10
15
|
const engine = ctx.program.opts().engine;
|
|
11
|
-
|
|
16
|
+
if (opts.poll && opts.native) {
|
|
17
|
+
ctx.program.error('--poll and --native are mutually exclusive');
|
|
18
|
+
}
|
|
19
|
+
// Explicit --poll or --native wins; otherwise let watcher auto-detect by platform
|
|
20
|
+
const poll = opts.poll ? true : opts.native ? false : undefined;
|
|
21
|
+
await watchProject(root, {
|
|
22
|
+
engine,
|
|
23
|
+
poll,
|
|
24
|
+
pollInterval: opts.pollInterval ? Number(opts.pollInterval) : undefined,
|
|
25
|
+
});
|
|
12
26
|
},
|
|
13
27
|
};
|