@mrclrchtr/supi-tree-sitter 6.0.1 → 6.2.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 +6 -18
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +5 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/package.json +3 -5
- package/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
- package/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
- package/src/api.ts +1 -11
- package/src/coordinates.ts +19 -0
- package/src/language.ts +5 -23
- package/src/operation-support.ts +3 -21
- package/src/provider/tree-sitter-provider.ts +5 -9
- package/src/session/runtime-controller.ts +10 -20
- package/src/session/session.ts +7 -1
- package/src/session/structural-worker-client.ts +9 -5
- package/src/session/structural-worker-protocol.ts +134 -9
- package/src/tool/call-name.ts +96 -0
- package/src/tool/call-sites.ts +11 -12
- package/src/tool/callees.ts +39 -56
- package/src/tool/exports.ts +9 -7
- package/src/tool/js-binding-pattern.ts +26 -0
- package/src/tool/node-at.ts +2 -21
- package/src/tool/outline-html-sql.ts +144 -12
- package/src/tool/outline.ts +50 -37
- package/src/tool/scope.ts +138 -0
- package/src/types.ts +0 -7
- package/src/worker/parsed-file-store.ts +1 -14
- package/src/worker/runtime.ts +0 -10
- package/src/worker/service.ts +6 -8
- package/src/worker/worker-main.ts +34 -10
- package/scripts/generate-kotlin-wasm.mjs +0 -132
- package/scripts/generate-sql-wasm.mjs +0 -158
- package/scripts/vendor-wasm.mjs +0 -167
- package/src/index.ts +0 -42
- package/src/session/runtime-registration.ts +0 -37
- package/src/session/service-registry.ts +0 -30
- package/src/tool/structure.ts +0 -8
|
@@ -80,7 +80,7 @@ function sqlType(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
|
80
80
|
? enumElements.children
|
|
81
81
|
.filter((child) => child.type === "literal")
|
|
82
82
|
.map((child) => ({
|
|
83
|
-
name:
|
|
83
|
+
name: sqlStringLiteralValue(child.text),
|
|
84
84
|
kind: "enum-member",
|
|
85
85
|
range: nodeToRange(child, source),
|
|
86
86
|
}))
|
|
@@ -112,10 +112,12 @@ function sqlColumns(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
|
112
112
|
function sqlInlineConstraints(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
113
113
|
const marker = node.children.findIndex((child) => child.type === "keyword_constraint");
|
|
114
114
|
if (marker < 0) return [];
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
const nameNode = node.children
|
|
116
|
+
.slice(marker + 1)
|
|
117
|
+
.find((child) => child.type === "literal" || child.type === "identifier");
|
|
118
|
+
const name = readSqlNameAfterKeyword(node.text, "CONSTRAINT");
|
|
119
|
+
if (!name || !nameNode) return [];
|
|
120
|
+
return [{ name, kind: "constraint", range: nodeToRange(nameNode, source) }];
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
function sqlSchemaChildren(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
@@ -139,21 +141,29 @@ function sqlItem(
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
function sqlMember(node: SyntaxNodeLike, kind: string, source: string): OutlineItem | null {
|
|
144
|
+
const textName =
|
|
145
|
+
kind === "constraint"
|
|
146
|
+
? readSqlNameAfterKeyword(node.text, "CONSTRAINT")
|
|
147
|
+
: readSqlIdentifier(node.text);
|
|
142
148
|
const name = node.childForFieldName("name") ?? directChild(node, "identifier");
|
|
143
|
-
|
|
149
|
+
const value = textName ?? (name ? normalizeSqlIdentifierLike(name.text) : null);
|
|
150
|
+
return value ? { name: value, kind, range: nodeToRange(node, source) } : null;
|
|
144
151
|
}
|
|
145
152
|
|
|
146
153
|
function sqlDeclarationName(node: SyntaxNodeLike): string | null {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
const keyword = SQL_DECLARATION_KEYWORDS[node.type];
|
|
155
|
+
if (keyword) {
|
|
156
|
+
const name = readSqlNameAfterKeyword(node.text, keyword);
|
|
157
|
+
if (name) return name;
|
|
150
158
|
}
|
|
159
|
+
|
|
151
160
|
const reference = directChild(node, "object_reference");
|
|
152
161
|
if (reference) {
|
|
153
162
|
const name = reference.childForFieldName("name") ?? lastDirectChild(reference, "identifier");
|
|
154
|
-
if (name) return name.text;
|
|
163
|
+
if (name) return normalizeSqlIdentifierLike(name.text);
|
|
155
164
|
}
|
|
156
|
-
|
|
165
|
+
const name = directChild(node, "identifier");
|
|
166
|
+
return name ? normalizeSqlIdentifierLike(name.text) : null;
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
function directChild(node: SyntaxNodeLike, type: string): SyntaxNodeLike | null {
|
|
@@ -168,12 +178,119 @@ function lastDirectChild(node: SyntaxNodeLike, type: string): SyntaxNodeLike | n
|
|
|
168
178
|
return null;
|
|
169
179
|
}
|
|
170
180
|
|
|
171
|
-
function
|
|
181
|
+
function normalizeSqlIdentifierLike(value: string): string {
|
|
182
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
183
|
+
return value.slice(1, -1).replaceAll('""', '"');
|
|
184
|
+
}
|
|
185
|
+
if (value.startsWith("'") && value.endsWith("'")) {
|
|
186
|
+
return sqlStringLiteralValue(value);
|
|
187
|
+
}
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function sqlStringLiteralValue(value: string): string {
|
|
172
192
|
return value.startsWith("'") && value.endsWith("'")
|
|
173
193
|
? value.slice(1, -1).replaceAll("''", "'")
|
|
174
194
|
: value;
|
|
175
195
|
}
|
|
176
196
|
|
|
197
|
+
function readSqlNameAfterKeyword(text: string, keyword: string): string | null {
|
|
198
|
+
const marker = findSqlKeyword(text, keyword);
|
|
199
|
+
if (marker < 0) return null;
|
|
200
|
+
let position = skipSqlSpace(text, marker + keyword.length);
|
|
201
|
+
for (const optional of ["CONCURRENTLY", "IF", "NOT", "EXISTS"]) {
|
|
202
|
+
if (readSqlWord(text, position) === optional) {
|
|
203
|
+
position = skipSqlSpace(text, position + optional.length);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const name = readSqlQualifiedIdentifier(text, position);
|
|
207
|
+
return name ? normalizeSqlIdentifierLike(name) : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function readSqlIdentifier(text: string): string | null {
|
|
211
|
+
const name = readSqlQualifiedIdentifier(text, 0);
|
|
212
|
+
return name ? normalizeSqlIdentifierLike(name) : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function readSqlQualifiedIdentifier(text: string, start: number): string | null {
|
|
216
|
+
let position = skipSqlSpace(text, start);
|
|
217
|
+
let last: string | null = null;
|
|
218
|
+
while (position < text.length) {
|
|
219
|
+
const token = readSqlIdentifierToken(text, position);
|
|
220
|
+
if (!token) break;
|
|
221
|
+
last = token.value;
|
|
222
|
+
position = skipSqlSpace(text, token.end);
|
|
223
|
+
if (text[position] !== ".") break;
|
|
224
|
+
position = skipSqlSpace(text, position + 1);
|
|
225
|
+
}
|
|
226
|
+
return last;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function readSqlIdentifierToken(
|
|
230
|
+
text: string,
|
|
231
|
+
start: number,
|
|
232
|
+
): { value: string; end: number } | null {
|
|
233
|
+
if (text[start] === '"' || text[start] === "'") {
|
|
234
|
+
const quote = text[start];
|
|
235
|
+
let position = start + 1;
|
|
236
|
+
while (position < text.length) {
|
|
237
|
+
if (text[position] !== quote) {
|
|
238
|
+
position += 1;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (text[position + 1] === quote) {
|
|
242
|
+
position += 2;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
position += 1;
|
|
246
|
+
return { value: text.slice(start, position), end: position };
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let position = start;
|
|
252
|
+
while (position < text.length && !/[\s(;.,]/.test(text[position] ?? "")) position += 1;
|
|
253
|
+
return position > start ? { value: text.slice(start, position), end: position } : null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: quoted SQL names need a small lexical scan before keyword recovery
|
|
257
|
+
function findSqlKeyword(text: string, keyword: string): number {
|
|
258
|
+
const upper = text.toUpperCase();
|
|
259
|
+
let quote: '"' | "'" | null = null;
|
|
260
|
+
for (let position = 0; position < text.length; position += 1) {
|
|
261
|
+
const character = text[position];
|
|
262
|
+
if (quote !== null) {
|
|
263
|
+
if (character === quote && text[position + 1] === quote) {
|
|
264
|
+
position += 1;
|
|
265
|
+
} else if (character === quote) {
|
|
266
|
+
quote = null;
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (character === '"' || character === "'") {
|
|
271
|
+
quote = character;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (!upper.startsWith(keyword, position)) continue;
|
|
275
|
+
const before = upper[position - 1] ?? " ";
|
|
276
|
+
const after = upper[position + keyword.length] ?? " ";
|
|
277
|
+
if (!/[A-Z0-9_]/.test(before) && !/[A-Z0-9_]/.test(after)) return position;
|
|
278
|
+
}
|
|
279
|
+
return -1;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function readSqlWord(text: string, start: number): string | null {
|
|
283
|
+
const end = text.slice(start).search(/\s/);
|
|
284
|
+
const wordEnd = end < 0 ? text.length : start + end;
|
|
285
|
+
return wordEnd > start ? text.slice(start, wordEnd).toUpperCase() : null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function skipSqlSpace(text: string, start: number): number {
|
|
289
|
+
let position = start;
|
|
290
|
+
while (/\s/.test(text[position] ?? "")) position += 1;
|
|
291
|
+
return position;
|
|
292
|
+
}
|
|
293
|
+
|
|
177
294
|
function one(item: OutlineItem | null): OutlineItem[] {
|
|
178
295
|
return item ? [item] : [];
|
|
179
296
|
}
|
|
@@ -184,3 +301,18 @@ const HTML_ELEMENT_TYPES = new Set([
|
|
|
184
301
|
"self_closing_tag",
|
|
185
302
|
"style_element",
|
|
186
303
|
]);
|
|
304
|
+
|
|
305
|
+
const SQL_DECLARATION_KEYWORDS: Record<string, string> = {
|
|
306
|
+
create_schema: "SCHEMA",
|
|
307
|
+
create_type: "TYPE",
|
|
308
|
+
create_table: "TABLE",
|
|
309
|
+
create_view: "VIEW",
|
|
310
|
+
create_materialized_view: "VIEW",
|
|
311
|
+
create_index: "INDEX",
|
|
312
|
+
create_sequence: "SEQUENCE",
|
|
313
|
+
create_function: "FUNCTION",
|
|
314
|
+
create_trigger: "TRIGGER",
|
|
315
|
+
create_database: "DATABASE",
|
|
316
|
+
create_role: "ROLE",
|
|
317
|
+
create_extension: "EXTENSION",
|
|
318
|
+
};
|
package/src/tool/outline.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { nodeToRange } from "../coordinates.ts";
|
|
4
4
|
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
5
5
|
import type { OutlineItem } from "../types.ts";
|
|
6
|
+
import { extractBindingIdentifiers } from "./js-binding-pattern.ts";
|
|
6
7
|
import { extractPolyglotOutlineItems } from "./outline-polyglot.ts";
|
|
7
8
|
|
|
8
9
|
/** Node types that can be extracted directly as outline items. */
|
|
@@ -18,6 +19,7 @@ const OUTLINE_DECLARATION_NODE_TYPES = new Set([
|
|
|
18
19
|
"method_definition",
|
|
19
20
|
"public_field_definition",
|
|
20
21
|
"variable_declarator",
|
|
22
|
+
"lexical_declaration",
|
|
21
23
|
"ambient_declaration",
|
|
22
24
|
"internal_module",
|
|
23
25
|
"module",
|
|
@@ -39,20 +41,27 @@ function collectItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
|
39
41
|
continue;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
+
const extractedItems = extractItems(child, source);
|
|
45
|
+
if (extractedItems) items.push(...extractedItems);
|
|
44
46
|
else items.push(...collectItems(child, source));
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
return items;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
function extractItems(node: SyntaxNodeLike, source: string): OutlineItem[] | null {
|
|
53
|
+
if (node.type === "lexical_declaration") return extractLexicalDeclarationItems(node, source);
|
|
54
|
+
if (node.type === "ambient_declaration") return extractAmbientDeclarationItems(node, source);
|
|
55
|
+
if (node.type === "export_statement") return extractExportStatement(node, source);
|
|
56
|
+
|
|
57
|
+
const item = extractItem(node, source);
|
|
58
|
+
return item ? [item] : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
function extractItem(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
51
62
|
switch (node.type) {
|
|
52
|
-
case "export_statement":
|
|
53
|
-
return extractExportStatement(node, source);
|
|
54
63
|
case "lexical_declaration":
|
|
55
|
-
return
|
|
64
|
+
return extractLexicalDeclarationItems(node, source)[0] ?? null;
|
|
56
65
|
case "function_declaration":
|
|
57
66
|
case "generator_function_declaration":
|
|
58
67
|
case "function_signature":
|
|
@@ -72,7 +81,7 @@ function extractItem(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
|
72
81
|
case "public_field_definition":
|
|
73
82
|
return extractFieldDefinition(node, source);
|
|
74
83
|
case "variable_declarator":
|
|
75
|
-
return
|
|
84
|
+
return extractVariableDeclaratorItems(node, source)[0] ?? null;
|
|
76
85
|
case "ambient_declaration":
|
|
77
86
|
return extractAmbientDeclaration(node, source);
|
|
78
87
|
case "internal_module":
|
|
@@ -83,38 +92,36 @@ function extractItem(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
/** Extract
|
|
87
|
-
function extractExportStatement(node: SyntaxNodeLike, source: string): OutlineItem
|
|
95
|
+
/** Extract outline items from an export wrapper without exposing local syntax nodes. */
|
|
96
|
+
function extractExportStatement(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
88
97
|
const decl = node.children.find((child) => OUTLINE_DECLARATION_NODE_TYPES.has(child.type));
|
|
89
98
|
if (decl) {
|
|
99
|
+
if (decl.type === "lexical_declaration") return extractLexicalDeclarationItems(decl, source);
|
|
100
|
+
if (decl.type === "ambient_declaration") return extractAmbientDeclarationItems(decl, source);
|
|
90
101
|
const item = extractItem(decl, source);
|
|
91
|
-
if (item) return item;
|
|
102
|
+
if (item) return [item];
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
if (hasDefaultKeyword(node)) {
|
|
95
|
-
return {
|
|
96
|
-
name: "default",
|
|
97
|
-
kind: "export",
|
|
98
|
-
range: nodeToRange(node, source),
|
|
99
|
-
};
|
|
106
|
+
return [{ name: "default", kind: "export", range: nodeToRange(node, source) }];
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
const exportClause = node.children.find((child) => child.type === "export_clause");
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
return exportClause
|
|
111
|
+
? [
|
|
112
|
+
{
|
|
113
|
+
name: node.text.replace(/^export\s+/, "").substring(0, 60),
|
|
114
|
+
kind: "export",
|
|
115
|
+
range: nodeToRange(node, source),
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
: [];
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
function extractLexicalDeclarationItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
122
|
+
return node.children
|
|
123
|
+
.filter((child) => child.type === "variable_declarator")
|
|
124
|
+
.flatMap((declarator) => extractVariableDeclaratorItems(declarator, source));
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
function extractNamedDeclaration(
|
|
@@ -154,14 +161,11 @@ function extractEnumDeclaration(node: SyntaxNodeLike, source: string): OutlineIt
|
|
|
154
161
|
return { ...item, children: collectEnumMembers(node, source) };
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
function
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
kind: detectKind(node),
|
|
163
|
-
range: nodeToRange(node, source),
|
|
164
|
-
};
|
|
164
|
+
function extractVariableDeclaratorItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
165
|
+
const names = extractBindingIdentifiers(node.childForFieldName("name"));
|
|
166
|
+
const kind = detectKind(node);
|
|
167
|
+
const range = nodeToRange(node, source);
|
|
168
|
+
return names.map((name) => ({ name, kind, range }));
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
function extractFieldDefinition(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
@@ -174,9 +178,18 @@ function extractFieldDefinition(node: SyntaxNodeLike, source: string): OutlineIt
|
|
|
174
178
|
};
|
|
175
179
|
}
|
|
176
180
|
|
|
177
|
-
function
|
|
181
|
+
function extractAmbientDeclarationItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
178
182
|
const declaration = node.children.find((child) => OUTLINE_DECLARATION_NODE_TYPES.has(child.type));
|
|
179
|
-
|
|
183
|
+
if (!declaration) return [];
|
|
184
|
+
if (declaration.type === "lexical_declaration") {
|
|
185
|
+
return extractLexicalDeclarationItems(declaration, source);
|
|
186
|
+
}
|
|
187
|
+
const item = extractItem(declaration, source);
|
|
188
|
+
return item ? [item] : [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function extractAmbientDeclaration(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
192
|
+
return extractAmbientDeclarationItems(node, source)[0] ?? null;
|
|
180
193
|
}
|
|
181
194
|
|
|
182
195
|
function extractModuleDeclaration(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
2
|
+
import { extractBindingIdentifiers } from "./js-binding-pattern.ts";
|
|
3
|
+
|
|
4
|
+
/** Return the declared name for one executable syntax scope. */
|
|
5
|
+
export function extractScopeName(node: SyntaxNodeLike): string {
|
|
6
|
+
const declaredName = findDeclaredName(node);
|
|
7
|
+
if (declaredName) return declaredName;
|
|
8
|
+
|
|
9
|
+
if (node.type === "arrow_function" || node.type === "function_expression") {
|
|
10
|
+
const binding = findNearestJavaScriptBinding(node);
|
|
11
|
+
if (binding) return binding;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (node.type === "function_definition") {
|
|
15
|
+
const binding = findNearestRBinding(node);
|
|
16
|
+
if (binding) return binding;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return "anonymous";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function findDeclaredName(node: SyntaxNodeLike): string | null {
|
|
23
|
+
const name = node.childForFieldName("name");
|
|
24
|
+
if (name && isNameNode(name)) return name.text;
|
|
25
|
+
|
|
26
|
+
const declarator = node.childForFieldName("declarator");
|
|
27
|
+
return declarator ? findDeclaratorName(declarator) : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findDeclaratorName(node: SyntaxNodeLike): string | null {
|
|
31
|
+
const namedChild = node.childForFieldName("name");
|
|
32
|
+
if (namedChild && isNameNode(namedChild)) return namedChild.text;
|
|
33
|
+
if (isNameNode(node)) return node.text;
|
|
34
|
+
const nested = node.childForFieldName("declarator");
|
|
35
|
+
if (nested) {
|
|
36
|
+
const name = findDeclaratorName(nested);
|
|
37
|
+
if (name) return name;
|
|
38
|
+
}
|
|
39
|
+
for (const child of node.children) {
|
|
40
|
+
const name = findDeclaratorName(child);
|
|
41
|
+
if (name) return name;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findNearestJavaScriptBinding(node: SyntaxNodeLike): string | null {
|
|
47
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
48
|
+
const binding = bindingForJavaScriptAncestor(current, node);
|
|
49
|
+
if (binding) return binding;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: binding lookup covers the grammar's distinct assignment and object-pattern forms
|
|
55
|
+
function bindingForJavaScriptAncestor(node: SyntaxNodeLike, target: SyntaxNodeLike): string | null {
|
|
56
|
+
if (node.type === "variable_declarator") {
|
|
57
|
+
return isDirectValue(node, target)
|
|
58
|
+
? (extractBindingIdentifiers(node.childForFieldName("name"))[0] ?? null)
|
|
59
|
+
: null;
|
|
60
|
+
}
|
|
61
|
+
if (node.type === "pair_pattern") {
|
|
62
|
+
return isDirectValue(node, target)
|
|
63
|
+
? (extractBindingIdentifiers(node.childForFieldName("value"))[0] ??
|
|
64
|
+
node.childForFieldName("key")?.text ??
|
|
65
|
+
null)
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
if (node.type === "pair") {
|
|
69
|
+
return isDirectValue(node, target) ? (node.childForFieldName("key")?.text ?? null) : null;
|
|
70
|
+
}
|
|
71
|
+
if (node.type === "assignment_expression") {
|
|
72
|
+
const right = node.childForFieldName("right");
|
|
73
|
+
if (!sameNode(right, target)) return null;
|
|
74
|
+
const left = node.childForFieldName("left");
|
|
75
|
+
return left
|
|
76
|
+
? (extractBindingIdentifiers(left)[0] ??
|
|
77
|
+
(left.type === "member_expression" || left.type === "subscript_expression"
|
|
78
|
+
? left.text
|
|
79
|
+
: null))
|
|
80
|
+
: null;
|
|
81
|
+
}
|
|
82
|
+
if (node.type === "public_field_definition") {
|
|
83
|
+
return isDirectValue(node, target)
|
|
84
|
+
? ((node.childForFieldName("name") ?? node.childForFieldName("key"))?.text ?? null)
|
|
85
|
+
: null;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isDirectValue(node: SyntaxNodeLike, target: SyntaxNodeLike): boolean {
|
|
91
|
+
return sameNode(node.childForFieldName("value"), target);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findNearestRBinding(node: SyntaxNodeLike): string | null {
|
|
95
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
96
|
+
const binding = bindingForRAncestor(current, node);
|
|
97
|
+
if (binding) return binding;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function bindingForRAncestor(node: SyntaxNodeLike, target: SyntaxNodeLike): string | null {
|
|
103
|
+
if (node.type !== "binary_operator") return null;
|
|
104
|
+
const right = node.childForFieldName("rhs") ?? node.childForFieldName("right");
|
|
105
|
+
const left = node.childForFieldName("lhs") ?? node.childForFieldName("left");
|
|
106
|
+
return right && left && sameNode(right, target) && left.type === "identifier" ? left.text : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function sameNode(left: SyntaxNodeLike | null, right: SyntaxNodeLike): boolean {
|
|
110
|
+
if (!left) return false;
|
|
111
|
+
return (
|
|
112
|
+
left.type === right.type &&
|
|
113
|
+
left.startPosition.row === right.startPosition.row &&
|
|
114
|
+
left.startPosition.column === right.startPosition.column &&
|
|
115
|
+
left.endPosition.row === right.endPosition.row &&
|
|
116
|
+
left.endPosition.column === right.endPosition.column
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const NAME_NODE_TYPES = new Set([
|
|
121
|
+
"identifier",
|
|
122
|
+
"type_identifier",
|
|
123
|
+
"field_identifier",
|
|
124
|
+
"property_identifier",
|
|
125
|
+
"private_property_identifier",
|
|
126
|
+
"simple_identifier",
|
|
127
|
+
"word",
|
|
128
|
+
"constant",
|
|
129
|
+
"scope_resolution",
|
|
130
|
+
"qualified_identifier",
|
|
131
|
+
"template_function",
|
|
132
|
+
"operator_name",
|
|
133
|
+
"destructor_name",
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
function isNameNode(node: SyntaxNodeLike): boolean {
|
|
137
|
+
return NAME_NODE_TYPES.has(node.type);
|
|
138
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -128,13 +128,6 @@ export interface TreeSitterSession extends TreeSitterService {
|
|
|
128
128
|
dispose(): Promise<void>;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/** Session-scoped shared structural service published by the extension runtime. */
|
|
132
|
-
export type SessionTreeSitterService = TreeSitterService;
|
|
133
|
-
|
|
134
|
-
export type SessionTreeSitterServiceState =
|
|
135
|
-
| { kind: "ready"; service: SessionTreeSitterService }
|
|
136
|
-
| { kind: "unavailable"; reason: string };
|
|
137
|
-
|
|
138
131
|
/** Supported grammar identifiers. */
|
|
139
132
|
export type GrammarId =
|
|
140
133
|
| "javascript"
|
|
@@ -196,10 +196,8 @@ export class ParsedFileStore {
|
|
|
196
196
|
withQuery<T>(
|
|
197
197
|
grammarId: GrammarId,
|
|
198
198
|
queryText: string,
|
|
199
|
-
|
|
200
|
-
legacyExecute?: (query: Pick<Query, "matches">, cache: StructuralCacheObservation) => T,
|
|
199
|
+
execution: QueryExecution<T>,
|
|
201
200
|
): { readonly data: T; readonly cache: StructuralCacheObservation } {
|
|
202
|
-
const execution = normalizeQueryExecution(compileOrExecution, legacyExecute);
|
|
203
201
|
this.#assertActive();
|
|
204
202
|
throwIfStructuralRequestInterrupted(execution.control);
|
|
205
203
|
const key = queryKey(grammarId, queryText);
|
|
@@ -365,17 +363,6 @@ export class ParsedFileStore {
|
|
|
365
363
|
}
|
|
366
364
|
}
|
|
367
365
|
|
|
368
|
-
function normalizeQueryExecution<T>(
|
|
369
|
-
compileOrExecution: (() => Query) | QueryExecution<T>,
|
|
370
|
-
legacyExecute:
|
|
371
|
-
| ((query: Pick<Query, "matches">, cache: StructuralCacheObservation) => T)
|
|
372
|
-
| undefined,
|
|
373
|
-
): QueryExecution<T> {
|
|
374
|
-
if (typeof compileOrExecution !== "function") return compileOrExecution;
|
|
375
|
-
if (!legacyExecute) throw new Error("Query execution callback is required");
|
|
376
|
-
return { compile: compileOrExecution, execute: legacyExecute };
|
|
377
|
-
}
|
|
378
|
-
|
|
379
366
|
function fileReadMessage(error: unknown): string {
|
|
380
367
|
return error instanceof Error ? error.message : "File could not be read";
|
|
381
368
|
}
|
package/src/worker/runtime.ts
CHANGED
|
@@ -372,16 +372,6 @@ export class TreeSitterRuntime {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
-
/** Get the grammar ID for a file, or undefined if unsupported. */
|
|
376
|
-
getGrammarId(filePath: string): GrammarId | undefined {
|
|
377
|
-
return detectGrammar(filePath);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/** Resolve a file path from cwd. */
|
|
381
|
-
resolvePath(filePath: string): string {
|
|
382
|
-
return resolveToolPath(this.cwd, filePath);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
375
|
/** Dispose all held tree, query, and parser resources. */
|
|
386
376
|
dispose(): void {
|
|
387
377
|
if (this.disposed) return;
|
package/src/worker/service.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { detectGrammar } from "../language.ts";
|
|
2
2
|
import { supportsGrammarOperation } from "../operation-support.ts";
|
|
3
3
|
import type { StructuralWorkerOperation } from "../session/structural-worker-protocol.ts";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
lookupNodeAt,
|
|
11
|
-
} from "../tool/structure.ts";
|
|
4
|
+
import { extractCallSites } from "../tool/call-sites.ts";
|
|
5
|
+
import { lookupCalleesAt } from "../tool/callees.ts";
|
|
6
|
+
import { extractExports } from "../tool/exports.ts";
|
|
7
|
+
import { extractImports } from "../tool/imports.ts";
|
|
8
|
+
import { lookupNodeAt } from "../tool/node-at.ts";
|
|
9
|
+
import { collectOutline as extractOutline } from "../tool/outline.ts";
|
|
12
10
|
import type { TreeSitterResult } from "../types.ts";
|
|
13
11
|
import {
|
|
14
12
|
type StructuralRequestControl,
|
|
@@ -4,11 +4,11 @@ import type { StructuralTimingEvent } from "../session/structural-timing.ts";
|
|
|
4
4
|
import { assertStructuralProtocolMessageSize } from "../session/structural-worker-message-size.ts";
|
|
5
5
|
import {
|
|
6
6
|
encodeStructuralResult,
|
|
7
|
-
|
|
7
|
+
getStructuralWorkerMessageGeneration,
|
|
8
8
|
STRUCTURAL_WORKER_PROTOCOL_VERSION,
|
|
9
9
|
type StructuralWorkerChunkAckMessage,
|
|
10
10
|
type StructuralWorkerRequestMessage,
|
|
11
|
-
|
|
11
|
+
validateParentToStructuralWorkerMessage,
|
|
12
12
|
} from "../session/structural-worker-protocol.ts";
|
|
13
13
|
import type { TreeSitterResult } from "../types.ts";
|
|
14
14
|
import type { StructuralRequestControl } from "./request-control.ts";
|
|
@@ -56,21 +56,37 @@ export async function runStructuralWorker(
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: protocol dispatch keeps request, cancel, and acknowledgement validation together
|
|
59
|
-
parentPort.on("message", (
|
|
60
|
-
|
|
59
|
+
parentPort.on("message", (value: unknown) => {
|
|
60
|
+
const validation = validateParentToStructuralWorkerMessage(value);
|
|
61
|
+
if (validation.kind === "invalid") {
|
|
62
|
+
const generation = getStructuralWorkerMessageGeneration(value);
|
|
63
|
+
if (generation !== undefined && generation !== workerData.generation) return;
|
|
64
|
+
throw new Error(`Structural Worker protocol violation: ${validation.reason}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const message = validation.message;
|
|
61
68
|
if (message.generation !== workerData.generation) return;
|
|
62
69
|
if (message.kind === "cancel") {
|
|
63
|
-
if (active
|
|
70
|
+
if (!active) return;
|
|
71
|
+
assertActiveRequestOwner(active, message.requestId);
|
|
72
|
+
active.abortController.abort();
|
|
64
73
|
return;
|
|
65
74
|
}
|
|
66
75
|
if (message.kind === "chunk-ack") {
|
|
67
|
-
if (active
|
|
76
|
+
if (!active) return;
|
|
77
|
+
assertActiveRequestOwner(active, message.requestId);
|
|
78
|
+
if (!active.awaitingAck || message.sequence !== active.nextSequence - 1) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Structural Worker protocol violation: Invalid chunk acknowledgement state",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
sendNextChunk(message);
|
|
68
84
|
return;
|
|
69
85
|
}
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
void execute(
|
|
86
|
+
if (active) {
|
|
87
|
+
throw new Error("Structural Worker protocol violation: Structural Worker request is busy");
|
|
88
|
+
}
|
|
89
|
+
void execute(message);
|
|
74
90
|
});
|
|
75
91
|
|
|
76
92
|
try {
|
|
@@ -187,6 +203,14 @@ export async function runStructuralWorker(
|
|
|
187
203
|
active = null;
|
|
188
204
|
}
|
|
189
205
|
|
|
206
|
+
function assertActiveRequestOwner(request: ActiveWorkerRequest, requestId: string): void {
|
|
207
|
+
if (request.message.requestId !== requestId) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"Structural Worker protocol violation: Message does not own the active request",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
190
214
|
function updateInterruptionOutcome(request: ActiveWorkerRequest): void {
|
|
191
215
|
if (request.message.deadline !== undefined && Date.now() >= request.message.deadline) {
|
|
192
216
|
request.outcome = "timeout";
|