@mrclrchtr/supi-tree-sitter 11.3.0 → 12.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.
package/README.md CHANGED
@@ -82,7 +82,7 @@ The `type` filter accepts `class`, `interface`, `type`, `enum`, `struct`, `union
82
82
  ### Extraction limits
83
83
 
84
84
  - TS/JS, Python, Java, Go, Rust, and C/C++ outlines include nested named declarations and members. They are not complete indexes of local variables, parameters, or anonymous expressions. Other languages retain their existing extraction depth. HTML outlines list only elements with non-empty `id` attributes. SQL outlines list supported `CREATE` declarations and first-level table/type members. LaTeX outlines list section headings and selected command, environment, label, acronym, glossary-entry, and color definitions.
85
- - TypeScript and TSX use [checked build-time grammar patches](scripts/generate-typescript-wasm.mjs) for type-only star re-exports, arrays of qualified import types such as `import("node:fs").Dirent[]`, and import types in generic calls such as `read<import("./api").Value>()` or `read<typeof import("./api")>()`. Runtime parsing retains the original source and coordinates; other grammar gaps still report recovered syntax.
85
+ - TypeScript and TSX use [checked build-time grammar patches](scripts/generate-typescript-wasm.mjs) for type-only star re-exports, arrays of qualified import types such as `import("node:fs").Dirent[]`, import types in generic calls such as `read<import("./api").Value>()` or `read<typeof import("./api")>()`, and import types with type arguments such as `import("typebox").Static<T>`. Runtime parsing retains the original source and coordinates; other grammar gaps still report recovered syntax.
86
86
  - JavaScript-family imports collect `import` statements, not CommonJS `require()` calls. Exports collect file-level `export` statements, including TypeScript `export =`; they do not collect `module.exports` assignments or nested namespace exports. An eligible `.cjs` file can therefore return no imports or exports.
87
87
  - Python imports retain `import`/`from` module targets, including relative prefixes. Java imports retain ordinary/static targets. Go imports retain single/grouped paths. Rust imports retain explicit `use` paths, `extern crate`, and external `mod` names; inline module bodies are not imports. C/C++ imports retain literal quoted and angle-bracket include targets. Grouped paths use explicit syntax only; no files or bindings are resolved. Conditional directives are written evidence, not proof of active dependencies. Dynamic loaders, computed targets, and C++20 module imports remain outside extraction support.
88
88
  - LaTeX imports include package, class, source, and bibliography directives. Bibliography directives can refer to `.bib` files, but the grammar does not parse those files.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "11.3.0",
3
+ "version": "12.0.0",
4
4
  "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,6 +14,7 @@ export type {
14
14
  StructuralProvider,
15
15
  StructuralResult,
16
16
  } from "./capability/types.ts";
17
+ export { childDeclarationLevel } from "./declaration-level.ts";
17
18
  export type {
18
19
  CFamilyExtension,
19
20
  CFamilyFileAssociationResolver,
@@ -50,7 +51,7 @@ export type {
50
51
  CodeResult,
51
52
  CodeSymbol,
52
53
  ConfidenceMode,
53
- DeclarationNesting,
54
+ DeclarationLevel,
54
55
  DisambiguationCandidate,
55
56
  DocumentCodeSymbol,
56
57
  DocumentEditPrecondition,
@@ -59,6 +60,7 @@ export type {
59
60
  ImportData,
60
61
  NodeAtData,
61
62
  OutlineData,
63
+ RefactorActionIdentity,
62
64
  RefactorOperation,
63
65
  RefactorRequest,
64
66
  RefactorResult,
@@ -0,0 +1,34 @@
1
+ import type { DeclarationLevel } from "./types.ts";
2
+
3
+ /** Declarations whose body holds statements, so nested declarations are local to it. */
4
+ const FUNCTION_LIKE_KINDS = new Set(["function", "method", "constructor"]);
5
+ /** Bindings whose value may be a function (locals) or an object literal (members). */
6
+ const VALUE_BINDING_KINDS = new Set(["variable", "constant", "property", "field"]);
7
+ /** Kinds that name an entry of an object, type, or enum rather than a statement-level binding. */
8
+ const MEMBER_ENTRY_KINDS = new Set(["property", "field", "method", "enummember", "key", "event"]);
9
+
10
+ /**
11
+ * Level of a declaration reported directly inside `parent`.
12
+ *
13
+ * Inside a local declaration everything stays local. A function, method, or
14
+ * constructor makes its nested declarations local. A variable, constant,
15
+ * property, or field holds either a function body (nested bindings are local)
16
+ * or an object literal (nested properties and methods are members). Every
17
+ * other container — class, interface, enum, struct, namespace, module, or a
18
+ * document section — makes its nested declarations members.
19
+ *
20
+ * Kinds compare case-insensitively, so LSP SymbolKind names and Tree-sitter
21
+ * outline kinds share one rule.
22
+ */
23
+ export function childDeclarationLevel(
24
+ parent: { readonly kind: string | null; readonly level: DeclarationLevel },
25
+ childKind: string | null,
26
+ ): Exclude<DeclarationLevel, "top-level" | "unknown"> {
27
+ if (parent.level === "local") return "local";
28
+ const parentKind = parent.kind?.toLowerCase() ?? "";
29
+ if (FUNCTION_LIKE_KINDS.has(parentKind)) return "local";
30
+ if (VALUE_BINDING_KINDS.has(parentKind)) {
31
+ return MEMBER_ENTRY_KINDS.has(childKind?.toLowerCase() ?? "") ? "member" : "local";
32
+ }
33
+ return "member";
34
+ }
@@ -34,8 +34,16 @@ export interface SymbolAnchor {
34
34
  character: number;
35
35
  }
36
36
 
37
- /** Provider-backed nesting evidence for a declaration reported within one document. */
38
- export type DeclarationNesting = "top-level" | "nested" | "unknown";
37
+ /**
38
+ * Where a declaration sits within its file, as reported by a provider.
39
+ *
40
+ * - `top-level`: directly in the file, not inside another declaration.
41
+ * - `member`: directly inside a class, interface, enum, struct, namespace,
42
+ * module, or object literal (methods, fields, properties, enum members).
43
+ * - `local`: inside the body of a function, method, or constructor.
44
+ * - `unknown`: the provider reported a flat symbol list without hierarchy.
45
+ */
46
+ export type DeclarationLevel = "top-level" | "member" | "local" | "unknown";
39
47
 
40
48
  /**
41
49
  * A discovered symbol / declaration.
@@ -54,7 +62,7 @@ export interface CodeSymbol {
54
62
  file: string;
55
63
  declarationAnchor: SymbolAnchor;
56
64
  nameAnchor?: SymbolAnchor;
57
- /** Named symbolic container when reported; absence does not prove top-level nesting. */
65
+ /** Named enclosing declaration when reported; absence does not prove a top-level declaration. */
58
66
  container?: string | null;
59
67
  }
60
68
 
@@ -63,10 +71,10 @@ export interface CodeSymbol {
63
71
  *
64
72
  * `unknown` means the provider returned a flat observation without an actual
65
73
  * hierarchy. A reported container name remains metadata and does not promote
66
- * a flat observation to known nesting.
74
+ * a flat observation to a known level.
67
75
  */
68
76
  export interface DocumentCodeSymbol extends CodeSymbol {
69
- nesting: DeclarationNesting;
77
+ level: DeclarationLevel;
70
78
  }
71
79
 
72
80
  // ── Result types ───────────────────────────────────────────────────────
@@ -185,31 +193,53 @@ export interface WorkspaceEdit {
185
193
  }
186
194
 
187
195
  /**
188
- * Supported refactor operation names for the current semantic planning path.
189
- */
190
- export type RefactorOperation =
191
- | "rename_symbol"
192
- | "extract_function"
193
- | "extract_variable"
194
- | "rename_file"
195
- | "move_file"
196
- | "update_imports"
197
- | "delete_dead_code";
198
-
199
- /**
200
- * Operation-aware refactor planning request.
196
+ * Operation-aware refactor planning request. Each operation carries only the
197
+ * scope it needs:
198
+ *
199
+ * - `rename_symbol`: one symbol position and the new name.
200
+ * - `code_action`: one file range and a CodeActionKind prefix under `refactor`.
201
+ * - `source_action`: one whole file and a CodeActionKind prefix under `source`.
201
202
  *
202
- * Consumers provide a target file/position plus operation-specific options.
203
- * File/resource operations remain requestable so providers can reject them
204
- * explicitly and honestly until shared workspace-edit resource ops exist.
203
+ * `kind` matches the requested kind and its dotted sub-kinds. `title`, when
204
+ * present, selects one action by its exact title.
205
205
  */
206
- export interface RefactorRequest {
207
- operation: RefactorOperation;
208
- file: string;
209
- position: CodePosition;
210
- range?: SourceRange;
211
- newName?: string;
212
- destination?: string;
206
+ export type RefactorRequest =
207
+ | {
208
+ operation: "rename_symbol";
209
+ file: string;
210
+ position: CodePosition;
211
+ newName: string;
212
+ }
213
+ | {
214
+ operation: "code_action";
215
+ file: string;
216
+ range: SourceRange;
217
+ kind: string;
218
+ title?: string;
219
+ }
220
+ | {
221
+ operation: "source_action";
222
+ file: string;
223
+ kind: string;
224
+ title?: string;
225
+ };
226
+
227
+ /** Supported refactor operation names. */
228
+ export type RefactorOperation = RefactorRequest["operation"];
229
+
230
+ /** The code action selected for a precise code- or source-action plan. */
231
+ export interface RefactorActionIdentity {
232
+ /** Server-reported CodeActionKind. */
233
+ kind: string;
234
+ /** Server-reported action title. */
235
+ title: string;
236
+ /**
237
+ * How the edits were obtained: `edit` from the action itself (after
238
+ * `codeAction/resolve` when needed), or `captured-command` from the single
239
+ * `workspace/applyEdit` the server pushed while running the action's command.
240
+ * Captured edits were refused, never applied.
241
+ */
242
+ provenance?: "edit" | "captured-command";
213
243
  }
214
244
 
215
245
  /**
@@ -220,6 +250,10 @@ export interface DisambiguationCandidate {
220
250
  file?: string;
221
251
  line?: number;
222
252
  character?: number;
253
+ /** Server-reported CodeActionKind, for code-action candidates. */
254
+ kind?: string;
255
+ /** Exact action title to pass back as `title`, for code-action candidates. */
256
+ title?: string;
223
257
  }
224
258
 
225
259
  /**
@@ -233,6 +267,8 @@ export type RefactorResult =
233
267
  | {
234
268
  kind: "precise";
235
269
  edits: WorkspaceEdit;
270
+ /** The selected code action, for code- and source-action plans. */
271
+ action?: RefactorActionIdentity;
236
272
  /** Provider roots authorized by the semantic route. Consumers canonicalize before storage. */
237
273
  authorizedMutationRoots: string[];
238
274
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "11.3.0",
3
+ "version": "12.0.0",
4
4
  "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "11.3.0",
3
+ "version": "12.0.0",
4
4
  "description": "Structural AST analysis for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "jiti": "^2.7.0",
38
38
  "web-tree-sitter": "^0.27.0",
39
- "@mrclrchtr/supi-code-runtime": "11.3.0",
40
- "@mrclrchtr/supi-core": "11.3.0"
39
+ "@mrclrchtr/supi-code-runtime": "12.0.0",
40
+ "@mrclrchtr/supi-core": "12.0.0"
41
41
  },
42
42
  "bundledDependencies": [
43
43
  "@mrclrchtr/supi-code-runtime",
@@ -22,7 +22,11 @@
22
22
  {
23
23
  "id": "import-type-generic-calls-v1",
24
24
  "file": "common/define-grammar.js"
25
+ },
26
+ {
27
+ "id": "import-type-member-type-arguments-v1",
28
+ "file": "common/define-grammar.js"
25
29
  }
26
30
  ],
27
- "sha256": "aaa2904a00000d8e058708845172bd3ce7836566c7e16e9ee594c43f4ba4f451"
31
+ "sha256": "7c7552ea30a3a6785826e26c12c9dd33410ac577d9cb09a4fa5cf5701665abd8"
28
32
  }
@@ -22,7 +22,11 @@
22
22
  {
23
23
  "id": "import-type-generic-calls-v1",
24
24
  "file": "common/define-grammar.js"
25
+ },
26
+ {
27
+ "id": "import-type-member-type-arguments-v1",
28
+ "file": "common/define-grammar.js"
25
29
  }
26
30
  ],
27
- "sha256": "86ba3af6f8214c90f32af1ea82214a0c1da87c9e2f5ea47c19577ba5b6c78bfb"
31
+ "sha256": "d9e67877e2da665b26ab837bccd036e267cf1b5f521073e76352ec4af22ded4c"
28
32
  }
@@ -100,7 +100,21 @@ export function validatePublicPositionBounds(
100
100
  return null;
101
101
  }
102
102
 
103
- /** Split source into logical lines without CRLF line-ending bytes. */
103
+ /**
104
+ * Split source into Tree-sitter rows, dropping the `\r` of CRLF endings.
105
+ * Tree-sitter rows break only on `\n`, so a lone `\r` is not a line break here.
106
+ * LSP treats a lone `\r` as a line break, so structural lines after one differ from LSP lines.
107
+ */
104
108
  export function splitSourceLines(source: string): string[] {
105
- return source.replace(/\r\n?/g, "\n").split("\n");
109
+ if (lastSplit?.source === source) return lastSplit.lines;
110
+ const lines = source.split(/\r?\n/);
111
+ lastSplit = { source, lines };
112
+ return lines;
106
113
  }
114
+
115
+ /**
116
+ * Single-entry cache: range conversion calls this once per node on the same
117
+ * source, so re-splitting each time made extraction quadratic in file size.
118
+ * Callers must not mutate the returned array.
119
+ */
120
+ let lastSplit: { source: string; lines: string[] } | undefined;
@@ -331,8 +331,9 @@ export class StructuralWorkerClient {
331
331
  try {
332
332
  throwIfCodeRequestInterrupted(active.control);
333
333
  } catch (error) {
334
+ // Keep the chunk handshake alive so the Worker can send its terminal message
335
+ // instead of waiting for the hard stop.
334
336
  this.#interrupt(active, error);
335
- return;
336
337
  }
337
338
  active.chunks.push(Uint8Array.from(message.payload));
338
339
  active.nextSequence += 1;
@@ -433,7 +434,7 @@ export class StructuralWorkerClient {
433
434
  return;
434
435
  }
435
436
  const worker = this.#worker;
436
- if (this.#active !== request || !worker) return;
437
+ if (this.#active !== request || !worker || request.hardStopTimer) return;
437
438
  Atomics.store(request.cancellationFlag, 0, 1);
438
439
  if (
439
440
  !this.#send(worker, {
@@ -4,7 +4,9 @@ import type { GrammarId, SourceRange } from "./types.ts";
4
4
 
5
5
  /** Return whether a parsed tree contains an explicit or recovered syntax error. */
6
6
  export function hasSyntaxErrors(root: SyntaxNodeLike): boolean {
7
+ // web-tree-sitter's hasError already covers ERROR and missing descendants.
7
8
  if (root.hasError === true) return true;
9
+ if (root.hasError === false) return false;
8
10
  if (root.type === "ERROR" || root.isMissing === true) return true;
9
11
  return (root.children ?? []).some(hasSyntaxErrors);
10
12
  }
@@ -0,0 +1,94 @@
1
+ // Callee ranges for grammars whose call queries capture the whole call node.
2
+
3
+ import { tsPointToPublic } from "../coordinates.ts";
4
+ import type { SyntaxNodeLike } from "../syntax-node.ts";
5
+ import type { GrammarId, SourceRange } from "../types.ts";
6
+
7
+ /**
8
+ * Return the callee range for a whole-call capture, so call-site ranges cover the
9
+ * callee expression in every grammar (`a.b` in `a.b(1)`), as JS/TS, Python, Go,
10
+ * and Rust queries capture directly. Returns `null` when the capture already is
11
+ * the callee or no narrower callee exists.
12
+ */
13
+ export function calleeRangeForCallNode(
14
+ node: SyntaxNodeLike,
15
+ grammarId: GrammarId,
16
+ source: string,
17
+ ): SourceRange | null {
18
+ const end = calleeEnd(node, grammarId);
19
+ if (!end) return null;
20
+ const start = tsPointToPublic(node.startPosition, source);
21
+ const publicEnd = tsPointToPublic(end, source);
22
+ return {
23
+ startLine: start.line,
24
+ startCharacter: start.character,
25
+ endLine: publicEnd.line,
26
+ endCharacter: publicEnd.character,
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Find the syntax node for one query capture by walking up from the leaf at its start.
32
+ * Returns `null` when no ancestor has the capture's type and range.
33
+ */
34
+ export function findCaptureNode(
35
+ root: SyntaxNodeLike & {
36
+ descendantForPosition(point: { row: number; column: number }): SyntaxNodeLike | null;
37
+ },
38
+ capture: { nodeType: string; range: SourceRange },
39
+ ): SyntaxNodeLike | null {
40
+ const { range } = capture;
41
+ let node = root.descendantForPosition({
42
+ row: range.startLine - 1,
43
+ column: range.startCharacter - 1,
44
+ });
45
+ while (node) {
46
+ if (
47
+ node.type === capture.nodeType &&
48
+ node.startPosition.row === range.startLine - 1 &&
49
+ node.startPosition.column === range.startCharacter - 1 &&
50
+ node.endPosition.row === range.endLine - 1 &&
51
+ node.endPosition.column === range.endCharacter - 1
52
+ ) {
53
+ return node;
54
+ }
55
+ node = node.parent;
56
+ }
57
+ return null;
58
+ }
59
+
60
+ function calleeEnd(
61
+ node: SyntaxNodeLike,
62
+ grammarId: GrammarId,
63
+ ): { row: number; column: number } | null {
64
+ switch (grammarId) {
65
+ case "java":
66
+ return node.type === "method_invocation"
67
+ ? (node.childForFieldName("name")?.endPosition ?? null)
68
+ : null;
69
+ case "kotlin":
70
+ return node.type === "call_expression" ? (kotlinCallee(node)?.endPosition ?? null) : null;
71
+ case "ruby":
72
+ return node.type === "call"
73
+ ? ((node.childForFieldName("method") ?? node.childForFieldName("receiver"))?.endPosition ??
74
+ null)
75
+ : null;
76
+ default:
77
+ return null;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Kotlin nests `f(x) { }` as an outer call (lambda-only suffix) wrapping `f(x)`.
83
+ * Treat both as one call so they share the callee `f`; `f(1)(2)` keeps `f(1)` as
84
+ * the outer callee because its suffix has value arguments.
85
+ */
86
+ function kotlinCallee(node: SyntaxNodeLike): SyntaxNodeLike | null {
87
+ const callee = node.children[0];
88
+ if (!callee) return null;
89
+ const suffix = node.children.find((child) => child.type === "call_suffix");
90
+ const lambdaOnly =
91
+ suffix !== undefined && !suffix.children.some((child) => child.type === "value_arguments");
92
+ if (callee.type === "call_expression" && lambdaOnly) return kotlinCallee(callee);
93
+ return callee;
94
+ }
@@ -1,10 +1,17 @@
1
1
  /** File-wide call-site extraction using per-grammar tree-sitter queries. */
2
2
 
3
3
  import type { CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
4
- import type { CallSiteMatch, GrammarId, TreeSitterResult } from "../types.ts";
4
+ import type {
5
+ CallSiteMatch,
6
+ GrammarId,
7
+ QueryCapture,
8
+ SourceRange,
9
+ TreeSitterResult,
10
+ } from "../types.ts";
5
11
  import { isTypeOnlyImportCapture } from "../worker/call-context.ts";
6
12
  import { queryParsedFile, type TreeSitterRuntime } from "../worker/runtime.ts";
7
13
  import { normalizeCallName } from "./call-name.ts";
14
+ import { calleeRangeForCallNode, findCaptureNode } from "./call-range.ts";
8
15
 
9
16
  // ── Per-grammar call-site queries ─────────────────────────────────────
10
17
 
@@ -90,17 +97,19 @@ export async function extractCallSites(
90
97
  const name = normalizeCallName(capture.text, grammarId, capture.nodeType);
91
98
  if (name.length === 0) continue;
92
99
 
100
+ const range = callSiteRange(tree.rootNode, capture, grammarId, source);
101
+
93
102
  // Collapse duplicate captures only when they identify the same source range.
94
- const key = `${capture.range.startLine}:${capture.range.startCharacter}:${capture.range.endLine}:${capture.range.endCharacter}`;
103
+ const key = `${range.startLine}:${range.startCharacter}:${range.endLine}:${range.endCharacter}`;
95
104
  if (seen.has(key)) continue;
96
105
  seen.add(key);
97
106
 
98
107
  matches.push({
99
108
  name,
100
- startLine: capture.range.startLine,
101
- startCharacter: capture.range.startCharacter,
102
- endLine: capture.range.endLine,
103
- endCharacter: capture.range.endCharacter,
109
+ startLine: range.startLine,
110
+ startCharacter: range.startCharacter,
111
+ endLine: range.endLine,
112
+ endCharacter: range.endCharacter,
104
113
  });
105
114
  }
106
115
 
@@ -111,3 +120,14 @@ export async function extractCallSites(
111
120
  tree.delete();
112
121
  }
113
122
  }
123
+
124
+ /** Narrow whole-call captures to their callee so ranges match the other grammars. */
125
+ function callSiteRange(
126
+ root: Parameters<typeof findCaptureNode>[0],
127
+ capture: QueryCapture,
128
+ grammarId: GrammarId,
129
+ source: string,
130
+ ): SourceRange {
131
+ const node = findCaptureNode(root, capture);
132
+ return (node && calleeRangeForCallNode(node, grammarId, source)) ?? capture.range;
133
+ }
@@ -9,6 +9,7 @@ import type { GrammarId, SourceRange, TreeSitterResult } from "../types.ts";
9
9
  import { isTypeOnlyImportCapture } from "../worker/call-context.ts";
10
10
  import { queryParsedFile, type TreeSitterRuntime } from "../worker/runtime.ts";
11
11
  import { normalizeCallName } from "./call-name.ts";
12
+ import { calleeRangeForCallNode } from "./call-range.ts";
12
13
  import { createCalleeDisplayName, createSourceLineStarts } from "./callee-display-name.ts";
13
14
  import { extractScopeName } from "./scope.ts";
14
15
 
@@ -387,11 +388,13 @@ function filterCalleeCaptures(
387
388
 
388
389
  if (isTypeOnlyImportCapture(enclosingNode, capture, grammarId)) continue;
389
390
  const name = normalizeCallName(capture.text, grammarId, capture.nodeType);
390
- const rangeKey = sourceRangeKey(capture.range);
391
+ const syntaxNode = syntaxNodes.get(captureNodeKey(capture));
392
+ const range =
393
+ (syntaxNode && calleeRangeForCallNode(syntaxNode, grammarId, source)) ?? capture.range;
394
+ const rangeKey = sourceRangeKey(range);
391
395
  if (name.length === 0 || seenRanges.has(rangeKey)) continue;
392
396
  seenRanges.add(rangeKey);
393
397
 
394
- const syntaxNode = syntaxNodes.get(captureNodeKey(capture));
395
398
  const displayName = syntaxNode
396
399
  ? createCalleeDisplayName({
397
400
  node: syntaxNode,
@@ -404,7 +407,7 @@ function filterCalleeCaptures(
404
407
  callees.push({
405
408
  name,
406
409
  ...(displayName === undefined ? {} : { displayName }),
407
- range: capture.range,
410
+ range,
408
411
  });
409
412
  }
410
413
 
@@ -91,9 +91,11 @@ const DECLARATION_EXPORT_NODE_TYPES = new Set([
91
91
  "type_alias_declaration",
92
92
  "enum_declaration",
93
93
  "lexical_declaration",
94
+ "variable_declaration",
94
95
  "ambient_declaration",
95
96
  "internal_module",
96
97
  "module",
98
+ "import_alias",
97
99
  ]);
98
100
 
99
101
  function extractDeclarationExport(
@@ -101,7 +103,7 @@ function extractDeclarationExport(
101
103
  source: string,
102
104
  exports: ExportRecord[],
103
105
  ): void {
104
- if (decl.type === "lexical_declaration") {
106
+ if (decl.type === "lexical_declaration" || decl.type === "variable_declaration") {
105
107
  extractLexicalDeclarationExports(decl, source, exports);
106
108
  return;
107
109
  }
@@ -111,7 +113,11 @@ function extractDeclarationExport(
111
113
  return;
112
114
  }
113
115
 
114
- const name = getDeclarationName(decl);
116
+ // `export import X = Y.Z` exports the alias `X`, which may name a value, type, or namespace.
117
+ const name =
118
+ decl.type === "import_alias"
119
+ ? (decl.children.find((child) => child.type === "identifier")?.text ?? null)
120
+ : getDeclarationName(decl);
115
121
  if (!name) return;
116
122
 
117
123
  exports.push({
@@ -143,7 +149,7 @@ function extractAmbientDeclarationExport(
143
149
  const nested = decl.children.find((child) => child.type !== "declare" && child.type !== ";");
144
150
  if (!nested) return;
145
151
 
146
- if (nested.type === "lexical_declaration") {
152
+ if (nested.type === "lexical_declaration" || nested.type === "variable_declaration") {
147
153
  extractLexicalDeclarationExports(nested, source, exports);
148
154
  return;
149
155
  }
@@ -177,6 +183,8 @@ function exportKindForDeclaration(type: string): string {
177
183
  case "internal_module":
178
184
  case "module":
179
185
  return "namespace";
186
+ case "import_alias":
187
+ return "alias";
180
188
  default:
181
189
  return type.replace(/_declaration$/, "");
182
190
  }
@@ -217,8 +225,9 @@ function extractDefaultExport(node: SyntaxNodeLike, source: string, exports: Exp
217
225
  });
218
226
  }
219
227
 
228
+ /** Return the `from "module"` specifier; a string value such as `export default "x"` is not one. */
220
229
  function findStringChild(node: SyntaxNodeLike): SyntaxNodeLike | undefined {
221
- return node.children.find((child) => child.type === "string");
230
+ return node.childForFieldName("source") ?? undefined;
222
231
  }
223
232
 
224
233
  function extractReExport(
@@ -13,11 +13,7 @@ import {
13
13
  type StructuralTimingEvent,
14
14
  startStructuralTiming,
15
15
  } from "../session/structural-timing.ts";
16
- import {
17
- collectSyntaxErrorRanges,
18
- hasSyntaxErrors,
19
- withSyntaxCompleteness,
20
- } from "../syntax-errors.ts";
16
+ import { withSyntaxCompleteness } from "../syntax-errors.ts";
21
17
  import type { GrammarId, QueryCapture, TreeSitterResult } from "../types.ts";
22
18
  import {
23
19
  ParsedFileReadError,
@@ -172,8 +168,6 @@ export class TreeSitterRuntime {
172
168
  source: string;
173
169
  resolvedPath: string;
174
170
  grammarId: GrammarId;
175
- hasSyntaxErrors: boolean;
176
- errorRanges: ReturnType<typeof collectSyntaxErrorRanges>;
177
171
  };
178
172
  }
179
173
  | Exclude<TreeSitterResult<never>, { kind: "success" | "partial" }>
@@ -249,8 +243,6 @@ export class TreeSitterRuntime {
249
243
  source: parsed.source,
250
244
  resolvedPath: parsed.resolvedPath,
251
245
  grammarId: parsed.grammarId,
252
- hasSyntaxErrors: hasSyntaxErrors(parsed.tree.rootNode),
253
- errorRanges: collectSyntaxErrorRanges(parsed.tree.rootNode, parsed.source),
254
246
  },
255
247
  };
256
248
  } catch (err: unknown) {