@optave/codegraph 3.9.3 → 3.9.5
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 +10 -10
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +14 -0
- package/dist/ast-analysis/visitor.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +2 -0
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/cli.js +24 -1
- package/dist/cli.js.map +1 -1
- package/dist/domain/graph/builder/context.d.ts +17 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +7 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +13 -2
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +30 -4
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +221 -51
- 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 +67 -6
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +2 -2
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +58 -26
- package/dist/domain/graph/builder/stages/collect-files.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 +105 -55
- 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 +27 -4
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/run-analyses.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/run-analyses.js +5 -20
- package/dist/domain/graph/builder/stages/run-analyses.js.map +1 -1
- package/dist/domain/graph/journal.d.ts +15 -0
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +283 -28
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +17 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +23 -7
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +13 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +174 -80
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +28 -2
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/wasm-worker-entry.d.ts +24 -0
- package/dist/domain/wasm-worker-entry.d.ts.map +1 -0
- package/dist/domain/wasm-worker-entry.js +643 -0
- package/dist/domain/wasm-worker-entry.js.map +1 -0
- package/dist/domain/wasm-worker-pool.d.ts +59 -0
- package/dist/domain/wasm-worker-pool.d.ts.map +1 -0
- package/dist/domain/wasm-worker-pool.js +312 -0
- package/dist/domain/wasm-worker-pool.js.map +1 -0
- package/dist/domain/wasm-worker-protocol.d.ts +65 -0
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -0
- package/dist/domain/wasm-worker-protocol.js +13 -0
- package/dist/domain/wasm-worker-protocol.js.map +1 -0
- package/dist/extractors/javascript.js +265 -1
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/features/boundaries.d.ts +2 -2
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +2 -31
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/snapshot.d.ts.map +1 -1
- package/dist/features/snapshot.js +99 -13
- package/dist/features/snapshot.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +14 -1
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/louvain.d.ts.map +1 -1
- package/dist/graph/algorithms/louvain.js +2 -4
- package/dist/graph/algorithms/louvain.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +12 -2
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/shared/globs.d.ts +40 -0
- package/dist/shared/globs.d.ts.map +1 -0
- package/dist/shared/globs.js +126 -0
- package/dist/shared/globs.js.map +1 -0
- package/dist/types.d.ts +26 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/package.json +7 -7
- package/src/ast-analysis/visitor.ts +15 -0
- package/src/cli/commands/watch.ts +2 -0
- package/src/cli.ts +31 -8
- package/src/domain/graph/builder/context.ts +19 -0
- package/src/domain/graph/builder/helpers.ts +53 -3
- package/src/domain/graph/builder/pipeline.ts +235 -49
- package/src/domain/graph/builder/stages/build-edges.ts +80 -6
- package/src/domain/graph/builder/stages/build-structure.ts +2 -2
- package/src/domain/graph/builder/stages/collect-files.ts +56 -26
- package/src/domain/graph/builder/stages/detect-changes.ts +118 -61
- package/src/domain/graph/builder/stages/finalize.ts +27 -4
- package/src/domain/graph/builder/stages/run-analyses.ts +5 -26
- package/src/domain/graph/journal.ts +284 -27
- package/src/domain/graph/watcher.ts +29 -9
- package/src/domain/parser.ts +166 -73
- package/src/domain/search/generator.ts +34 -2
- package/src/domain/wasm-worker-entry.ts +788 -0
- package/src/domain/wasm-worker-pool.ts +330 -0
- package/src/domain/wasm-worker-protocol.ts +81 -0
- package/src/extractors/javascript.ts +290 -1
- package/src/features/boundaries.ts +2 -27
- package/src/features/snapshot.ts +93 -14
- package/src/features/structure.ts +17 -1
- package/src/graph/algorithms/louvain.ts +2 -4
- package/src/infrastructure/config.ts +12 -2
- package/src/shared/globs.ts +121 -0
- package/src/types.ts +26 -1
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM parse worker entry point.
|
|
3
|
+
*
|
|
4
|
+
* Runs as a Node.js `worker_threads` worker. Owns every tree-sitter WASM call
|
|
5
|
+
* so that fatal V8 aborts from the grammar (#965) kill only this worker —
|
|
6
|
+
* never the main build process.
|
|
7
|
+
*
|
|
8
|
+
* For each `parse` request:
|
|
9
|
+
* 1. Pick the language by file extension.
|
|
10
|
+
* 2. Lazy-load the WASM grammar (first request per language).
|
|
11
|
+
* 3. Parse the source.
|
|
12
|
+
* 4. Run the extractor.
|
|
13
|
+
* 5. Run the unified AST visitor walk (ast-store, complexity, CFG, dataflow)
|
|
14
|
+
* so that all analysis data is pre-computed before the tree is freed.
|
|
15
|
+
* 6. `tree.delete()` in a finally block to release WASM linear memory.
|
|
16
|
+
* 7. Serialize ExtractorOutput to a structured-clone-safe form and respond.
|
|
17
|
+
*
|
|
18
|
+
* The worker does NOT import from `./parser.js` — that module owns
|
|
19
|
+
* process-global parser/grammar caches that are not worker-safe. Instead
|
|
20
|
+
* this file keeps its own per-worker caches and its own (local) language
|
|
21
|
+
* registry. Extractors are imported directly from `../extractors/index.js`.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { parentPort } from 'node:worker_threads';
|
|
27
|
+
import type { Tree } from 'web-tree-sitter';
|
|
28
|
+
import { Language, Parser, Query } from 'web-tree-sitter';
|
|
29
|
+
import { computeLOCMetrics, computeMaintainabilityIndex } from '../ast-analysis/metrics.js';
|
|
30
|
+
import {
|
|
31
|
+
AST_TYPE_MAPS,
|
|
32
|
+
CFG_RULES,
|
|
33
|
+
COMPLEXITY_RULES,
|
|
34
|
+
DATAFLOW_RULES,
|
|
35
|
+
HALSTEAD_RULES,
|
|
36
|
+
} from '../ast-analysis/rules/index.js';
|
|
37
|
+
import { walkWithVisitors } from '../ast-analysis/visitor.js';
|
|
38
|
+
import { functionName as getFuncName } from '../ast-analysis/visitor-utils.js';
|
|
39
|
+
import { createAstStoreVisitor } from '../ast-analysis/visitors/ast-store-visitor.js';
|
|
40
|
+
import { createCfgVisitor } from '../ast-analysis/visitors/cfg-visitor.js';
|
|
41
|
+
import { createComplexityVisitor } from '../ast-analysis/visitors/complexity-visitor.js';
|
|
42
|
+
import { createDataflowVisitor } from '../ast-analysis/visitors/dataflow-visitor.js';
|
|
43
|
+
import {
|
|
44
|
+
extractBashSymbols,
|
|
45
|
+
extractClojureSymbols,
|
|
46
|
+
extractCppSymbols,
|
|
47
|
+
extractCSharpSymbols,
|
|
48
|
+
extractCSymbols,
|
|
49
|
+
extractCudaSymbols,
|
|
50
|
+
extractDartSymbols,
|
|
51
|
+
extractElixirSymbols,
|
|
52
|
+
extractErlangSymbols,
|
|
53
|
+
extractFSharpSymbols,
|
|
54
|
+
extractGleamSymbols,
|
|
55
|
+
extractGoSymbols,
|
|
56
|
+
extractGroovySymbols,
|
|
57
|
+
extractHaskellSymbols,
|
|
58
|
+
extractHCLSymbols,
|
|
59
|
+
extractJavaSymbols,
|
|
60
|
+
extractJuliaSymbols,
|
|
61
|
+
extractKotlinSymbols,
|
|
62
|
+
extractLuaSymbols,
|
|
63
|
+
extractObjCSymbols,
|
|
64
|
+
extractOCamlSymbols,
|
|
65
|
+
extractPHPSymbols,
|
|
66
|
+
extractPythonSymbols,
|
|
67
|
+
extractRSymbols,
|
|
68
|
+
extractRubySymbols,
|
|
69
|
+
extractRustSymbols,
|
|
70
|
+
extractScalaSymbols,
|
|
71
|
+
extractSoliditySymbols,
|
|
72
|
+
extractSwiftSymbols,
|
|
73
|
+
extractSymbols,
|
|
74
|
+
extractVerilogSymbols,
|
|
75
|
+
extractZigSymbols,
|
|
76
|
+
} from '../extractors/index.js';
|
|
77
|
+
import type {
|
|
78
|
+
CfgBlock,
|
|
79
|
+
CfgEdge,
|
|
80
|
+
DataflowResult,
|
|
81
|
+
Definition,
|
|
82
|
+
ExtractorOutput,
|
|
83
|
+
LanguageId,
|
|
84
|
+
LanguageRegistryEntry,
|
|
85
|
+
TreeSitterNode,
|
|
86
|
+
Visitor,
|
|
87
|
+
WalkOptions,
|
|
88
|
+
WalkResults,
|
|
89
|
+
} from '../types.js';
|
|
90
|
+
import type {
|
|
91
|
+
SerializedExtractorOutput,
|
|
92
|
+
WorkerParseRequest,
|
|
93
|
+
WorkerRequest,
|
|
94
|
+
WorkerResponse,
|
|
95
|
+
} from './wasm-worker-protocol.js';
|
|
96
|
+
|
|
97
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
98
|
+
|
|
99
|
+
/** Grammars ship at `<repo-root>/grammars/`. Worker file is at
|
|
100
|
+
* `src/domain/wasm-worker-entry.ts` (or `dist/domain/wasm-worker-entry.js`);
|
|
101
|
+
* both resolve to the same relative path. */
|
|
102
|
+
function grammarPath(name: string): string {
|
|
103
|
+
return path.join(__dirname, '..', '..', 'grammars', name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Shared JS/TS/TSX query patterns (mirrors parser.ts) ─────────────────────
|
|
107
|
+
|
|
108
|
+
const COMMON_QUERY_PATTERNS: string[] = [
|
|
109
|
+
'(function_declaration name: (identifier) @fn_name) @fn_node',
|
|
110
|
+
'(variable_declarator name: (identifier) @varfn_name value: (arrow_function) @varfn_value)',
|
|
111
|
+
'(variable_declarator name: (identifier) @varfn_name value: (function_expression) @varfn_value)',
|
|
112
|
+
'(method_definition name: (property_identifier) @meth_name) @meth_node',
|
|
113
|
+
'(method_definition name: (private_property_identifier) @meth_name) @meth_node',
|
|
114
|
+
'(import_statement source: (string) @imp_source) @imp_node',
|
|
115
|
+
'(export_statement) @exp_node',
|
|
116
|
+
'(call_expression function: (identifier) @callfn_name) @callfn_node',
|
|
117
|
+
'(call_expression function: (member_expression) @callmem_fn) @callmem_node',
|
|
118
|
+
'(call_expression function: (subscript_expression) @callsub_fn) @callsub_node',
|
|
119
|
+
'(new_expression constructor: (identifier) @newfn_name) @newfn_node',
|
|
120
|
+
'(new_expression constructor: (member_expression) @newmem_fn) @newmem_node',
|
|
121
|
+
'(expression_statement (assignment_expression left: (member_expression) @assign_left right: (_) @assign_right)) @assign_node',
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const JS_CLASS_PATTERN: string = '(class_declaration name: (identifier) @cls_name) @cls_node';
|
|
125
|
+
|
|
126
|
+
const TS_EXTRA_PATTERNS: string[] = [
|
|
127
|
+
'(class_declaration name: (type_identifier) @cls_name) @cls_node',
|
|
128
|
+
'(interface_declaration name: (type_identifier) @iface_name) @iface_node',
|
|
129
|
+
'(type_alias_declaration name: (type_identifier) @type_name) @type_node',
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// ── Local language registry ─────────────────────────────────────────────────
|
|
133
|
+
// Local copy — re-using parser.ts's registry would drag in its process-wide
|
|
134
|
+
// parser/grammar caches, which are not safe to share across worker threads.
|
|
135
|
+
|
|
136
|
+
const LANGUAGE_REGISTRY: LanguageRegistryEntry[] = [
|
|
137
|
+
{
|
|
138
|
+
id: 'javascript',
|
|
139
|
+
extensions: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
140
|
+
grammarFile: 'tree-sitter-javascript.wasm',
|
|
141
|
+
extractor: extractSymbols,
|
|
142
|
+
required: true,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'typescript',
|
|
146
|
+
extensions: ['.ts'],
|
|
147
|
+
grammarFile: 'tree-sitter-typescript.wasm',
|
|
148
|
+
extractor: extractSymbols,
|
|
149
|
+
required: true,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'tsx',
|
|
153
|
+
extensions: ['.tsx'],
|
|
154
|
+
grammarFile: 'tree-sitter-tsx.wasm',
|
|
155
|
+
extractor: extractSymbols,
|
|
156
|
+
required: true,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'hcl',
|
|
160
|
+
extensions: ['.tf', '.hcl'],
|
|
161
|
+
grammarFile: 'tree-sitter-hcl.wasm',
|
|
162
|
+
extractor: extractHCLSymbols,
|
|
163
|
+
required: false,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'python',
|
|
167
|
+
extensions: ['.py', '.pyi'],
|
|
168
|
+
grammarFile: 'tree-sitter-python.wasm',
|
|
169
|
+
extractor: extractPythonSymbols,
|
|
170
|
+
required: false,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'go',
|
|
174
|
+
extensions: ['.go'],
|
|
175
|
+
grammarFile: 'tree-sitter-go.wasm',
|
|
176
|
+
extractor: extractGoSymbols,
|
|
177
|
+
required: false,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'rust',
|
|
181
|
+
extensions: ['.rs'],
|
|
182
|
+
grammarFile: 'tree-sitter-rust.wasm',
|
|
183
|
+
extractor: extractRustSymbols,
|
|
184
|
+
required: false,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'java',
|
|
188
|
+
extensions: ['.java'],
|
|
189
|
+
grammarFile: 'tree-sitter-java.wasm',
|
|
190
|
+
extractor: extractJavaSymbols,
|
|
191
|
+
required: false,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'csharp',
|
|
195
|
+
extensions: ['.cs'],
|
|
196
|
+
grammarFile: 'tree-sitter-c_sharp.wasm',
|
|
197
|
+
extractor: extractCSharpSymbols,
|
|
198
|
+
required: false,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'ruby',
|
|
202
|
+
extensions: ['.rb', '.rake', '.gemspec'],
|
|
203
|
+
grammarFile: 'tree-sitter-ruby.wasm',
|
|
204
|
+
extractor: extractRubySymbols,
|
|
205
|
+
required: false,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'php',
|
|
209
|
+
extensions: ['.php', '.phtml'],
|
|
210
|
+
grammarFile: 'tree-sitter-php.wasm',
|
|
211
|
+
extractor: extractPHPSymbols,
|
|
212
|
+
required: false,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: 'c',
|
|
216
|
+
extensions: ['.c', '.h'],
|
|
217
|
+
grammarFile: 'tree-sitter-c.wasm',
|
|
218
|
+
extractor: extractCSymbols,
|
|
219
|
+
required: false,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'cpp',
|
|
223
|
+
extensions: ['.cpp', '.cc', '.cxx', '.hpp'],
|
|
224
|
+
grammarFile: 'tree-sitter-cpp.wasm',
|
|
225
|
+
extractor: extractCppSymbols,
|
|
226
|
+
required: false,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: 'kotlin',
|
|
230
|
+
extensions: ['.kt', '.kts'],
|
|
231
|
+
grammarFile: 'tree-sitter-kotlin.wasm',
|
|
232
|
+
extractor: extractKotlinSymbols,
|
|
233
|
+
required: false,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'swift',
|
|
237
|
+
extensions: ['.swift'],
|
|
238
|
+
grammarFile: 'tree-sitter-swift.wasm',
|
|
239
|
+
extractor: extractSwiftSymbols,
|
|
240
|
+
required: false,
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'scala',
|
|
244
|
+
extensions: ['.scala'],
|
|
245
|
+
grammarFile: 'tree-sitter-scala.wasm',
|
|
246
|
+
extractor: extractScalaSymbols,
|
|
247
|
+
required: false,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'bash',
|
|
251
|
+
extensions: ['.sh', '.bash'],
|
|
252
|
+
grammarFile: 'tree-sitter-bash.wasm',
|
|
253
|
+
extractor: extractBashSymbols,
|
|
254
|
+
required: false,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'elixir',
|
|
258
|
+
extensions: ['.ex', '.exs'],
|
|
259
|
+
grammarFile: 'tree-sitter-elixir.wasm',
|
|
260
|
+
extractor: extractElixirSymbols,
|
|
261
|
+
required: false,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'lua',
|
|
265
|
+
extensions: ['.lua'],
|
|
266
|
+
grammarFile: 'tree-sitter-lua.wasm',
|
|
267
|
+
extractor: extractLuaSymbols,
|
|
268
|
+
required: false,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'dart',
|
|
272
|
+
extensions: ['.dart'],
|
|
273
|
+
grammarFile: 'tree-sitter-dart.wasm',
|
|
274
|
+
extractor: extractDartSymbols,
|
|
275
|
+
required: false,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: 'zig',
|
|
279
|
+
extensions: ['.zig'],
|
|
280
|
+
grammarFile: 'tree-sitter-zig.wasm',
|
|
281
|
+
extractor: extractZigSymbols,
|
|
282
|
+
required: false,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'haskell',
|
|
286
|
+
extensions: ['.hs'],
|
|
287
|
+
grammarFile: 'tree-sitter-haskell.wasm',
|
|
288
|
+
extractor: extractHaskellSymbols,
|
|
289
|
+
required: false,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: 'ocaml',
|
|
293
|
+
extensions: ['.ml'],
|
|
294
|
+
grammarFile: 'tree-sitter-ocaml.wasm',
|
|
295
|
+
extractor: extractOCamlSymbols,
|
|
296
|
+
required: false,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
id: 'ocaml-interface',
|
|
300
|
+
extensions: ['.mli'],
|
|
301
|
+
grammarFile: 'tree-sitter-ocaml_interface.wasm',
|
|
302
|
+
extractor: extractOCamlSymbols,
|
|
303
|
+
required: false,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 'fsharp',
|
|
307
|
+
extensions: ['.fs', '.fsx', '.fsi'],
|
|
308
|
+
grammarFile: 'tree-sitter-fsharp.wasm',
|
|
309
|
+
extractor: extractFSharpSymbols,
|
|
310
|
+
required: false,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: 'gleam',
|
|
314
|
+
extensions: ['.gleam'],
|
|
315
|
+
grammarFile: 'tree-sitter-gleam.wasm',
|
|
316
|
+
extractor: extractGleamSymbols,
|
|
317
|
+
required: false,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: 'clojure',
|
|
321
|
+
extensions: ['.clj', '.cljs', '.cljc'],
|
|
322
|
+
grammarFile: 'tree-sitter-clojure.wasm',
|
|
323
|
+
extractor: extractClojureSymbols,
|
|
324
|
+
required: false,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: 'julia',
|
|
328
|
+
extensions: ['.jl'],
|
|
329
|
+
grammarFile: 'tree-sitter-julia.wasm',
|
|
330
|
+
extractor: extractJuliaSymbols,
|
|
331
|
+
required: false,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: 'r',
|
|
335
|
+
extensions: ['.r', '.R'],
|
|
336
|
+
grammarFile: 'tree-sitter-r.wasm',
|
|
337
|
+
extractor: extractRSymbols,
|
|
338
|
+
required: false,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 'erlang',
|
|
342
|
+
extensions: ['.erl', '.hrl'],
|
|
343
|
+
grammarFile: 'tree-sitter-erlang.wasm',
|
|
344
|
+
extractor: extractErlangSymbols,
|
|
345
|
+
required: false,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: 'solidity',
|
|
349
|
+
extensions: ['.sol'],
|
|
350
|
+
grammarFile: 'tree-sitter-solidity.wasm',
|
|
351
|
+
extractor: extractSoliditySymbols,
|
|
352
|
+
required: false,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'objc',
|
|
356
|
+
extensions: ['.m'],
|
|
357
|
+
grammarFile: 'tree-sitter-objc.wasm',
|
|
358
|
+
extractor: extractObjCSymbols,
|
|
359
|
+
required: false,
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: 'cuda',
|
|
363
|
+
extensions: ['.cu', '.cuh'],
|
|
364
|
+
grammarFile: 'tree-sitter-cuda.wasm',
|
|
365
|
+
extractor: extractCudaSymbols,
|
|
366
|
+
required: false,
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: 'groovy',
|
|
370
|
+
extensions: ['.groovy', '.gvy'],
|
|
371
|
+
grammarFile: 'tree-sitter-groovy.wasm',
|
|
372
|
+
extractor: extractGroovySymbols,
|
|
373
|
+
required: false,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: 'verilog',
|
|
377
|
+
extensions: ['.v', '.sv'],
|
|
378
|
+
grammarFile: 'tree-sitter-verilog.wasm',
|
|
379
|
+
extractor: extractVerilogSymbols,
|
|
380
|
+
required: false,
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
const _extToLang: Map<string, LanguageRegistryEntry> = new Map();
|
|
385
|
+
for (const entry of LANGUAGE_REGISTRY) {
|
|
386
|
+
for (const ext of entry.extensions) {
|
|
387
|
+
_extToLang.set(ext.toLowerCase(), entry);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ── Per-worker caches (not shared across workers) ───────────────────────────
|
|
392
|
+
|
|
393
|
+
let _runtimeInitialized = false;
|
|
394
|
+
// Value of `null` means "tried to load, grammar is optional and failed" —
|
|
395
|
+
// we cache the failure so we don't retry on every file.
|
|
396
|
+
const _parsers: Map<string, Parser | null> = new Map();
|
|
397
|
+
const _queries: Map<string, Query> = new Map();
|
|
398
|
+
|
|
399
|
+
async function initRuntime(): Promise<void> {
|
|
400
|
+
if (_runtimeInitialized) return;
|
|
401
|
+
await Parser.init();
|
|
402
|
+
_runtimeInitialized = true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Load the grammar for a language on demand. Returns the cached parser, or
|
|
407
|
+
* `null` if the grammar is optional and failed to load (same convention as
|
|
408
|
+
* parser.ts). Throws for required grammars that fail.
|
|
409
|
+
*/
|
|
410
|
+
async function loadLanguageLazy(entry: LanguageRegistryEntry): Promise<Parser | null> {
|
|
411
|
+
if (_parsers.has(entry.id)) return _parsers.get(entry.id) ?? null;
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const lang = await Language.load(grammarPath(entry.grammarFile));
|
|
415
|
+
const parser = new Parser();
|
|
416
|
+
parser.setLanguage(lang);
|
|
417
|
+
_parsers.set(entry.id, parser);
|
|
418
|
+
|
|
419
|
+
// Build the JS/TS/TSX query (mirrors parser.ts::doLoadLanguage)
|
|
420
|
+
if (entry.extractor === extractSymbols && !_queries.has(entry.id)) {
|
|
421
|
+
const isTS = entry.id === 'typescript' || entry.id === 'tsx';
|
|
422
|
+
const patterns = isTS
|
|
423
|
+
? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
|
|
424
|
+
: [...COMMON_QUERY_PATTERNS, JS_CLASS_PATTERN];
|
|
425
|
+
_queries.set(entry.id, new Query(lang, patterns.join('\n')));
|
|
426
|
+
}
|
|
427
|
+
return parser;
|
|
428
|
+
} catch (e: unknown) {
|
|
429
|
+
if (entry.required) {
|
|
430
|
+
throw new Error(`Required parser ${entry.id} failed to initialize: ${(e as Error).message}`);
|
|
431
|
+
}
|
|
432
|
+
_parsers.set(entry.id, null);
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ── Per-function walk-result shapes (mirrors engine.ts, kept local) ─────────
|
|
438
|
+
|
|
439
|
+
interface ComplexityFuncResult {
|
|
440
|
+
funcNode: TreeSitterNode;
|
|
441
|
+
funcName: string | null;
|
|
442
|
+
metrics: {
|
|
443
|
+
cognitive: number;
|
|
444
|
+
cyclomatic: number;
|
|
445
|
+
maxNesting: number;
|
|
446
|
+
halstead?: { volume: number; difficulty: number; effort: number; bugs: number };
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
interface CfgFuncResult {
|
|
451
|
+
funcNode: TreeSitterNode;
|
|
452
|
+
blocks: CfgBlock[];
|
|
453
|
+
edges: CfgEdge[];
|
|
454
|
+
cyclomatic?: number;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ── Helpers mirroring engine.ts (copied, db-free) ───────────────────────────
|
|
458
|
+
|
|
459
|
+
function hasFuncBody(d: {
|
|
460
|
+
name: string;
|
|
461
|
+
kind: string;
|
|
462
|
+
line: number;
|
|
463
|
+
endLine?: number | null;
|
|
464
|
+
}): boolean {
|
|
465
|
+
return (
|
|
466
|
+
(d.kind === 'function' || d.kind === 'method') &&
|
|
467
|
+
d.line > 0 &&
|
|
468
|
+
d.endLine != null &&
|
|
469
|
+
d.endLine > d.line &&
|
|
470
|
+
!d.name.includes('.')
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function indexByLine<T extends { funcNode: TreeSitterNode }>(results: T[]): Map<number, T[]> {
|
|
475
|
+
const byLine = new Map<number, T[]>();
|
|
476
|
+
for (const r of results) {
|
|
477
|
+
if (!r.funcNode) continue;
|
|
478
|
+
const line = r.funcNode.startPosition.row + 1;
|
|
479
|
+
if (!byLine.has(line)) byLine.set(line, []);
|
|
480
|
+
byLine.get(line)?.push(r);
|
|
481
|
+
}
|
|
482
|
+
return byLine;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function matchResultToDef<T extends { funcNode: TreeSitterNode }>(
|
|
486
|
+
candidates: T[] | undefined,
|
|
487
|
+
defName: string,
|
|
488
|
+
): T | undefined {
|
|
489
|
+
if (!candidates) return undefined;
|
|
490
|
+
if (candidates.length === 1) return candidates[0];
|
|
491
|
+
return (
|
|
492
|
+
candidates.find((r) => {
|
|
493
|
+
const n = r.funcNode.childForFieldName('name');
|
|
494
|
+
return n && n.text === defName;
|
|
495
|
+
}) ?? candidates[0]
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** Override a definition's cyclomatic complexity with a CFG-derived value and recompute MI. */
|
|
500
|
+
function overrideCyclomaticFromCfg(def: Definition, cfgCyclomatic: number): void {
|
|
501
|
+
if (!def.complexity) return;
|
|
502
|
+
if (cfgCyclomatic <= 0) return;
|
|
503
|
+
def.complexity.cyclomatic = cfgCyclomatic;
|
|
504
|
+
const { loc, halstead } = def.complexity;
|
|
505
|
+
const volume = halstead ? halstead.volume : 0;
|
|
506
|
+
const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
|
|
507
|
+
def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
|
|
508
|
+
volume,
|
|
509
|
+
cfgCyclomatic,
|
|
510
|
+
loc?.sloc ?? 0,
|
|
511
|
+
commentRatio,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function storeComplexityResults(results: WalkResults, defs: Definition[], langId: string): void {
|
|
516
|
+
const byLine = indexByLine((results.complexity || []) as ComplexityFuncResult[]);
|
|
517
|
+
for (const def of defs) {
|
|
518
|
+
if ((def.kind === 'function' || def.kind === 'method') && def.line && !def.complexity) {
|
|
519
|
+
const funcResult = matchResultToDef(byLine.get(def.line), def.name);
|
|
520
|
+
if (!funcResult) continue;
|
|
521
|
+
const { metrics } = funcResult;
|
|
522
|
+
const loc = computeLOCMetrics(funcResult.funcNode, langId);
|
|
523
|
+
const volume = metrics.halstead ? metrics.halstead.volume : 0;
|
|
524
|
+
const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
|
|
525
|
+
const mi = computeMaintainabilityIndex(volume, metrics.cyclomatic, loc.sloc, commentRatio);
|
|
526
|
+
def.complexity = {
|
|
527
|
+
cognitive: metrics.cognitive,
|
|
528
|
+
cyclomatic: metrics.cyclomatic,
|
|
529
|
+
maxNesting: metrics.maxNesting,
|
|
530
|
+
halstead: metrics.halstead,
|
|
531
|
+
loc,
|
|
532
|
+
maintainabilityIndex: mi,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function storeCfgResults(results: WalkResults, defs: Definition[]): void {
|
|
539
|
+
const byLine = indexByLine((results.cfg || []) as CfgFuncResult[]);
|
|
540
|
+
for (const def of defs) {
|
|
541
|
+
if (
|
|
542
|
+
(def.kind === 'function' || def.kind === 'method') &&
|
|
543
|
+
def.line &&
|
|
544
|
+
!def.cfg?.blocks?.length
|
|
545
|
+
) {
|
|
546
|
+
const cfgResult = matchResultToDef(byLine.get(def.line), def.name);
|
|
547
|
+
if (!cfgResult) continue;
|
|
548
|
+
def.cfg = { blocks: cfgResult.blocks, edges: cfgResult.edges };
|
|
549
|
+
if (cfgResult.cyclomatic != null) {
|
|
550
|
+
overrideCyclomaticFromCfg(def, cfgResult.cyclomatic);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ── Per-file visitor setup (db-free version of engine.ts::setupVisitors) ────
|
|
557
|
+
|
|
558
|
+
interface SetupResult {
|
|
559
|
+
visitors: Visitor[];
|
|
560
|
+
walkerOpts: WalkOptions;
|
|
561
|
+
astVisitor: Visitor | null;
|
|
562
|
+
complexityVisitor: Visitor | null;
|
|
563
|
+
cfgVisitor: Visitor | null;
|
|
564
|
+
dataflowVisitor: Visitor | null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function setupVisitorsLocal(
|
|
568
|
+
symbols: ExtractorOutput,
|
|
569
|
+
relPath: string,
|
|
570
|
+
langId: string,
|
|
571
|
+
opts: WorkerParseRequest['opts'],
|
|
572
|
+
): SetupResult {
|
|
573
|
+
const defs = symbols.definitions || [];
|
|
574
|
+
const visitors: Visitor[] = [];
|
|
575
|
+
const walkerOpts: WalkOptions = {
|
|
576
|
+
functionNodeTypes: new Set<string>(),
|
|
577
|
+
nestingNodeTypes: new Set<string>(),
|
|
578
|
+
getFunctionName: (_node: TreeSitterNode) => null,
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
// AST-store: db-free — pass an empty nodeIdMap. The main thread re-resolves
|
|
582
|
+
// parent node IDs in features/ast.ts::collectFileAstRows.
|
|
583
|
+
let astVisitor: Visitor | null = null;
|
|
584
|
+
if (opts.ast) {
|
|
585
|
+
const astTypeMap = AST_TYPE_MAPS.get(langId);
|
|
586
|
+
if (astTypeMap) {
|
|
587
|
+
astVisitor = createAstStoreVisitor(astTypeMap, defs, relPath, new Map<string, number>());
|
|
588
|
+
visitors.push(astVisitor);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Complexity
|
|
593
|
+
let complexityVisitor: Visitor | null = null;
|
|
594
|
+
if (opts.complexity) {
|
|
595
|
+
const cRules = COMPLEXITY_RULES.get(langId);
|
|
596
|
+
if (cRules && defs.some((d) => hasFuncBody(d) && !d.complexity)) {
|
|
597
|
+
const hRules = HALSTEAD_RULES.get(langId);
|
|
598
|
+
complexityVisitor = createComplexityVisitor(cRules, hRules, {
|
|
599
|
+
fileLevelWalk: true,
|
|
600
|
+
langId,
|
|
601
|
+
});
|
|
602
|
+
for (const t of cRules.nestingNodes) walkerOpts.nestingNodeTypes?.add(t);
|
|
603
|
+
const dfRules = DATAFLOW_RULES.get(langId);
|
|
604
|
+
walkerOpts.getFunctionName = (node: TreeSitterNode): string | null => {
|
|
605
|
+
const nameNode = node.childForFieldName('name');
|
|
606
|
+
if (nameNode) return nameNode.text;
|
|
607
|
+
// dfRules shape varies per language; visitor-utils accepts any shape
|
|
608
|
+
if (dfRules) return getFuncName(node, dfRules as any);
|
|
609
|
+
return null;
|
|
610
|
+
};
|
|
611
|
+
visitors.push(complexityVisitor);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// CFG
|
|
616
|
+
let cfgVisitor: Visitor | null = null;
|
|
617
|
+
if (opts.cfg) {
|
|
618
|
+
const cfgRulesForLang = CFG_RULES.get(langId);
|
|
619
|
+
if (
|
|
620
|
+
cfgRulesForLang &&
|
|
621
|
+
defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks))
|
|
622
|
+
) {
|
|
623
|
+
cfgVisitor = createCfgVisitor(cfgRulesForLang);
|
|
624
|
+
visitors.push(cfgVisitor);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Dataflow
|
|
629
|
+
let dataflowVisitor: Visitor | null = null;
|
|
630
|
+
if (opts.dataflow) {
|
|
631
|
+
const dfRules = DATAFLOW_RULES.get(langId);
|
|
632
|
+
if (dfRules && !symbols.dataflow) {
|
|
633
|
+
dataflowVisitor = createDataflowVisitor(dfRules);
|
|
634
|
+
visitors.push(dataflowVisitor);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return { visitors, walkerOpts, astVisitor, complexityVisitor, cfgVisitor, dataflowVisitor };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ── Main parse handler ──────────────────────────────────────────────────────
|
|
642
|
+
|
|
643
|
+
async function handleParse(msg: WorkerParseRequest): Promise<SerializedExtractorOutput | null> {
|
|
644
|
+
const ext = path.extname(msg.filePath).toLowerCase();
|
|
645
|
+
const entry = _extToLang.get(ext);
|
|
646
|
+
if (!entry) return null;
|
|
647
|
+
|
|
648
|
+
await initRuntime();
|
|
649
|
+
const parser = await loadLanguageLazy(entry);
|
|
650
|
+
if (!parser) return null;
|
|
651
|
+
|
|
652
|
+
let tree: Tree | null = null;
|
|
653
|
+
try {
|
|
654
|
+
try {
|
|
655
|
+
tree = parser.parse(msg.code);
|
|
656
|
+
} catch (e: unknown) {
|
|
657
|
+
// Parse error — report back but keep worker alive.
|
|
658
|
+
throw new Error(`parse failed: ${(e as Error).message}`);
|
|
659
|
+
}
|
|
660
|
+
if (!tree) return null;
|
|
661
|
+
|
|
662
|
+
// Extractor — on failure, skip file (ok:true, null) to match parser.ts
|
|
663
|
+
// behavior where extractor issues don't crash the build.
|
|
664
|
+
let symbols: ExtractorOutput | null;
|
|
665
|
+
try {
|
|
666
|
+
const query = _queries.get(entry.id);
|
|
667
|
+
// tree-sitter's Tree/Query are structurally compatible with
|
|
668
|
+
// TreeSitterTree/TreeSitterQuery at runtime — same cast style as
|
|
669
|
+
// parser.ts::wasmExtractSymbols (parser.ts:789).
|
|
670
|
+
symbols = entry.extractor(tree as any, msg.filePath, query as any) ?? null;
|
|
671
|
+
} catch {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
if (!symbols) return null;
|
|
675
|
+
|
|
676
|
+
// Unified visitor walk — mirrors engine.ts:791-829. Runs BEFORE tree.delete()
|
|
677
|
+
// because storeComplexityResults/storeCfgResults read funcNode off live nodes.
|
|
678
|
+
const { visitors, walkerOpts, astVisitor, complexityVisitor, cfgVisitor, dataflowVisitor } =
|
|
679
|
+
setupVisitorsLocal(symbols, msg.filePath, entry.id, msg.opts);
|
|
680
|
+
|
|
681
|
+
// astNodes are kept in the serialized shape (without `file`/`parentNodeId`),
|
|
682
|
+
// not assigned back to symbols.astNodes — ExtractorOutput.astNodes is
|
|
683
|
+
// ASTNodeRow[] (DB row shape with node_id), which is a different type.
|
|
684
|
+
let serializedAstNodes: SerializedExtractorOutput['astNodes'];
|
|
685
|
+
|
|
686
|
+
if (visitors.length > 0) {
|
|
687
|
+
// rootNode shape matches TreeSitterNode at runtime — same cast as parser.ts:789.
|
|
688
|
+
const results = walkWithVisitors(tree.rootNode as any, visitors, entry.id, walkerOpts);
|
|
689
|
+
|
|
690
|
+
const defs = symbols.definitions || [];
|
|
691
|
+
if (astVisitor) {
|
|
692
|
+
const astRows = (results['ast-store'] || []) as Array<{
|
|
693
|
+
line: number;
|
|
694
|
+
kind: string;
|
|
695
|
+
name: string | null | undefined;
|
|
696
|
+
text: string | null;
|
|
697
|
+
receiver: string | null;
|
|
698
|
+
file?: string;
|
|
699
|
+
parentNodeId?: number | null;
|
|
700
|
+
}>;
|
|
701
|
+
if (astRows.length > 0) {
|
|
702
|
+
// Strip `file` and `parentNodeId` — main thread re-resolves parent IDs
|
|
703
|
+
// against its DB in features/ast.ts::collectFileAstRows, and `file` is
|
|
704
|
+
// known from the map key.
|
|
705
|
+
serializedAstNodes = astRows.map((n) => ({
|
|
706
|
+
line: n.line,
|
|
707
|
+
kind: n.kind,
|
|
708
|
+
name: n.name ?? '',
|
|
709
|
+
text: n.text ?? undefined,
|
|
710
|
+
receiver: n.receiver ?? undefined,
|
|
711
|
+
}));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (complexityVisitor) storeComplexityResults(results, defs, entry.id);
|
|
716
|
+
if (cfgVisitor) storeCfgResults(results, defs);
|
|
717
|
+
if (dataflowVisitor) symbols.dataflow = results.dataflow as DataflowResult;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Serialize — convert Map<string, TypeMapEntry> to tuple array for the wire.
|
|
721
|
+
const serialized: SerializedExtractorOutput = {
|
|
722
|
+
definitions: symbols.definitions,
|
|
723
|
+
calls: symbols.calls,
|
|
724
|
+
imports: symbols.imports,
|
|
725
|
+
classes: symbols.classes,
|
|
726
|
+
exports: symbols.exports,
|
|
727
|
+
typeMap: Array.from(symbols.typeMap.entries()),
|
|
728
|
+
_langId: entry.id as LanguageId,
|
|
729
|
+
_lineCount: msg.code.split('\n').length,
|
|
730
|
+
dataflow: symbols.dataflow,
|
|
731
|
+
astNodes: serializedAstNodes,
|
|
732
|
+
};
|
|
733
|
+
// _tree is deliberately not serialized — it cannot cross the worker boundary.
|
|
734
|
+
return serialized;
|
|
735
|
+
} finally {
|
|
736
|
+
// ALWAYS release WASM memory before responding. Deferring this would let
|
|
737
|
+
// trees accumulate in the worker's WASM heap across requests and defeat
|
|
738
|
+
// the point of isolating parse calls.
|
|
739
|
+
if (tree && typeof (tree as unknown as { delete?: () => void }).delete === 'function') {
|
|
740
|
+
try {
|
|
741
|
+
(tree as unknown as { delete: () => void }).delete();
|
|
742
|
+
} catch {
|
|
743
|
+
// best-effort cleanup — swallow; worker continues.
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ── Worker message loop ─────────────────────────────────────────────────────
|
|
750
|
+
|
|
751
|
+
if (!parentPort) {
|
|
752
|
+
throw new Error('wasm-worker-entry must be run as a worker_thread');
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Test-only crash marker. When the code contains this exact magic token
|
|
756
|
+
// and CODEGRAPH_WASM_WORKER_TEST_CRASH=1 is set in the worker env, the
|
|
757
|
+
// worker calls process.exit(1) mid-parse — simulating a V8 fatal from the
|
|
758
|
+
// grammar so we can unit-test pool crash recovery without a real V8 abort.
|
|
759
|
+
const TEST_CRASH_MAGIC = '__CODEGRAPH_WASM_WORKER_TEST_CRASH__';
|
|
760
|
+
|
|
761
|
+
// The pool terminates workers via `Worker.terminate()`; no graceful-shutdown
|
|
762
|
+
// handshake is needed. Worker only handles `parse` messages.
|
|
763
|
+
parentPort.on('message', async (msg: WorkerRequest) => {
|
|
764
|
+
if (msg.type !== 'parse') return;
|
|
765
|
+
|
|
766
|
+
if (
|
|
767
|
+
process.env.CODEGRAPH_WASM_WORKER_TEST_CRASH === '1' &&
|
|
768
|
+
typeof msg.code === 'string' &&
|
|
769
|
+
msg.code.includes(TEST_CRASH_MAGIC)
|
|
770
|
+
) {
|
|
771
|
+
// Simulate a fatal V8 abort — hard-exit the worker.
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
try {
|
|
776
|
+
const result = await handleParse(msg);
|
|
777
|
+
const res: WorkerResponse = { type: 'result', id: msg.id, ok: true, result };
|
|
778
|
+
parentPort!.postMessage(res);
|
|
779
|
+
} catch (e: unknown) {
|
|
780
|
+
const res: WorkerResponse = {
|
|
781
|
+
type: 'result',
|
|
782
|
+
id: msg.id,
|
|
783
|
+
ok: false,
|
|
784
|
+
error: (e as Error).message ?? String(e),
|
|
785
|
+
};
|
|
786
|
+
parentPort!.postMessage(res);
|
|
787
|
+
}
|
|
788
|
+
});
|