@optave/codegraph 3.10.0 → 3.11.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 +40 -33
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +77 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.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 +78 -62
- package/dist/ast-analysis/visitors/ast-store-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 +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/check.js +1 -1
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/children.js +1 -1
- package/dist/cli/commands/children.js.map +1 -1
- package/dist/cli/commands/diff-impact.js +1 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/cli/commands/roles.js +1 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/cli/commands/structure.js +1 -1
- package/dist/cli/commands/structure.js.map +1 -1
- package/dist/cli/shared/options.js +1 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +8 -0
- package/dist/db/connection.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -6
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +148 -99
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +23 -637
- 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 +141 -98
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +73 -22
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +28 -20
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +153 -80
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +18 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +72 -4
- 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 +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +209 -137
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/c.js +25 -6
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/cpp.js +47 -6
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.js +90 -14
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +108 -4
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/erlang.js +56 -20
- package/dist/extractors/erlang.js.map +1 -1
- package/dist/extractors/fsharp.d.ts +7 -0
- package/dist/extractors/fsharp.d.ts.map +1 -1
- package/dist/extractors/fsharp.js +94 -0
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +29 -33
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/groovy.js +41 -1
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/haskell.js +48 -4
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +198 -74
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/kotlin.js +4 -0
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +184 -47
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/python.js +7 -4
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +103 -87
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/scala.d.ts.map +1 -1
- package/dist/extractors/scala.js +18 -32
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +55 -69
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/verilog.js +80 -15
- package/dist/extractors/verilog.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.js +28 -36
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +4 -0
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -0
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.js +1 -0
- package/dist/mcp/tools/semantic-search.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-fsharp_signature.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +10 -10
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/rules/index.ts +87 -0
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/children.ts +1 -1
- package/src/cli/commands/diff-impact.ts +1 -1
- package/src/cli/commands/embed.ts +54 -4
- package/src/cli/commands/roles.ts +1 -1
- package/src/cli/commands/structure.ts +1 -1
- package/src/cli/shared/options.ts +1 -1
- package/src/db/connection.ts +8 -0
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +223 -131
- package/src/domain/graph/builder/pipeline.ts +32 -785
- package/src/domain/graph/builder/stages/build-edges.ts +207 -142
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +29 -25
- package/src/domain/parser.ts +170 -67
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +75 -4
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +243 -153
- package/src/extractors/c.ts +27 -8
- package/src/extractors/cpp.ts +50 -8
- package/src/extractors/cuda.ts +90 -16
- package/src/extractors/elixir.ts +103 -4
- package/src/extractors/erlang.ts +63 -20
- package/src/extractors/fsharp.ts +104 -0
- package/src/extractors/gleam.ts +40 -39
- package/src/extractors/groovy.ts +45 -1
- package/src/extractors/haskell.ts +45 -4
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +191 -77
- package/src/extractors/kotlin.ts +4 -0
- package/src/extractors/objc.ts +171 -47
- package/src/extractors/python.ts +5 -3
- package/src/extractors/r.ts +104 -82
- package/src/extractors/scala.ts +24 -36
- package/src/extractors/solidity.ts +59 -78
- package/src/extractors/verilog.ts +83 -15
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +56 -56
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/mcp/tool-registry.ts +5 -0
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +16 -1
package/src/extractors/gleam.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
|
|
2
|
+
import {
|
|
3
|
+
findChild,
|
|
4
|
+
findFirstChildOfTypes,
|
|
5
|
+
nodeEndLine,
|
|
6
|
+
nodeStartLine,
|
|
7
|
+
pushCall,
|
|
8
|
+
pushImport,
|
|
9
|
+
stripQuotes,
|
|
10
|
+
} from './helpers.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Extract symbols from Gleam files.
|
|
@@ -74,7 +76,7 @@ function handleFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
74
76
|
ctx.definitions.push({
|
|
75
77
|
name: nameNode.text,
|
|
76
78
|
kind: 'function',
|
|
77
|
-
line: node
|
|
79
|
+
line: nodeStartLine(node),
|
|
78
80
|
endLine: nodeEndLine(node),
|
|
79
81
|
visibility,
|
|
80
82
|
children: params.length > 0 ? params : undefined,
|
|
@@ -85,12 +87,15 @@ function handleExternalFunction(node: TreeSitterNode, ctx: ExtractorOutput): voi
|
|
|
85
87
|
const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
|
|
86
88
|
if (!nameNode) return;
|
|
87
89
|
|
|
90
|
+
const params = extractParams(node);
|
|
91
|
+
|
|
88
92
|
ctx.definitions.push({
|
|
89
93
|
name: nameNode.text,
|
|
90
94
|
kind: 'function',
|
|
91
|
-
line: node
|
|
95
|
+
line: nodeStartLine(node),
|
|
92
96
|
endLine: nodeEndLine(node),
|
|
93
97
|
visibility: isPublic(node) ? 'public' : 'private',
|
|
98
|
+
children: params.length > 0 ? params : undefined,
|
|
94
99
|
});
|
|
95
100
|
}
|
|
96
101
|
|
|
@@ -104,10 +109,7 @@ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
104
109
|
const child = node.child(i);
|
|
105
110
|
if (!child) continue;
|
|
106
111
|
if (child.type === 'data_constructor' || child.type === 'type_constructor') {
|
|
107
|
-
|
|
108
|
-
if (ctorName) {
|
|
109
|
-
children.push({ name: ctorName.text, kind: 'property', line: child.startPosition.row + 1 });
|
|
110
|
-
}
|
|
112
|
+
pushConstructor(child, children);
|
|
111
113
|
}
|
|
112
114
|
// Recurse into constructors block
|
|
113
115
|
if (child.type === 'data_constructors' || child.type === 'type_constructors') {
|
|
@@ -115,14 +117,7 @@ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
115
117
|
const ctor = child.child(j);
|
|
116
118
|
if (!ctor) continue;
|
|
117
119
|
if (ctor.type === 'data_constructor' || ctor.type === 'type_constructor') {
|
|
118
|
-
|
|
119
|
-
if (ctorName) {
|
|
120
|
-
children.push({
|
|
121
|
-
name: ctorName.text,
|
|
122
|
-
kind: 'property',
|
|
123
|
-
line: ctor.startPosition.row + 1,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
120
|
+
pushConstructor(ctor, children);
|
|
126
121
|
}
|
|
127
122
|
}
|
|
128
123
|
}
|
|
@@ -131,13 +126,20 @@ function handleTypeDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
131
126
|
ctx.definitions.push({
|
|
132
127
|
name: nameNode.text,
|
|
133
128
|
kind: 'type',
|
|
134
|
-
line: node
|
|
129
|
+
line: nodeStartLine(node),
|
|
135
130
|
endLine: nodeEndLine(node),
|
|
136
131
|
visibility: isPublic(node) ? 'public' : 'private',
|
|
137
132
|
children: children.length > 0 ? children : undefined,
|
|
138
133
|
});
|
|
139
134
|
}
|
|
140
135
|
|
|
136
|
+
function pushConstructor(ctorNode: TreeSitterNode, out: SubDeclaration[]): void {
|
|
137
|
+
const ctorName = ctorNode.childForFieldName('name') || findChild(ctorNode, 'constructor_name');
|
|
138
|
+
if (ctorName) {
|
|
139
|
+
out.push({ name: ctorName.text, kind: 'property', line: nodeStartLine(ctorNode) });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
141
143
|
function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
142
144
|
const nameNode = node.childForFieldName('name') || findChild(node, 'type_name');
|
|
143
145
|
if (!nameNode) return;
|
|
@@ -145,7 +147,7 @@ function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
145
147
|
ctx.definitions.push({
|
|
146
148
|
name: nameNode.text,
|
|
147
149
|
kind: 'type',
|
|
148
|
-
line: node
|
|
150
|
+
line: nodeStartLine(node),
|
|
149
151
|
endLine: nodeEndLine(node),
|
|
150
152
|
visibility: isPublic(node) ? 'public' : 'private',
|
|
151
153
|
});
|
|
@@ -158,7 +160,7 @@ function handleConstant(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
158
160
|
ctx.definitions.push({
|
|
159
161
|
name: nameNode.text,
|
|
160
162
|
kind: 'variable',
|
|
161
|
-
line: node
|
|
163
|
+
line: nodeStartLine(node),
|
|
162
164
|
endLine: nodeEndLine(node),
|
|
163
165
|
visibility: isPublic(node) ? 'public' : 'private',
|
|
164
166
|
});
|
|
@@ -166,7 +168,7 @@ function handleConstant(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
166
168
|
|
|
167
169
|
function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
168
170
|
const moduleNode =
|
|
169
|
-
node.childForFieldName('module') ||
|
|
171
|
+
node.childForFieldName('module') || findFirstChildOfTypes(node, ['module', 'string']);
|
|
170
172
|
if (!moduleNode) return;
|
|
171
173
|
|
|
172
174
|
const source = stripQuotes(moduleNode.text);
|
|
@@ -190,26 +192,25 @@ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
190
192
|
names.push(alias.text);
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
line: node.startPosition.row + 1,
|
|
197
|
-
});
|
|
195
|
+
// `pushImport` falls back to the source basename when `names` is empty,
|
|
196
|
+
// preserving the previous `source.split('/').pop() || source` default.
|
|
197
|
+
pushImport(ctx, node, source, names);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
201
|
-
const funcNode = node.childForFieldName('function') || node.
|
|
201
|
+
const funcNode = node.childForFieldName('function') || node.namedChild(0);
|
|
202
202
|
if (!funcNode) return;
|
|
203
203
|
|
|
204
204
|
if (funcNode.type === 'identifier' || funcNode.type === 'variable') {
|
|
205
|
-
ctx
|
|
205
|
+
pushCall(ctx, node, funcNode.text);
|
|
206
206
|
} else if (funcNode.type === 'field_access' || funcNode.type === 'module_select') {
|
|
207
207
|
const field = funcNode.childForFieldName('field') || funcNode.childForFieldName('label');
|
|
208
|
-
|
|
208
|
+
// Prefer the `record` field; fall back to first named child to skip
|
|
209
|
+
// anonymous punctuation tokens (the `.` between record and field).
|
|
210
|
+
const record = funcNode.childForFieldName('record') || funcNode.namedChild(0);
|
|
209
211
|
if (field) {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
ctx.calls.push(call);
|
|
212
|
+
const receiver = record && record !== field ? record.text : undefined;
|
|
213
|
+
pushCall(ctx, node, field.text, receiver !== undefined ? { receiver } : {});
|
|
213
214
|
}
|
|
214
215
|
}
|
|
215
216
|
}
|
|
@@ -226,11 +227,11 @@ function extractParams(funcNode: TreeSitterNode): SubDeclaration[] {
|
|
|
226
227
|
if (param.type === 'function_parameter' || param.type === 'parameter') {
|
|
227
228
|
const nameNode = param.childForFieldName('name') || findChild(param, 'identifier');
|
|
228
229
|
if (nameNode) {
|
|
229
|
-
params.push({ name: nameNode.text, kind: 'parameter', line: param
|
|
230
|
+
params.push({ name: nameNode.text, kind: 'parameter', line: nodeStartLine(param) });
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
if (param.type === 'identifier') {
|
|
233
|
-
params.push({ name: param.text, kind: 'parameter', line: param
|
|
234
|
+
params.push({ name: param.text, kind: 'parameter', line: nodeStartLine(param) });
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
return params;
|
package/src/extractors/groovy.ts
CHANGED
|
@@ -68,6 +68,7 @@ function walkGroovyNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
68
68
|
case 'method_invocation':
|
|
69
69
|
case 'call_expression':
|
|
70
70
|
case 'function_call':
|
|
71
|
+
case 'juxt_function_call':
|
|
71
72
|
handleGroovyCallExpr(node, ctx);
|
|
72
73
|
break;
|
|
73
74
|
case 'object_creation_expression':
|
|
@@ -140,14 +141,57 @@ function handleGroovyClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
140
141
|
function handleGroovyInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
141
142
|
const nameNode = node.childForFieldName('name');
|
|
142
143
|
if (!nameNode) return;
|
|
144
|
+
const ifaceName = nameNode.text;
|
|
143
145
|
|
|
144
146
|
ctx.definitions.push({
|
|
145
|
-
name:
|
|
147
|
+
name: ifaceName,
|
|
146
148
|
kind: 'interface',
|
|
147
149
|
line: node.startPosition.row + 1,
|
|
148
150
|
endLine: nodeEndLine(node),
|
|
149
151
|
visibility: extractModifierVisibility(node),
|
|
150
152
|
});
|
|
153
|
+
|
|
154
|
+
// `interface X extends Y, Z` — tree-sitter-groovy 0.1.x exposes parent
|
|
155
|
+
// interfaces via an unnamed `extends_interfaces` child (not a field), which
|
|
156
|
+
// wraps a `type_list` of `_type` nodes. Mirrors the Rust extractor.
|
|
157
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
158
|
+
const child = node.child(i);
|
|
159
|
+
if (child && child.type === 'extends_interfaces') {
|
|
160
|
+
collectGroovyParentInterfaces(child, ifaceName, ctx);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function collectGroovyParentInterfaces(
|
|
167
|
+
parent: TreeSitterNode,
|
|
168
|
+
name: string,
|
|
169
|
+
ctx: ExtractorOutput,
|
|
170
|
+
): void {
|
|
171
|
+
// Use the current node's start line at each recursion level — matches the
|
|
172
|
+
// Rust `collect_interfaces` helper, which re-evaluates `start_line(interfaces)`
|
|
173
|
+
// for whatever node (`extends_interfaces` → `type_list`) is being processed.
|
|
174
|
+
const line = parent.startPosition.row + 1;
|
|
175
|
+
for (let i = 0; i < parent.childCount; i++) {
|
|
176
|
+
const child = parent.child(i);
|
|
177
|
+
if (!child) continue;
|
|
178
|
+
switch (child.type) {
|
|
179
|
+
case 'type_identifier':
|
|
180
|
+
case 'identifier':
|
|
181
|
+
case 'scoped_type_identifier': {
|
|
182
|
+
ctx.classes.push({ name, implements: child.text, line });
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'generic_type': {
|
|
186
|
+
const inner = child.child(0)?.text;
|
|
187
|
+
if (inner) ctx.classes.push({ name, implements: inner, line });
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case 'type_list':
|
|
191
|
+
collectGroovyParentInterfaces(child, name, ctx);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
151
195
|
}
|
|
152
196
|
|
|
153
197
|
function handleGroovyEnumDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
@@ -81,19 +81,60 @@ function extractHaskellParams(funcNode: TreeSitterNode): SubDeclaration[] {
|
|
|
81
81
|
if (child.type === 'patterns' || child.type === 'parameter') {
|
|
82
82
|
for (let j = 0; j < child.childCount; j++) {
|
|
83
83
|
const pat = child.child(j);
|
|
84
|
-
if (pat
|
|
85
|
-
params.push({ name: pat.text, kind: 'parameter', line: pat.startPosition.row + 1 });
|
|
86
|
-
}
|
|
84
|
+
if (pat) collectHaskellPatternBindings(pat, params);
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
if (child.type === 'variable' && i > 0) {
|
|
90
|
-
// Pattern parameters after the function name
|
|
88
|
+
// Pattern parameters after the function name (no enclosing `patterns` node)
|
|
91
89
|
params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
return params;
|
|
95
93
|
}
|
|
96
94
|
|
|
95
|
+
// Walk a pattern node and emit each bound variable (and `_` for wildcards) as a parameter.
|
|
96
|
+
// Container patterns — parens, constructor application, infix (cons), tuple, list, as, strict,
|
|
97
|
+
// irrefutable, qualified — are transparent: descend into their children. `record` is special:
|
|
98
|
+
// only the right-hand-side of each `field_pattern` is bound (the field name is not).
|
|
99
|
+
// Literals, bare constructors, and operators do not bind.
|
|
100
|
+
function collectHaskellPatternBindings(node: TreeSitterNode, out: SubDeclaration[]): void {
|
|
101
|
+
switch (node.type) {
|
|
102
|
+
case 'variable':
|
|
103
|
+
case 'identifier':
|
|
104
|
+
out.push({ name: node.text, kind: 'parameter', line: node.startPosition.row + 1 });
|
|
105
|
+
return;
|
|
106
|
+
case 'wildcard':
|
|
107
|
+
out.push({ name: '_', kind: 'parameter', line: node.startPosition.row + 1 });
|
|
108
|
+
return;
|
|
109
|
+
case 'parens':
|
|
110
|
+
case 'apply':
|
|
111
|
+
case 'infix':
|
|
112
|
+
case 'tuple':
|
|
113
|
+
case 'list':
|
|
114
|
+
case 'strict':
|
|
115
|
+
case 'irrefutable':
|
|
116
|
+
case 'as':
|
|
117
|
+
case 'qualified':
|
|
118
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
119
|
+
const c = node.child(i);
|
|
120
|
+
if (c) collectHaskellPatternBindings(c, out);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
case 'record':
|
|
124
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
125
|
+
const fp = node.child(i);
|
|
126
|
+
if (!fp || fp.type !== 'field_pattern') continue;
|
|
127
|
+
for (let j = 0; j < fp.childCount; j++) {
|
|
128
|
+
const g = fp.child(j);
|
|
129
|
+
if (g && g.type !== 'field_name') collectHaskellPatternBindings(g, out);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
default:
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
97
138
|
function handleHaskellBind(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
98
139
|
const nameNode = node.childForFieldName('name');
|
|
99
140
|
if (!nameNode) return;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Call,
|
|
3
|
+
ExtractorOutput,
|
|
4
|
+
Import,
|
|
5
|
+
SubDeclaration,
|
|
6
|
+
TreeSitterNode,
|
|
7
|
+
TypeMapEntry,
|
|
8
|
+
} from '../types.js';
|
|
2
9
|
|
|
3
10
|
/**
|
|
4
11
|
* Maximum recursion depth for tree-sitter AST walkers.
|
|
@@ -6,6 +13,11 @@ import type { SubDeclaration, TreeSitterNode, TypeMapEntry } from '../types.js';
|
|
|
6
13
|
*/
|
|
7
14
|
export const MAX_WALK_DEPTH = 200;
|
|
8
15
|
|
|
16
|
+
/** Convert a tree-sitter node's start row to a 1-based source line. */
|
|
17
|
+
export function nodeStartLine(node: TreeSitterNode): number {
|
|
18
|
+
return node.startPosition.row + 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
export function nodeEndLine(node: TreeSitterNode): number {
|
|
10
22
|
return node.endPosition.row + 1;
|
|
11
23
|
}
|
|
@@ -18,6 +30,56 @@ export function findChild(node: TreeSitterNode, type: string): TreeSitterNode |
|
|
|
18
30
|
return null;
|
|
19
31
|
}
|
|
20
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Find the first child whose type is in `types`. Useful when several grammar
|
|
35
|
+
* variants name the same conceptual node differently (e.g. `string` vs
|
|
36
|
+
* `string_literal`). Returns the first match in document order, or null.
|
|
37
|
+
*/
|
|
38
|
+
export function findFirstChildOfTypes(
|
|
39
|
+
node: TreeSitterNode,
|
|
40
|
+
types: readonly string[],
|
|
41
|
+
): TreeSitterNode | null {
|
|
42
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
43
|
+
const child = node.child(i);
|
|
44
|
+
if (child && types.includes(child.type)) return child;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Iterate the direct children of `node` in document order, skipping nulls and
|
|
51
|
+
* tokens whose type appears in `skipTypes`. Mirrors the common
|
|
52
|
+
* `for (let i = 0; i < node.childCount; i++) { const c = node.child(i); if (...) continue; ... }`
|
|
53
|
+
* idiom while letting callers filter out grammar punctuation (`,`, `(`, `{`, etc.).
|
|
54
|
+
*/
|
|
55
|
+
export function* iterChildren(
|
|
56
|
+
node: TreeSitterNode,
|
|
57
|
+
skipTypes: ReadonlySet<string> = EMPTY_SKIP_SET,
|
|
58
|
+
): Generator<TreeSitterNode> {
|
|
59
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
60
|
+
const child = node.child(i);
|
|
61
|
+
if (!child) continue;
|
|
62
|
+
if (skipTypes.has(child.type)) continue;
|
|
63
|
+
yield child;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const EMPTY_SKIP_SET: ReadonlySet<string> = new Set();
|
|
68
|
+
|
|
69
|
+
/** Common punctuation tokens — handy as a `skipTypes` set for `iterChildren`. */
|
|
70
|
+
export const PUNCTUATION_TOKENS: ReadonlySet<string> = new Set([
|
|
71
|
+
',',
|
|
72
|
+
';',
|
|
73
|
+
'(',
|
|
74
|
+
')',
|
|
75
|
+
'[',
|
|
76
|
+
']',
|
|
77
|
+
'{',
|
|
78
|
+
'}',
|
|
79
|
+
':',
|
|
80
|
+
'.',
|
|
81
|
+
]);
|
|
82
|
+
|
|
21
83
|
/**
|
|
22
84
|
* Merge a type-map entry, keeping the higher-confidence one.
|
|
23
85
|
* Shared across all language extractors that build type maps for call resolution.
|
|
@@ -197,3 +259,145 @@ export function extractModifierVisibility(
|
|
|
197
259
|
}
|
|
198
260
|
return undefined;
|
|
199
261
|
}
|
|
262
|
+
|
|
263
|
+
// ── Output-push helpers ────────────────────────────────────────────────────
|
|
264
|
+
//
|
|
265
|
+
// Most extractors finish with `ctx.calls.push({ name, line: node.startPosition.row + 1 })`
|
|
266
|
+
// or `ctx.imports.push({ source, names, line: node.startPosition.row + 1 })`.
|
|
267
|
+
// Centralising the construction keeps `line` derivation consistent and removes
|
|
268
|
+
// the ~108 hand-rolled `startPosition.row + 1` literals scattered across
|
|
269
|
+
// language extractors.
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Append a `Call` to the extractor output. `line` defaults to the start line of
|
|
273
|
+
* `node`; pass `extra` for `receiver` / `dynamic` flags.
|
|
274
|
+
*/
|
|
275
|
+
export function pushCall(
|
|
276
|
+
ctx: ExtractorOutput,
|
|
277
|
+
node: TreeSitterNode,
|
|
278
|
+
name: string,
|
|
279
|
+
extra: { receiver?: string; dynamic?: boolean } = {},
|
|
280
|
+
): void {
|
|
281
|
+
if (!name) return;
|
|
282
|
+
const call: Call = { name, line: nodeStartLine(node) };
|
|
283
|
+
if (extra.receiver !== undefined) call.receiver = extra.receiver;
|
|
284
|
+
if (extra.dynamic !== undefined) call.dynamic = extra.dynamic;
|
|
285
|
+
ctx.calls.push(call);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Append an `Import` to the extractor output. `line` defaults to the start
|
|
290
|
+
* line of `node`. If `names` is empty, the source basename (split on `/`) is
|
|
291
|
+
* used as a single-name fallback — matching the convention in gleam, julia,
|
|
292
|
+
* and similar module-path imports.
|
|
293
|
+
*/
|
|
294
|
+
export function pushImport(
|
|
295
|
+
ctx: ExtractorOutput,
|
|
296
|
+
node: TreeSitterNode,
|
|
297
|
+
source: string,
|
|
298
|
+
names: string[],
|
|
299
|
+
flags: Partial<Omit<Import, 'source' | 'names' | 'line'>> = {},
|
|
300
|
+
): void {
|
|
301
|
+
if (!source) return;
|
|
302
|
+
const resolved = names.length > 0 ? names : [lastPathSegment(source, '/') || source];
|
|
303
|
+
const entry: Import = { source, names: resolved, line: nodeStartLine(node) };
|
|
304
|
+
Object.assign(entry, flags);
|
|
305
|
+
ctx.imports.push(entry);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── Parameter extraction ───────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Options for {@link extractSimpleParameters}.
|
|
312
|
+
*/
|
|
313
|
+
export interface ExtractParametersOptions {
|
|
314
|
+
/** Tree-sitter types that mark a single parameter node (e.g. `formal_parameter`). */
|
|
315
|
+
paramTypes: readonly string[];
|
|
316
|
+
/**
|
|
317
|
+
* Field name on each parameter that holds the bound identifier. Defaults to
|
|
318
|
+
* `'name'`. Pass `null` to use the parameter node itself when its type is in
|
|
319
|
+
* `paramTypes` and it has no `name` field (e.g. R's bare `identifier`).
|
|
320
|
+
*/
|
|
321
|
+
nameField?: string | null;
|
|
322
|
+
/**
|
|
323
|
+
* If true, when `nameField` lookup fails fall back to the first `identifier`
|
|
324
|
+
* child of the parameter. Useful for Gleam / Solidity-style grammars.
|
|
325
|
+
*/
|
|
326
|
+
fallbackToIdentifier?: boolean;
|
|
327
|
+
/**
|
|
328
|
+
* Optional type-map sink. When provided, the parameter's `type` field text
|
|
329
|
+
* (if present) is recorded with the given confidence.
|
|
330
|
+
*/
|
|
331
|
+
typeMap?: Map<string, TypeMapEntry>;
|
|
332
|
+
/** Confidence used when writing into `typeMap`. Defaults to `0.9`. */
|
|
333
|
+
typeMapConfidence?: number;
|
|
334
|
+
/**
|
|
335
|
+
* Optional callback to derive the type text from the parameter's `type`
|
|
336
|
+
* field node. Defaults to `node.text`. Use this for languages where the
|
|
337
|
+
* `type` field is wrapped (e.g. Java `generic_type` → first child).
|
|
338
|
+
*/
|
|
339
|
+
resolveType?: (typeNode: TreeSitterNode) => string | undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Extract parameters from a parameter-list node using a uniform pattern.
|
|
344
|
+
*
|
|
345
|
+
* This collapses the boilerplate in `extract*Params` helpers across
|
|
346
|
+
* Java/Julia/Gleam/Solidity/R/etc. — each one walks the parameter list,
|
|
347
|
+
* matches a parameter node type, reads the `name` field, and pushes a
|
|
348
|
+
* `SubDeclaration` with `kind: 'parameter'`.
|
|
349
|
+
*/
|
|
350
|
+
export function extractSimpleParameters(
|
|
351
|
+
paramListNode: TreeSitterNode | null,
|
|
352
|
+
options: ExtractParametersOptions,
|
|
353
|
+
): SubDeclaration[] {
|
|
354
|
+
const params: SubDeclaration[] = [];
|
|
355
|
+
if (!paramListNode) return params;
|
|
356
|
+
const { paramTypes, nameField = 'name', fallbackToIdentifier = false } = options;
|
|
357
|
+
|
|
358
|
+
for (let i = 0; i < paramListNode.childCount; i++) {
|
|
359
|
+
const param = paramListNode.child(i);
|
|
360
|
+
if (!param || !paramTypes.includes(param.type)) continue;
|
|
361
|
+
const nameNode = resolveParamName(param, nameField, fallbackToIdentifier);
|
|
362
|
+
if (!nameNode) continue;
|
|
363
|
+
params.push({ name: nameNode.text, kind: 'parameter', line: nodeStartLine(param) });
|
|
364
|
+
recordParamType(param, nameNode.text, options);
|
|
365
|
+
}
|
|
366
|
+
return params;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Record a parameter's declared type into the type-map sink, if configured. */
|
|
370
|
+
function recordParamType(
|
|
371
|
+
param: TreeSitterNode,
|
|
372
|
+
paramName: string,
|
|
373
|
+
options: ExtractParametersOptions,
|
|
374
|
+
): void {
|
|
375
|
+
const { typeMap, resolveType, typeMapConfidence = 0.9 } = options;
|
|
376
|
+
if (!typeMap) return;
|
|
377
|
+
const typeNode = param.childForFieldName('type');
|
|
378
|
+
if (!typeNode) return;
|
|
379
|
+
const typeText = resolveType ? resolveType(typeNode) : typeNode.text;
|
|
380
|
+
if (!typeText) return;
|
|
381
|
+
setTypeMapEntry(typeMap, paramName, typeText, typeMapConfidence);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Resolve the identifier node that names a parameter. Used by
|
|
386
|
+
* {@link extractSimpleParameters}; exposed so language-specific extractors
|
|
387
|
+
* can reuse the same lookup logic in custom loops.
|
|
388
|
+
*/
|
|
389
|
+
export function resolveParamName(
|
|
390
|
+
paramNode: TreeSitterNode,
|
|
391
|
+
nameField: string | null,
|
|
392
|
+
fallbackToIdentifier: boolean,
|
|
393
|
+
): TreeSitterNode | null {
|
|
394
|
+
if (nameField === null) {
|
|
395
|
+
return paramNode;
|
|
396
|
+
}
|
|
397
|
+
const named = paramNode.childForFieldName(nameField);
|
|
398
|
+
if (named) return named;
|
|
399
|
+
if (fallbackToIdentifier) {
|
|
400
|
+
return findChild(paramNode, 'identifier');
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
}
|