@mrclrchtr/supi-tree-sitter 9.1.2 → 11.0.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.
Files changed (41) hide show
  1. package/README.md +63 -17
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/LICENSE +21 -0
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/README.md +743 -0
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/index.js +17 -0
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/constants.js +184 -0
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/parse.js +1425 -0
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/picomatch.js +361 -0
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/scan.js +403 -0
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/lib/utils.js +72 -0
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/package.json +83 -0
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/picomatch/posix.js +3 -0
  12. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +4 -1
  13. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +11 -0
  14. package/node_modules/@mrclrchtr/supi-code-runtime/src/file-associations.ts +190 -0
  15. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +22 -0
  16. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  17. package/package.json +3 -3
  18. package/src/language.ts +16 -10
  19. package/src/operation-support.ts +13 -6
  20. package/src/provider/tree-sitter-provider.ts +19 -1
  21. package/src/session/runtime-controller.ts +18 -4
  22. package/src/session/session.ts +10 -3
  23. package/src/session/structural-worker-client.ts +17 -2
  24. package/src/session/structural-worker-process.ts +22 -5
  25. package/src/session/structural-worker-protocol.ts +36 -1
  26. package/src/syntax-errors.ts +73 -0
  27. package/src/syntax-node.ts +4 -0
  28. package/src/tool/call-sites.ts +15 -9
  29. package/src/tool/callees.ts +24 -15
  30. package/src/tool/exports.ts +8 -1
  31. package/src/tool/imports.ts +233 -7
  32. package/src/tool/node-at.ts +7 -3
  33. package/src/tool/outline-expansion.ts +78 -0
  34. package/src/tool/outline-jvm.ts +25 -4
  35. package/src/tool/outline-nested.ts +390 -0
  36. package/src/tool/outline.ts +49 -28
  37. package/src/types.ts +16 -0
  38. package/src/worker/process-host.mjs +61 -24
  39. package/src/worker/runtime.ts +44 -10
  40. package/src/worker/service.ts +13 -9
  41. package/src/worker/worker-main.ts +15 -2
@@ -11,44 +11,63 @@ if (
11
11
  throw new Error("Structural Worker process received invalid startup data");
12
12
  }
13
13
 
14
- const worker = new Worker(new URL("./bootstrap.mjs", import.meta.url), {
15
- execArgv: [],
16
- workerData: { cwd, generation },
17
- });
18
14
  const cancellationFlags = new Map();
15
+ let worker = null;
19
16
  let closing = false;
20
17
  let workerExited = false;
21
18
 
22
- worker.on("message", (message) => {
23
- if (message.kind === "terminal") cancellationFlags.delete(message.requestId);
24
- send({ channel: "supi-tree-sitter-worker", message });
25
- });
26
- worker.on("error", (error) => {
27
- send(
28
- {
29
- channel: "supi-tree-sitter-worker",
30
- message: { kind: "worker-error", message: error.message },
31
- },
32
- () => stopWorker(1),
33
- );
34
- });
35
- worker.on("exit", (code) => {
36
- workerExited = true;
37
- cancellationFlags.clear();
38
- process.exitCode = code === 0 ? 1 : code;
39
- disconnect();
40
- });
41
-
42
19
  process.on("message", (value) => {
43
20
  if (!isParentChannel(value)) {
44
21
  stopWorker(1);
45
22
  return;
46
23
  }
24
+ if (!worker) {
25
+ if (!isInitialization(value.message)) {
26
+ stopWorker(1);
27
+ return;
28
+ }
29
+ startWorker(value.message);
30
+ return;
31
+ }
32
+ if (value.message.kind === "initialize") {
33
+ stopWorker(1);
34
+ return;
35
+ }
47
36
  forwardToWorker(value.message);
48
37
  });
49
38
  process.on("disconnect", () => stopWorker());
50
39
  if (!process.connected) stopWorker();
51
40
 
