@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 +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +3 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/declaration-level.ts +34 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +64 -28
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/package.json +3 -3
- package/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
- package/resources/grammars/tsx/tree-sitter-tsx.wasm.json +5 -1
- package/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
- package/resources/grammars/typescript/tree-sitter-typescript.wasm.json +5 -1
- package/src/coordinates.ts +16 -2
- package/src/session/structural-worker-client.ts +3 -2
- package/src/syntax-errors.ts +2 -0
- package/src/tool/call-range.ts +94 -0
- package/src/tool/call-sites.ts +26 -6
- package/src/tool/callees.ts +6 -3
- package/src/tool/exports.ts +13 -4
- package/src/worker/runtime.ts +1 -9
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[]`,
|
|
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.
|
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
38
|
-
|
|
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
|
|
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
|
|
74
|
+
* a flat observation to a known level.
|
|
67
75
|
*/
|
|
68
76
|
export interface DocumentCodeSymbol extends CodeSymbol {
|
|
69
|
-
|
|
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
|
-
*
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
*
|
|
203
|
-
*
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-tree-sitter",
|
|
3
|
-
"version": "
|
|
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": "
|
|
40
|
-
"@mrclrchtr/supi-core": "
|
|
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",
|
|
Binary file
|
|
@@ -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": "
|
|
31
|
+
"sha256": "7c7552ea30a3a6785826e26c12c9dd33410ac577d9cb09a4fa5cf5701665abd8"
|
|
28
32
|
}
|
|
Binary file
|
|
@@ -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": "
|
|
31
|
+
"sha256": "d9e67877e2da665b26ab837bccd036e267cf1b5f521073e76352ec4af22ded4c"
|
|
28
32
|
}
|
package/src/coordinates.ts
CHANGED
|
@@ -100,7 +100,21 @@ export function validatePublicPositionBounds(
|
|
|
100
100
|
return null;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/**
|
|
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
|
-
|
|
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, {
|
package/src/syntax-errors.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/tool/call-sites.ts
CHANGED
|
@@ -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 {
|
|
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 = `${
|
|
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:
|
|
101
|
-
startCharacter:
|
|
102
|
-
endLine:
|
|
103
|
-
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
|
+
}
|
package/src/tool/callees.ts
CHANGED
|
@@ -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
|
|
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
|
|
410
|
+
range,
|
|
408
411
|
});
|
|
409
412
|
}
|
|
410
413
|
|
package/src/tool/exports.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
230
|
+
return node.childForFieldName("source") ?? undefined;
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
function extractReExport(
|
package/src/worker/runtime.ts
CHANGED
|
@@ -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) {
|