@optave/codegraph 3.9.6 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -12
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +1 -1
- 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/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +50 -8
- package/dist/ast-analysis/visitors/ast-store-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/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/graph/builder/context.d.ts +10 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +10 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +7 -2
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +7 -2
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +0 -6
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +6 -23
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +44 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +348 -42
- 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 +8 -2
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +8 -0
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts +24 -0
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +117 -3
- 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 +9 -6
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +30 -0
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +36 -13
- 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 +73 -22
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +23 -18
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +14 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +104 -11
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/models.d.ts +16 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +36 -2
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +20 -13
- 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 +83 -3
- 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.js +6 -2
- 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/julia.js +172 -41
- 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.js +93 -52
- 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.js +18 -9
- 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/infrastructure/config.d.ts +1 -0
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -0
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +14 -8
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +23 -5
- 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/types.d.ts +16 -1
- 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 +11 -10
- package/src/ast-analysis/engine.ts +3 -1
- package/src/ast-analysis/rules/index.ts +87 -0
- package/src/ast-analysis/visitors/ast-store-visitor.ts +45 -9
- 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/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/graph/builder/context.ts +10 -0
- package/src/domain/graph/builder/helpers.ts +8 -3
- package/src/domain/graph/builder/incremental.ts +6 -41
- package/src/domain/graph/builder/pipeline.ts +404 -41
- package/src/domain/graph/builder/stages/build-edges.ts +9 -2
- package/src/domain/graph/builder/stages/collect-files.ts +9 -0
- package/src/domain/graph/builder/stages/detect-changes.ts +130 -4
- package/src/domain/graph/builder/stages/finalize.ts +9 -6
- package/src/domain/graph/builder/stages/insert-nodes.ts +38 -14
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/watcher.ts +21 -23
- package/src/domain/parser.ts +110 -10
- package/src/domain/search/models.ts +37 -2
- package/src/domain/wasm-worker-entry.ts +20 -13
- 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 +75 -3
- package/src/extractors/erlang.ts +63 -20
- package/src/extractors/fsharp.ts +104 -0
- package/src/extractors/gleam.ts +7 -2
- package/src/extractors/groovy.ts +45 -1
- package/src/extractors/haskell.ts +45 -4
- package/src/extractors/julia.ts +164 -43
- 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 +88 -48
- package/src/extractors/scala.ts +24 -36
- package/src/extractors/solidity.ts +17 -8
- package/src/extractors/verilog.ts +83 -15
- package/src/infrastructure/config.ts +1 -0
- package/src/mcp/server.ts +16 -9
- package/src/mcp/tool-registry.ts +28 -5
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/types.ts +16 -0
package/src/domain/parser.ts
CHANGED
|
@@ -316,16 +316,23 @@ export function getParser(parsers: Map<string, Parser | null>, filePath: string)
|
|
|
316
316
|
*
|
|
317
317
|
* Name is preserved for caller compatibility; the function now ensures
|
|
318
318
|
* *analysis data* rather than *trees*.
|
|
319
|
+
*
|
|
320
|
+
* `needsFn` (optional): when provided, only files for which it returns true are
|
|
321
|
+
* re-parsed. Without it the function falls back to "any WASM-parseable file
|
|
322
|
+
* without _tree", which was the source of #1036 — a single file missing one
|
|
323
|
+
* analysis triggered a full-build re-parse of every WASM-parseable file.
|
|
319
324
|
*/
|
|
320
325
|
export async function ensureWasmTrees(
|
|
321
326
|
fileSymbols: Map<string, any>,
|
|
322
327
|
rootDir: string,
|
|
328
|
+
needsFn?: (relPath: string, symbols: any) => boolean,
|
|
323
329
|
): Promise<void> {
|
|
324
330
|
// Collect files that still need analysis data and are parseable by WASM.
|
|
325
331
|
const pending: Array<{ relPath: string; absPath: string; symbols: any }> = [];
|
|
326
332
|
for (const [relPath, symbols] of fileSymbols) {
|
|
327
333
|
if (symbols._tree) continue; // legacy path — leave existing trees alone
|
|
328
334
|
if (!_extToLang.has(path.extname(relPath).toLowerCase())) continue;
|
|
335
|
+
if (needsFn && !needsFn(relPath, symbols)) continue;
|
|
329
336
|
pending.push({ relPath, absPath: path.join(rootDir, relPath), symbols });
|
|
330
337
|
}
|
|
331
338
|
if (pending.length === 0) return;
|
|
@@ -450,6 +457,8 @@ export const NATIVE_SUPPORTED_EXTENSIONS: ReadonlySet<string> = new Set([
|
|
|
450
457
|
'.cc',
|
|
451
458
|
'.cxx',
|
|
452
459
|
'.hpp',
|
|
460
|
+
'.cu',
|
|
461
|
+
'.cuh',
|
|
453
462
|
'.kt',
|
|
454
463
|
'.kts',
|
|
455
464
|
'.swift',
|
|
@@ -464,6 +473,23 @@ export const NATIVE_SUPPORTED_EXTENSIONS: ReadonlySet<string> = new Set([
|
|
|
464
473
|
'.hs',
|
|
465
474
|
'.ml',
|
|
466
475
|
'.mli',
|
|
476
|
+
'.fs',
|
|
477
|
+
'.fsx',
|
|
478
|
+
'.fsi',
|
|
479
|
+
'.m',
|
|
480
|
+
'.gleam',
|
|
481
|
+
'.jl',
|
|
482
|
+
'.clj',
|
|
483
|
+
'.cljs',
|
|
484
|
+
'.cljc',
|
|
485
|
+
'.erl',
|
|
486
|
+
'.hrl',
|
|
487
|
+
'.groovy',
|
|
488
|
+
'.gvy',
|
|
489
|
+
'.r',
|
|
490
|
+
'.sol',
|
|
491
|
+
'.v',
|
|
492
|
+
'.sv',
|
|
467
493
|
]);
|
|
468
494
|
|
|
469
495
|
/**
|
|
@@ -805,11 +831,18 @@ export const LANGUAGE_REGISTRY: LanguageRegistryEntry[] = [
|
|
|
805
831
|
},
|
|
806
832
|
{
|
|
807
833
|
id: 'fsharp',
|
|
808
|
-
extensions: ['.fs', '.fsx'
|
|
834
|
+
extensions: ['.fs', '.fsx'],
|
|
809
835
|
grammarFile: 'tree-sitter-fsharp.wasm',
|
|
810
836
|
extractor: extractFSharpSymbols,
|
|
811
837
|
required: false,
|
|
812
838
|
},
|
|
839
|
+
{
|
|
840
|
+
id: 'fsharp-signature',
|
|
841
|
+
extensions: ['.fsi'],
|
|
842
|
+
grammarFile: 'tree-sitter-fsharp_signature.wasm',
|
|
843
|
+
extractor: extractFSharpSymbols,
|
|
844
|
+
required: false,
|
|
845
|
+
},
|
|
813
846
|
{
|
|
814
847
|
id: 'gleam',
|
|
815
848
|
extensions: ['.gleam'],
|
|
@@ -1060,6 +1093,71 @@ async function parseFilesWasm(
|
|
|
1060
1093
|
return result;
|
|
1061
1094
|
}
|
|
1062
1095
|
|
|
1096
|
+
/**
|
|
1097
|
+
* Files at or below this count use the inline parse path (no worker spawn).
|
|
1098
|
+
*
|
|
1099
|
+
* Sized for typical engine-parity drops: a handful of fixture files in one
|
|
1100
|
+
* or two languages (the recurring HCL case is 4 files). Above this, the
|
|
1101
|
+
* worker-pool's IPC + crash-isolation cost (#965) is amortized over enough
|
|
1102
|
+
* parse work to be worth paying; below it, the ~1–2s cold-start dominates.
|
|
1103
|
+
*/
|
|
1104
|
+
const INLINE_BACKFILL_THRESHOLD = 16;
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Inline WASM parse (no worker) for small file batches.
|
|
1108
|
+
*
|
|
1109
|
+
* Used by the engine-parity backfill path when the native engine drops a
|
|
1110
|
+
* handful of files (typically test fixtures). The worker pool's per-call
|
|
1111
|
+
* IPC + grammar-init overhead can cost 1–2s on slow CI runners — for a
|
|
1112
|
+
* 4-file backfill, that dwarfs the ~10ms of actual parse work.
|
|
1113
|
+
*
|
|
1114
|
+
* Returns symbols with `_tree` set so `runAnalyses` can run AST/CFG/dataflow
|
|
1115
|
+
* visitors via the unified walker (mirrors how WASM-engine results behaved
|
|
1116
|
+
* before the worker pool was introduced).
|
|
1117
|
+
*/
|
|
1118
|
+
async function parseFilesWasmInline(
|
|
1119
|
+
filePaths: string[],
|
|
1120
|
+
rootDir: string,
|
|
1121
|
+
): Promise<Map<string, ExtractorOutput>> {
|
|
1122
|
+
const result = new Map<string, ExtractorOutput>();
|
|
1123
|
+
if (filePaths.length === 0) return result;
|
|
1124
|
+
const parsers = await ensureParsersForFiles(filePaths);
|
|
1125
|
+
for (const filePath of filePaths) {
|
|
1126
|
+
if (!_extToLang.has(path.extname(filePath).toLowerCase())) continue;
|
|
1127
|
+
let code: string;
|
|
1128
|
+
try {
|
|
1129
|
+
code = fs.readFileSync(filePath, 'utf-8');
|
|
1130
|
+
} catch (err: unknown) {
|
|
1131
|
+
warn(`Skipping ${path.relative(rootDir, filePath)}: ${(err as Error).message}`);
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
const extracted = wasmExtractSymbols(parsers, filePath, code);
|
|
1135
|
+
if (!extracted) continue;
|
|
1136
|
+
const relPath = path.relative(rootDir, filePath).split(path.sep).join('/');
|
|
1137
|
+
const symbols = extracted.symbols as ExtractorOutput & { _tree?: unknown; _langId?: string };
|
|
1138
|
+
symbols._tree = extracted.tree;
|
|
1139
|
+
symbols._langId = extracted.langId;
|
|
1140
|
+
result.set(relPath, symbols);
|
|
1141
|
+
}
|
|
1142
|
+
return result;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Backfill helper: small batches use the inline (main-thread) path; larger
|
|
1147
|
+
* batches keep the worker-pool isolation against tree-sitter WASM crashes
|
|
1148
|
+
* (#965). Threshold matches typical engine-parity drop sizes (a few fixture
|
|
1149
|
+
* files in one or two languages).
|
|
1150
|
+
*/
|
|
1151
|
+
export async function parseFilesWasmForBackfill(
|
|
1152
|
+
filePaths: string[],
|
|
1153
|
+
rootDir: string,
|
|
1154
|
+
): Promise<Map<string, ExtractorOutput>> {
|
|
1155
|
+
if (filePaths.length <= INLINE_BACKFILL_THRESHOLD) {
|
|
1156
|
+
return parseFilesWasmInline(filePaths, rootDir);
|
|
1157
|
+
}
|
|
1158
|
+
return parseFilesWasm(filePaths, rootDir);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1063
1161
|
/**
|
|
1064
1162
|
* Parse multiple files in bulk and return a Map<relPath, symbols>.
|
|
1065
1163
|
*/
|
|
@@ -1110,7 +1208,7 @@ export async function parseFilesAuto(
|
|
|
1110
1208
|
);
|
|
1111
1209
|
if (dropped.length > 0) {
|
|
1112
1210
|
warn(`Native engine dropped ${dropped.length} file(s); falling back to WASM for parity`);
|
|
1113
|
-
const wasmResults = await
|
|
1211
|
+
const wasmResults = await parseFilesWasmForBackfill(dropped, rootDir);
|
|
1114
1212
|
for (const [relPath, symbols] of wasmResults) {
|
|
1115
1213
|
result.set(relPath, symbols);
|
|
1116
1214
|
}
|
|
@@ -1125,15 +1223,17 @@ export async function parseFilesAuto(
|
|
|
1125
1223
|
export function getActiveEngine(opts: ParseEngineOpts = {}): {
|
|
1126
1224
|
name: 'native' | 'wasm';
|
|
1127
1225
|
version: string | null;
|
|
1226
|
+
binaryVersion: string | null;
|
|
1128
1227
|
} {
|
|
1129
1228
|
const { name, native } = resolveEngine(opts);
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
//
|
|
1136
|
-
//
|
|
1229
|
+
const binaryVersion: string | null =
|
|
1230
|
+
native && typeof native.engineVersion === 'function' ? native.engineVersion() : null;
|
|
1231
|
+
// The display version prefers the platform package.json so the "Using native
|
|
1232
|
+
// engine (vX)" log matches the npm release the user installed. The Rust
|
|
1233
|
+
// orchestrator's check_version_mismatch compares against CARGO_PKG_VERSION
|
|
1234
|
+
// (the binary's own value), so build_meta writes must use `binaryVersion`,
|
|
1235
|
+
// not this display value — see pipeline.ts and finalize.ts (#1066).
|
|
1236
|
+
let version: string | null = binaryVersion;
|
|
1137
1237
|
if (native) {
|
|
1138
1238
|
try {
|
|
1139
1239
|
version = getNativePackageVersion() ?? version;
|
|
@@ -1141,7 +1241,7 @@ export function getActiveEngine(opts: ParseEngineOpts = {}): {
|
|
|
1141
1241
|
debug(`getNativePackageVersion failed: ${(e as Error).message}`);
|
|
1142
1242
|
}
|
|
1143
1243
|
}
|
|
1144
|
-
return { name, version };
|
|
1244
|
+
return { name, version, binaryVersion };
|
|
1145
1245
|
}
|
|
1146
1246
|
|
|
1147
1247
|
/**
|
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
2
4
|
import { createInterface } from 'node:readline';
|
|
3
5
|
import { info } from '../../infrastructure/logger.js';
|
|
4
6
|
import { ConfigError, EngineError } from '../../shared/errors.js';
|
|
5
7
|
|
|
8
|
+
const _require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the directory where `npm install` should run so the installed
|
|
12
|
+
* package ends up reachable by `await import(pkg)` from inside this module.
|
|
13
|
+
*
|
|
14
|
+
* Without a `cwd`, `execFileSync('npm', ['install', ...])` operates on
|
|
15
|
+
* `process.cwd()` — when the user runs codegraph against a repo that is *not*
|
|
16
|
+
* the directory where codegraph itself is installed, npm installs into the
|
|
17
|
+
* wrong `node_modules`, the dynamic import still fails, and the user gets
|
|
18
|
+
* `ENGINE_UNAVAILABLE: ... installed but failed to load`.
|
|
19
|
+
*
|
|
20
|
+
* Pin cwd to the directory that contains @optave/codegraph's `node_modules`
|
|
21
|
+
* so the install lands where Node's resolution algorithm will find it.
|
|
22
|
+
*
|
|
23
|
+
* @internal Exported for unit tests; not part of the public barrel.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveNpmInstallCwd(): string | undefined {
|
|
26
|
+
try {
|
|
27
|
+
const pkgJsonPath = _require.resolve('@optave/codegraph/package.json');
|
|
28
|
+
// pkgJsonPath = <host>/node_modules/@optave/codegraph/package.json
|
|
29
|
+
// dirname x4: package.json → codegraph → @optave → node_modules → <host>
|
|
30
|
+
return path.dirname(path.dirname(path.dirname(path.dirname(pkgJsonPath))));
|
|
31
|
+
} catch {
|
|
32
|
+
// Source-of-truth checkout (no @optave/codegraph in node_modules) — fall back
|
|
33
|
+
// to process.cwd() so legacy behavior survives in tests.
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
6
38
|
export interface ModelConfig {
|
|
7
39
|
name: string;
|
|
8
40
|
dim: number;
|
|
@@ -42,7 +74,7 @@ export const MODELS: Record<string, ModelConfig> = {
|
|
|
42
74
|
quantized: false,
|
|
43
75
|
},
|
|
44
76
|
'jina-code': {
|
|
45
|
-
name: '
|
|
77
|
+
name: 'jinaai/jina-embeddings-v2-base-code',
|
|
46
78
|
dim: 768,
|
|
47
79
|
contextWindow: 8192,
|
|
48
80
|
desc: 'Code-aware (~137MB). Trained on code+text, best for code search.',
|
|
@@ -104,12 +136,14 @@ export function getModelConfig(modelKey?: string): ModelConfig {
|
|
|
104
136
|
* @internal Not part of the public barrel.
|
|
105
137
|
*/
|
|
106
138
|
export function promptInstall(packageName: string): Promise<boolean> {
|
|
139
|
+
const installCwd = resolveNpmInstallCwd();
|
|
107
140
|
if (!process.stdin.isTTY) {
|
|
108
141
|
info(`Installing ${packageName} (optional dependency for semantic search)…`);
|
|
109
142
|
try {
|
|
110
143
|
execFileSync(NPM_BIN, ['install', '--no-save', packageName], {
|
|
111
144
|
stdio: 'inherit',
|
|
112
145
|
timeout: 300_000,
|
|
146
|
+
cwd: installCwd,
|
|
113
147
|
});
|
|
114
148
|
return Promise.resolve(true);
|
|
115
149
|
} catch (err) {
|
|
@@ -128,9 +162,10 @@ export function promptInstall(packageName: string): Promise<boolean> {
|
|
|
128
162
|
rl.close();
|
|
129
163
|
if (answer.trim().toLowerCase() !== 'y') return resolve(false);
|
|
130
164
|
try {
|
|
131
|
-
execFileSync(NPM_BIN, ['install', packageName], {
|
|
165
|
+
execFileSync(NPM_BIN, ['install', '--no-save', packageName], {
|
|
132
166
|
stdio: 'inherit',
|
|
133
167
|
timeout: 300_000,
|
|
168
|
+
cwd: installCwd,
|
|
134
169
|
});
|
|
135
170
|
resolve(true);
|
|
136
171
|
} catch (err) {
|
|
@@ -306,11 +306,18 @@ const LANGUAGE_REGISTRY: LanguageRegistryEntry[] = [
|
|
|
306
306
|
},
|
|
307
307
|
{
|
|
308
308
|
id: 'fsharp',
|
|
309
|
-
extensions: ['.fs', '.fsx'
|
|
309
|
+
extensions: ['.fs', '.fsx'],
|
|
310
310
|
grammarFile: 'tree-sitter-fsharp.wasm',
|
|
311
311
|
extractor: extractFSharpSymbols,
|
|
312
312
|
required: false,
|
|
313
313
|
},
|
|
314
|
+
{
|
|
315
|
+
id: 'fsharp-signature',
|
|
316
|
+
extensions: ['.fsi'],
|
|
317
|
+
grammarFile: 'tree-sitter-fsharp_signature.wasm',
|
|
318
|
+
extractor: extractFSharpSymbols,
|
|
319
|
+
required: false,
|
|
320
|
+
},
|
|
314
321
|
{
|
|
315
322
|
id: 'gleam',
|
|
316
323
|
extensions: ['.gleam'],
|
|
@@ -708,18 +715,18 @@ async function handleParse(msg: WorkerParseRequest): Promise<SerializedExtractor
|
|
|
708
715
|
file?: string;
|
|
709
716
|
parentNodeId?: number | null;
|
|
710
717
|
}>;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
718
|
+
// Always set an array (even empty) — leaving astNodes undefined makes
|
|
719
|
+
// engine.ts::fileNeedsWasmTree treat the file as un-walked and trigger
|
|
720
|
+
// a full ensureWasmTrees re-parse of every WASM-parseable file (#1036).
|
|
721
|
+
// Strip `file` and `parentNodeId` — main thread re-resolves both in
|
|
722
|
+
// features/ast.ts::collectFileAstRows.
|
|
723
|
+
serializedAstNodes = astRows.map((n) => ({
|
|
724
|
+
line: n.line,
|
|
725
|
+
kind: n.kind,
|
|
726
|
+
name: n.name ?? '',
|
|
727
|
+
text: n.text ?? undefined,
|
|
728
|
+
receiver: n.receiver ?? undefined,
|
|
729
|
+
}));
|
|
723
730
|
}
|
|
724
731
|
|
|
725
732
|
if (complexityVisitor) storeComplexityResults(results, defs, entry.id);
|
package/src/extractors/c.ts
CHANGED
|
@@ -159,6 +159,31 @@ function handleCCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
159
159
|
|
|
160
160
|
// ── Child extraction helpers ────────────────────────────────────────────────
|
|
161
161
|
|
|
162
|
+
const C_DECLARATOR_WRAPPERS = new Set([
|
|
163
|
+
'pointer_declarator',
|
|
164
|
+
'array_declarator',
|
|
165
|
+
'parenthesized_declarator',
|
|
166
|
+
'function_declarator',
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Drill through pointer/array/parenthesized/function declarator wrappers to
|
|
171
|
+
* recover the bare identifier. Mirrors `unwrap_declarator` in the native C
|
|
172
|
+
* extractor so both engines agree on the name for parameters such as
|
|
173
|
+
* `void process(int callback(int))` (function-type parameter → `callback`) or
|
|
174
|
+
* `int *func(int)` (pointer-returning function → `func`).
|
|
175
|
+
*/
|
|
176
|
+
function unwrapCDeclaratorName(node: TreeSitterNode): string {
|
|
177
|
+
let current: TreeSitterNode | null = node;
|
|
178
|
+
while (current && C_DECLARATOR_WRAPPERS.has(current.type)) {
|
|
179
|
+
current = current.childForFieldName('declarator');
|
|
180
|
+
}
|
|
181
|
+
if (current?.type === 'identifier' || current?.type === 'field_identifier') {
|
|
182
|
+
return current.text;
|
|
183
|
+
}
|
|
184
|
+
return current?.text ?? node.text;
|
|
185
|
+
}
|
|
186
|
+
|
|
162
187
|
function extractCParameters(paramListNode: TreeSitterNode | null): SubDeclaration[] {
|
|
163
188
|
const params: SubDeclaration[] = [];
|
|
164
189
|
if (!paramListNode) return params;
|
|
@@ -167,10 +192,7 @@ function extractCParameters(paramListNode: TreeSitterNode | null): SubDeclaratio
|
|
|
167
192
|
if (!param || param.type !== 'parameter_declaration') continue;
|
|
168
193
|
const nameNode = param.childForFieldName('declarator');
|
|
169
194
|
if (nameNode) {
|
|
170
|
-
const name =
|
|
171
|
-
nameNode.type === 'identifier'
|
|
172
|
-
? nameNode.text
|
|
173
|
-
: (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
|
|
195
|
+
const name = unwrapCDeclaratorName(nameNode);
|
|
174
196
|
params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
175
197
|
}
|
|
176
198
|
}
|
|
@@ -186,10 +208,7 @@ function extractStructFields(structNode: TreeSitterNode): SubDeclaration[] {
|
|
|
186
208
|
if (!member || member.type !== 'field_declaration') continue;
|
|
187
209
|
const nameNode = member.childForFieldName('declarator');
|
|
188
210
|
if (nameNode) {
|
|
189
|
-
const name =
|
|
190
|
-
nameNode.type === 'identifier'
|
|
191
|
-
? nameNode.text
|
|
192
|
-
: (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
|
|
211
|
+
const name = unwrapCDeclaratorName(nameNode);
|
|
193
212
|
fields.push({ name, kind: 'property', line: member.startPosition.row + 1 });
|
|
194
213
|
}
|
|
195
214
|
}
|
package/src/extractors/cpp.ts
CHANGED
|
@@ -239,6 +239,54 @@ function findCppParentClass(node: TreeSitterNode): string | null {
|
|
|
239
239
|
return null;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
const CPP_DECLARATOR_WRAPPERS = new Set([
|
|
243
|
+
'pointer_declarator',
|
|
244
|
+
'reference_declarator',
|
|
245
|
+
'array_declarator',
|
|
246
|
+
'parenthesized_declarator',
|
|
247
|
+
'function_declarator',
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Drill through pointer/reference/array/parenthesized/function declarator
|
|
252
|
+
* wrappers to recover the bare identifier. Mirrors `unwrap_cpp_declarator` in
|
|
253
|
+
* the native C++ extractor. tree-sitter-cpp's `reference_declarator` does not
|
|
254
|
+
* expose a `declarator` field, so the loop falls back to scanning children
|
|
255
|
+
* for the next nested declarator or identifier.
|
|
256
|
+
*/
|
|
257
|
+
function unwrapCppDeclaratorName(node: TreeSitterNode): string {
|
|
258
|
+
let current: TreeSitterNode | null = node;
|
|
259
|
+
while (current && CPP_DECLARATOR_WRAPPERS.has(current.type)) {
|
|
260
|
+
const named = current.childForFieldName('declarator');
|
|
261
|
+
if (named) {
|
|
262
|
+
current = named;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const fallback = nextCppDeclaratorChild(current);
|
|
266
|
+
if (!fallback) break;
|
|
267
|
+
current = fallback;
|
|
268
|
+
}
|
|
269
|
+
if (current?.type === 'identifier' || current?.type === 'field_identifier') {
|
|
270
|
+
return current.text;
|
|
271
|
+
}
|
|
272
|
+
return current?.text ?? node.text;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function nextCppDeclaratorChild(node: TreeSitterNode): TreeSitterNode | null {
|
|
276
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
277
|
+
const child = node.child(i);
|
|
278
|
+
if (!child) continue;
|
|
279
|
+
if (
|
|
280
|
+
child.type === 'identifier' ||
|
|
281
|
+
child.type === 'field_identifier' ||
|
|
282
|
+
CPP_DECLARATOR_WRAPPERS.has(child.type)
|
|
283
|
+
) {
|
|
284
|
+
return child;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
242
290
|
function extractCppParameters(paramListNode: TreeSitterNode | null): SubDeclaration[] {
|
|
243
291
|
const params: SubDeclaration[] = [];
|
|
244
292
|
if (!paramListNode) return params;
|
|
@@ -247,10 +295,7 @@ function extractCppParameters(paramListNode: TreeSitterNode | null): SubDeclarat
|
|
|
247
295
|
if (!param || param.type !== 'parameter_declaration') continue;
|
|
248
296
|
const nameNode = param.childForFieldName('declarator');
|
|
249
297
|
if (nameNode) {
|
|
250
|
-
const name =
|
|
251
|
-
nameNode.type === 'identifier'
|
|
252
|
-
? nameNode.text
|
|
253
|
-
: (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
|
|
298
|
+
const name = unwrapCppDeclaratorName(nameNode);
|
|
254
299
|
params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
255
300
|
}
|
|
256
301
|
}
|
|
@@ -267,10 +312,7 @@ function extractCppClassFields(classNode: TreeSitterNode): SubDeclaration[] {
|
|
|
267
312
|
if (!member || member.type !== 'field_declaration') continue;
|
|
268
313
|
const nameNode = member.childForFieldName('declarator');
|
|
269
314
|
if (nameNode) {
|
|
270
|
-
const name =
|
|
271
|
-
nameNode.type === 'identifier'
|
|
272
|
-
? nameNode.text
|
|
273
|
-
: (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
|
|
315
|
+
const name = unwrapCppDeclaratorName(nameNode);
|
|
274
316
|
fields.push({
|
|
275
317
|
name,
|
|
276
318
|
kind: 'property',
|
package/src/extractors/cuda.ts
CHANGED
|
@@ -265,10 +265,10 @@ function extractCudaParameters(paramListNode: TreeSitterNode | null): SubDeclara
|
|
|
265
265
|
if (!param || param.type !== 'parameter_declaration') continue;
|
|
266
266
|
const nameNode = param.childForFieldName('declarator');
|
|
267
267
|
if (nameNode) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
// Reuse the field-name drill helper so function-type parameters like
|
|
269
|
+
// `void process(int callback(int))` yield the bare name `callback`
|
|
270
|
+
// instead of the raw declarator text, matching the native unwrap path.
|
|
271
|
+
const name = extractCudaFieldName(nameNode);
|
|
272
272
|
params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
273
273
|
}
|
|
274
274
|
}
|
|
@@ -284,22 +284,96 @@ function extractCudaClassFields(classNode: TreeSitterNode): SubDeclaration[] {
|
|
|
284
284
|
const member = body.child(i);
|
|
285
285
|
if (!member || member.type !== 'field_declaration') continue;
|
|
286
286
|
const nameNode = member.childForFieldName('declarator');
|
|
287
|
-
if (nameNode)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
287
|
+
if (!nameNode) continue;
|
|
288
|
+
// Skip method declarations — a `field_declaration` whose declarator
|
|
289
|
+
// (after unwrapping pointer/reference/array) is a `function_declarator`
|
|
290
|
+
// is a method signature in a header, not a data field. Native and WASM
|
|
291
|
+
// previously diverged on how to format these (native stripped the `*`
|
|
292
|
+
// from pointer-return types, WASM kept it), and both produced
|
|
293
|
+
// method-signature-shaped "property" entries that are not real fields.
|
|
294
|
+
if (isCudaMethodDeclarator(nameNode)) continue;
|
|
295
|
+
const name = extractCudaFieldName(nameNode);
|
|
296
|
+
fields.push({
|
|
297
|
+
name,
|
|
298
|
+
kind: 'property',
|
|
299
|
+
line: member.startPosition.row + 1,
|
|
300
|
+
visibility: extractModifierVisibility(member),
|
|
301
|
+
});
|
|
299
302
|
}
|
|
300
303
|
return fields;
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
const CUDA_DECLARATOR_WRAPPERS = new Set([
|
|
307
|
+
'pointer_declarator',
|
|
308
|
+
'reference_declarator',
|
|
309
|
+
'array_declarator',
|
|
310
|
+
'parenthesized_declarator',
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
function isCudaMethodDeclarator(node: TreeSitterNode): boolean {
|
|
314
|
+
let current: TreeSitterNode | null = node;
|
|
315
|
+
while (current && CUDA_DECLARATOR_WRAPPERS.has(current.type)) {
|
|
316
|
+
current = current.childForFieldName('declarator');
|
|
317
|
+
}
|
|
318
|
+
if (current?.type !== 'function_declarator') return false;
|
|
319
|
+
// A `function_declarator` whose inner declarator is a `parenthesized_declarator`
|
|
320
|
+
// is a function-pointer (or function-reference) field — e.g. `void (*cb)(int)`
|
|
321
|
+
// parses as function_declarator > parenthesized_declarator > pointer_declarator >
|
|
322
|
+
// field_identifier. Those are real data fields, not method declarations.
|
|
323
|
+
const inner = current.childForFieldName('declarator');
|
|
324
|
+
return inner?.type !== 'parenthesized_declarator';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Resolve the identifier of a declarator by walking through any combination of
|
|
329
|
+
* pointer/reference/array/parenthesized wrappers and `function_declarator`
|
|
330
|
+
* nodes. Used by both class-field extraction (where `function_declarator`
|
|
331
|
+
* indicates a function-pointer field after method declarations have been
|
|
332
|
+
* filtered out) and parameter extraction (where `function_declarator` wraps a
|
|
333
|
+
* bare function-type parameter name like `callback` in
|
|
334
|
+
* `void process(int callback(int))`).
|
|
335
|
+
*/
|
|
336
|
+
function extractCudaFieldName(decl: TreeSitterNode): string {
|
|
337
|
+
let current: TreeSitterNode | null = decl;
|
|
338
|
+
while (current) {
|
|
339
|
+
if (current.type === 'identifier' || current.type === 'field_identifier') {
|
|
340
|
+
return current.text;
|
|
341
|
+
}
|
|
342
|
+
if (CUDA_DECLARATOR_WRAPPERS.has(current.type) || current.type === 'function_declarator') {
|
|
343
|
+
const next = innerCudaDeclarator(current);
|
|
344
|
+
if (!next) break;
|
|
345
|
+
current = next;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
return decl.text;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Find the inner declarator of a wrapper node. Most C++ declarator wrappers
|
|
355
|
+
* expose it via the `declarator` field, but some (e.g. `parenthesized_declarator`
|
|
356
|
+
* and `reference_declarator` in tree-sitter-cuda) have unnamed children — so
|
|
357
|
+
* fall back to scanning children for a declarator-shaped node.
|
|
358
|
+
*/
|
|
359
|
+
function innerCudaDeclarator(node: TreeSitterNode): TreeSitterNode | null {
|
|
360
|
+
const named = node.childForFieldName('declarator');
|
|
361
|
+
if (named) return named;
|
|
362
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
363
|
+
const child = node.child(i);
|
|
364
|
+
if (!child) continue;
|
|
365
|
+
if (
|
|
366
|
+
child.type === 'identifier' ||
|
|
367
|
+
child.type === 'field_identifier' ||
|
|
368
|
+
child.type === 'function_declarator' ||
|
|
369
|
+
CUDA_DECLARATOR_WRAPPERS.has(child.type)
|
|
370
|
+
) {
|
|
371
|
+
return child;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
|
|
303
377
|
function extractCudaEnumEntries(enumNode: TreeSitterNode): SubDeclaration[] {
|
|
304
378
|
const entries: SubDeclaration[] = [];
|
|
305
379
|
const body = findChild(enumNode, 'enumerator_list');
|
package/src/extractors/elixir.ts
CHANGED
|
@@ -190,14 +190,86 @@ function extractElixirParams(defCallNode: TreeSitterNode): SubDeclaration[] {
|
|
|
190
190
|
for (let j = 0; j < innerArgs.childCount; j++) {
|
|
191
191
|
const param = innerArgs.child(j);
|
|
192
192
|
if (!param) continue;
|
|
193
|
-
|
|
194
|
-
params.push({ name: param.text, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
195
|
-
}
|
|
193
|
+
collectElixirParamIdentifiers(param, params);
|
|
196
194
|
}
|
|
197
195
|
}
|
|
198
196
|
return params;
|
|
199
197
|
}
|
|
200
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Recursively walk a parameter pattern and emit each bound identifier as a
|
|
201
|
+
* `parameter` child. Handles bare identifiers, default-value `a \\ default`,
|
|
202
|
+
* list-cons `[head | tail]`, list `[a, b, c]`, tuple `{x, y}`, and
|
|
203
|
+
* map / struct destructuring (`%{k: v}`, `%Foo{k: v}`).
|
|
204
|
+
*/
|
|
205
|
+
function collectElixirParamIdentifiers(node: TreeSitterNode, out: SubDeclaration[]): void {
|
|
206
|
+
switch (node.type) {
|
|
207
|
+
case 'identifier':
|
|
208
|
+
out.push({ name: node.text, kind: 'parameter', line: node.startPosition.row + 1 });
|
|
209
|
+
return;
|
|
210
|
+
case 'binary_operator': {
|
|
211
|
+
// `name \\ default` (default-value) binds the left operand only.
|
|
212
|
+
// `head | tail` (list-cons, appears inside a `list` pattern) binds both operands.
|
|
213
|
+
const op = node.child(1);
|
|
214
|
+
if (!op) return;
|
|
215
|
+
if (op.type === '\\\\') {
|
|
216
|
+
const left = node.child(0);
|
|
217
|
+
if (left) collectElixirParamIdentifiers(left, out);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (op.type === '|') {
|
|
221
|
+
const left = node.child(0);
|
|
222
|
+
const right = node.child(2);
|
|
223
|
+
if (left) collectElixirParamIdentifiers(left, out);
|
|
224
|
+
if (right) collectElixirParamIdentifiers(right, out);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
case 'list':
|
|
230
|
+
// `[a, b, c]` or `[head | tail]` — walk children, skipping punctuation. The
|
|
231
|
+
// `|` cons case is handled by the `binary_operator` arm when we recurse.
|
|
232
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
233
|
+
const c = node.child(i);
|
|
234
|
+
if (!c || c.type === '[' || c.type === ']' || c.type === ',') continue;
|
|
235
|
+
collectElixirParamIdentifiers(c, out);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
case 'tuple':
|
|
239
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
240
|
+
const c = node.child(i);
|
|
241
|
+
if (!c || c.type === '{' || c.type === '}' || c.type === ',') continue;
|
|
242
|
+
collectElixirParamIdentifiers(c, out);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
case 'map':
|
|
246
|
+
// `%{k: v}` or `%Foo{k: v}` — walk map_content > keywords > pair and emit each
|
|
247
|
+
// pair's value side (the bound name). The struct alias (`Foo`) is a type, not a
|
|
248
|
+
// bound identifier, so the leading `struct` child is intentionally skipped.
|
|
249
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
250
|
+
const c = node.child(i);
|
|
251
|
+
if (c && c.type === 'map_content') collectElixirMapBindings(c, out);
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function collectElixirMapBindings(content: TreeSitterNode, out: SubDeclaration[]): void {
|
|
258
|
+
for (let i = 0; i < content.childCount; i++) {
|
|
259
|
+
const kws = content.child(i);
|
|
260
|
+
if (!kws || kws.type !== 'keywords') continue;
|
|
261
|
+
for (let j = 0; j < kws.childCount; j++) {
|
|
262
|
+
const pair = kws.child(j);
|
|
263
|
+
if (!pair || pair.type !== 'pair') continue;
|
|
264
|
+
for (let k = 0; k < pair.childCount; k++) {
|
|
265
|
+
const part = pair.child(k);
|
|
266
|
+
if (!part || part.type === 'keyword') continue;
|
|
267
|
+
collectElixirParamIdentifiers(part, out);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
201
273
|
function handleDefprotocol(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
202
274
|
const args = findChild(node, 'arguments');
|
|
203
275
|
if (!args) return;
|