41
+ function startWorker(initialization) {
42
+ worker = new Worker(new URL("./bootstrap.mjs", import.meta.url), {
43
+ execArgv: [],
44
+ workerData: {
45
+ cwd,
46
+ generation,
47
+ fileAssociations: initialization.fileAssociations,
48
+ },
49
+ });
50
+ worker.on("message", (message) => {
51
+ if (message.kind === "terminal") cancellationFlags.delete(message.requestId);
52
+ send({ channel: "supi-tree-sitter-worker", message });
53
+ });
54
+ worker.on("error", (error) => {
55
+ send(
56
+ {
57
+ channel: "supi-tree-sitter-worker",
58
+ message: { kind: "worker-error", message: error.message },
59
+ },
60
+ () => stopWorker(1),
61
+ );
62
+ });
63
+ worker.on("exit", (code) => {
64
+ workerExited = true;
65
+ cancellationFlags.clear();
66
+ process.exitCode = code === 0 ? 1 : code;
67
+ disconnect();
68
+ });
69
+ }
70
+
52
71
  function forwardToWorker(message) {
53
72
  if (!message || typeof message !== "object") {
54
73
  throw new Error("Structural Worker process received an invalid message");
@@ -70,6 +89,20 @@ function forwardToWorker(message) {
70
89
  worker.postMessage(message);
71
90
  }
72
91
 
92
+ function isInitialization(value) {
93
+ return (
94
+ value !== null &&
95
+ typeof value === "object" &&
96
+ !Array.isArray(value) &&
97
+ Object.keys(value).length === 5 &&
98
+ value.kind === "initialize" &&
99
+ value.version === 1 &&
100
+ value.generation === generation &&
101
+ value.cwd === cwd &&
102
+ Array.isArray(value.fileAssociations)
103
+ );
104
+ }
105
+
73
106
  function isParentChannel(value) {
74
107
  return (
75
108
  value !== null &&
@@ -105,5 +138,9 @@ function stopWorker(exitCode) {
105
138
  closing = true;
106
139
  if (exitCode !== undefined) process.exitCode = exitCode;
107
140
  cancellationFlags.clear();
141
+ if (!worker) {
142
+ disconnect();
143
+ return;
144
+ }
108
145
  void worker.terminate().finally(disconnect);
109
146
  }
@@ -2,6 +2,7 @@
2
2
  // Tree-sitter runtime — parser management, parse, and query services.
3
3
 
4
4
  import * as path from "node:path";
5
+ import type { CFamilyFileAssociationResolver } from "@mrclrchtr/supi-code-runtime/api";
5
6
  import { resolveToolPath } from "@mrclrchtr/supi-core/path";
6
7
  import type { Language, Parser, Tree } from "web-tree-sitter";
7
8
  import { detectGrammar, resolveGrammarWasmPath } from "../language.ts";
@@ -12,6 +13,11 @@ import {
12
13
  type StructuralTimingEvent,
13
14
  startStructuralTiming,
14
15
  } from "../session/structural-timing.ts";
16
+ import {
17
+ collectSyntaxErrorRanges,
18
+ hasSyntaxErrors,
19
+ withSyntaxCompleteness,
20
+ } from "../syntax-errors.ts";
15
21
  import type { GrammarId, QueryCapture, TreeSitterResult } from "../types.ts";
16
22
  import {
17
23
  ParsedFileReadError,
@@ -39,6 +45,7 @@ interface ParserEntry {
39
45
  const QUERY_PARSED_FILE = Symbol("query-parsed-file");
40
46
 
41
47
  interface ParsedQueryInput {
48
+ readonly file: string;
42
49
  readonly grammarId: GrammarId;
43
50
  readonly tree: Tree;
44
51
  readonly source: string;
@@ -66,8 +73,14 @@ export class TreeSitterRuntime {
66
73
  constructor(
67
74
  private cwd: string,
68
75
  private onTiming?: (event: StructuralTimingEvent) => void,
76
+ private readonly fileAssociations?: CFamilyFileAssociationResolver,
69
77
  ) {}
70
78
 
79
+ /** Resolve one file's grammar under this runtime's immutable association snapshot. */
80
+ getGrammarForFile(filePath: string): GrammarId | undefined {
81
+ return detectGrammar(filePath, this.fileAssociations);
82
+ }
83
+
71
84
  /** Ensure web-tree-sitter Parser is initialized. */
72
85
  private async ensureParserInit(): Promise<typeof import("web-tree-sitter")> {
73
86
  this.assertActive();
@@ -152,15 +165,21 @@ export class TreeSitterRuntime {
152
165
  filePath: string,
153
166
  control?: StructuralRequestControl,
154
167
  ): Promise<
155
- TreeSitterResult<{
156
- tree: Tree;
157
- source: string;
158
- resolvedPath: string;
159
- grammarId: GrammarId;
160
- }>
168
+ | {
169
+ kind: "success";
170
+ data: {
171
+ tree: Tree;
172
+ source: string;
173
+ resolvedPath: string;
174
+ grammarId: GrammarId;
175
+ hasSyntaxErrors: boolean;
176
+ errorRanges: ReturnType<typeof collectSyntaxErrorRanges>;
177
+ };
178
+ }
179
+ | Exclude<TreeSitterResult<never>, { kind: "success" | "partial" }>
161
180
  > {
162
181
  const resolvedPath = resolveToolPath(this.cwd, filePath);
163
- const grammarId = detectGrammar(filePath);
182
+ const grammarId = detectGrammar(filePath, this.fileAssociations);
164
183
  if (!grammarId) {
165
184
  return {
166
185
  kind: "unsupported-language",
@@ -230,6 +249,8 @@ export class TreeSitterRuntime {
230
249
  source: parsed.source,
231
250
  resolvedPath: parsed.resolvedPath,
232
251
  grammarId: parsed.grammarId,
252
+ hasSyntaxErrors: hasSyntaxErrors(parsed.tree.rootNode),
253
+ errorRanges: collectSyntaxErrorRanges(parsed.tree.rootNode, parsed.source),
233
254
  },
234
255
  };
235
256
  } catch (err: unknown) {
@@ -281,7 +302,14 @@ export class TreeSitterRuntime {
281
302
 
282
303
  const { grammarId, tree, source } = parseResult.data;
283
304
  try {
284
- return await this[QUERY_PARSED_FILE]({ grammarId, tree, source, queryString, control });
305
+ return await this[QUERY_PARSED_FILE]({
306
+ file: filePath,
307
+ grammarId,
308
+ tree,
309
+ source,
310
+ queryString,
311
+ control,
312
+ });
285
313
  } finally {
286
314
  tree.delete();
287
315
  }
@@ -289,7 +317,7 @@ export class TreeSitterRuntime {
289
317
 
290
318
  /** Execute a query against one caller-owned parsed tree without parsing again. */
291
319
  async [QUERY_PARSED_FILE](input: ParsedQueryInput): Promise<TreeSitterResult<QueryCapture[]>> {
292
- const { grammarId, tree, source, queryString, control } = input;
320
+ const { file, grammarId, tree, source, queryString, control } = input;
293
321
  const validation = validateQueryString(queryString);
294
322
  if (validation) return validation;
295
323
 
@@ -344,7 +372,13 @@ export class TreeSitterRuntime {
344
372
  cache: execution.cache,
345
373
  finalPhase: "query-execution",
346
374
  });
347
- return { kind: "success", data: execution.data };
375
+ return withSyntaxCompleteness({
376
+ data: execution.data,
377
+ file,
378
+ root: tree.rootNode,
379
+ source,
380
+ grammarId,
381
+ });
348
382
  } catch (err: unknown) {
349
383
  if (isStructuralRequestInterruption(err, control)) {
350
384
  finishStructuralTiming(timer, {
@@ -1,6 +1,6 @@
1
- import { detectGrammar } from "../language.ts";
2
1
  import { supportsGrammarOperation } from "../operation-support.ts";
3
2
  import type { StructuralWorkerOperation } from "../session/structural-worker-protocol.ts";
3
+ import { withSyntaxCompleteness } from "../syntax-errors.ts";
4
4
  import { extractCallSites } from "../tool/call-sites.ts";
5
5
  import { lookupCalleesAt } from "../tool/callees.ts";
6
6
  import { extractExports } from "../tool/exports.ts";
@@ -68,16 +68,19 @@ export class StructuralWorkerService {
68
68
  file: string,
69
69
  control: StructuralRequestControl,
70
70
  ): Promise<TreeSitterResult<unknown>> {
71
- const unsupported = unsupportedOperation(file, "outline");
71
+ const unsupported = unsupportedOperation(this.runtime, file, "outline");
72
72
  if (unsupported) return unsupported;
73
73
  const result = await this.runtime.parseFile(file, control);
74
74
  if (result.kind !== "success") return result;
75
75
  try {
76
76
  throwIfStructuralRequestInterrupted(control);
77
- return {
78
- kind: "success",
79
- data: extractOutline(result.data.tree.rootNode, result.data.source),
80
- };
77
+ return withSyntaxCompleteness({
78
+ data: extractOutline(result.data.tree.rootNode, result.data.source, result.data.grammarId),
79
+ file,
80
+ root: result.data.tree.rootNode,
81
+ source: result.data.source,
82
+ grammarId: result.data.grammarId,
83
+ });
81
84
  } finally {
82
85
  result.data.tree.delete();
83
86
  }
@@ -87,7 +90,7 @@ export class StructuralWorkerService {
87
90
  file: string,
88
91
  control: StructuralRequestControl,
89
92
  ): Promise<TreeSitterResult<unknown>> {
90
- const unsupported = unsupportedOperation(file, "imports");
93
+ const unsupported = unsupportedOperation(this.runtime, file, "imports");
91
94
  return unsupported ?? extractImports(this.runtime, file, control);
92
95
  }
93
96
 
@@ -95,16 +98,17 @@ export class StructuralWorkerService {
95
98
  file: string,
96
99
  control: StructuralRequestControl,
97
100
  ): Promise<TreeSitterResult<unknown>> {
98
- const unsupported = unsupportedOperation(file, "exports");
101
+ const unsupported = unsupportedOperation(this.runtime, file, "exports");
99
102
  return unsupported ?? extractExports(this.runtime, file, control);
100
103
  }
101
104
  }
102
105
 
103
106
  function unsupportedOperation(
107
+ runtime: TreeSitterRuntime,
104
108
  file: string,
105
109
  operation: "outline" | "imports" | "exports",
106
110
  ): TreeSitterResult<never> | null {
107
- const grammarId = detectGrammar(file);
111
+ const grammarId = runtime.getGrammarForFile(file);
108
112
  if (!grammarId || supportsGrammarOperation(grammarId, operation)) return null;
109
113
  return {
110
114
  kind: "unsupported-language",
@@ -1,5 +1,9 @@
1
1
  import type { MessagePort } from "node:worker_threads";
2
- import { isCodeRequestDeadlineError } from "@mrclrchtr/supi-code-runtime/api";
2
+ import {
3
+ type CFamilyFileAssociationRule,
4
+ createCFamilyFileAssociationResolver,
5
+ isCodeRequestDeadlineError,
6
+ } from "@mrclrchtr/supi-code-runtime/api";
3
7
  import type { StructuralTimingEvent } from "../session/structural-timing.ts";
4
8
  import { assertStructuralProtocolMessageSize } from "../session/structural-worker-message-size.ts";
5
9
  import {
@@ -18,6 +22,7 @@ import { StructuralWorkerService } from "./service.ts";
18
22
  interface WorkerBootstrapData {
19
23
  readonly cwd: string;
20
24
  readonly generation: number;
25
+ readonly fileAssociations?: readonly CFamilyFileAssociationRule[];
21
26
  }
22
27
 
23
28
  interface ActiveWorkerRequest {
@@ -36,7 +41,15 @@ export async function runStructuralWorker(
36
41
  parentPort: MessagePort,
37
42
  workerData: WorkerBootstrapData,
38
43
  ): Promise<void> {
39
- const runtime = new TreeSitterRuntime(workerData.cwd, (event) => forwardObservation(event));
44
+ const associationResolver = createCFamilyFileAssociationResolver(
45
+ workerData.cwd,
46
+ workerData.fileAssociations ?? [],
47
+ );
48
+ const runtime = new TreeSitterRuntime(
49
+ workerData.cwd,
50
+ (event) => forwardObservation(event),
51
+ associationResolver,
52
+ );
40
53
  const service = new StructuralWorkerService(runtime);
41
54
  let active: ActiveWorkerRequest | null = null;
42
55