@mrclrchtr/supi-tree-sitter 4.0.0 → 4.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 +5 -5
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/package.json +3 -3
- package/src/language.ts +2 -1
- package/src/operation-support.ts +19 -0
- package/src/syntax-node.ts +1 -0
- package/src/tool/outline-c-family.ts +230 -0
- package/src/tool/outline-html-sql.ts +186 -0
- package/src/tool/outline-jvm.ts +311 -0
- package/src/tool/outline-polyglot.ts +218 -0
- package/src/tool/outline-scripting.ts +307 -0
- package/src/tool/outline.ts +9 -5
- package/src/types.ts +6 -2
package/README.md
CHANGED
|
@@ -40,17 +40,17 @@ Coordinates in the library APIs use **1-based** line and character columns. Char
|
|
|
40
40
|
- JavaScript / TypeScript (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts`)
|
|
41
41
|
- Python (`.py`, `.pyi`)
|
|
42
42
|
- Rust (`.rs`)
|
|
43
|
-
- Go (`.go
|
|
43
|
+
- Go (`.go`)
|
|
44
44
|
- C / C++ (`.c`, `.h`, `.cpp`, `.hpp`, `.cc`, `.cxx`, `.hxx`, `.c++`, `.h++`)
|
|
45
45
|
- Java (`.java`)
|
|
46
46
|
- Kotlin (`.kt`, `.kts`)
|
|
47
|
-
- Ruby (`.rb`)
|
|
48
|
-
- Bash / shell (`.sh`, `.bash`, `.zsh`)
|
|
47
|
+
- Ruby (`.rb`, `.gemspec`)
|
|
48
|
+
- Bash / shell (`.sh`, `.bash`, `.zsh`, `.ksh`)
|
|
49
49
|
- HTML (`.html`, `.htm`, `.xhtml`)
|
|
50
50
|
- R (`.r`)
|
|
51
51
|
- SQL (`.sql`)
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Outline collection supports every listed family. HTML outlines contain elements with non-empty `id` attributes; SQL outlines contain `CREATE` declarations and shallow table/type members. Import and export collection remains JavaScript/TypeScript-only, and call-site collection supports every listed family except HTML and SQL. Go module manifests and ERB templates are intentionally excluded because the Go and Ruby grammars do not parse those mixed or separate syntaxes; `supi-lsp` still handles them semantically. Consumers performing a bulk structural scan should use `getStructuralSearchSupportedExtensions(operation)` rather than treating parser support as operation support.
|
|
54
54
|
|
|
55
55
|
## Architecture
|
|
56
56
|
|
|
@@ -107,4 +107,4 @@ if (state.kind === "ready") {
|
|
|
107
107
|
- `src/operation-support.ts` — authoritative operation-specific extension support
|
|
108
108
|
- `src/session/service-registry.ts` — shared session-scoped structural service registry
|
|
109
109
|
- `src/provider/tree-sitter-provider.ts` — `StructuralProvider` adapter consumed by `@mrclrchtr/supi-code-intelligence`
|
|
110
|
-
- `src/tool/outline.ts`, `src/tool/imports.ts`, `src/tool/exports.ts`, `src/tool/node-at.ts`, `src/tool/callees.ts`, `src/tool/call-sites.ts` — structural analyses exposed through the library surface
|
|
110
|
+
- `src/tool/outline.ts`, `src/tool/outline-*.ts`, `src/tool/imports.ts`, `src/tool/exports.ts`, `src/tool/node-at.ts`, `src/tool/callees.ts`, `src/tool/call-sites.ts` — structural analyses exposed through the library surface
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-runtime",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"!__tests__"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@mrclrchtr/supi-core": "4.
|
|
32
|
+
"@mrclrchtr/supi-core": "4.2.0"
|
|
33
33
|
},
|
|
34
34
|
"bundledDependencies": [
|
|
35
35
|
"@mrclrchtr/supi-core"
|
|
@@ -34,6 +34,7 @@ export function buildActionMenu(field: ScopedFieldValue, scope: SettingsScope):
|
|
|
34
34
|
if (choices.length > 0) {
|
|
35
35
|
for (const choice of choices) menu.push({ value: `set:${choice}`, label: choice });
|
|
36
36
|
} else if (field.field.kind === "number") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
|
+
else if (field.field.kind === "string") menu.push({ value: "edit", label: "Edit value…" });
|
|
37
38
|
else if (field.field.kind === "stringList") menu.push({ value: "edit", label: "Edit values…" });
|
|
38
39
|
else if (field.field.kind === "modelPicker")
|
|
39
40
|
menu.push({ value: "edit", label: "Choose model…" });
|
|
@@ -86,6 +86,11 @@ export interface NumberField extends BaseField {
|
|
|
86
86
|
values?: string[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** One free-form string. */
|
|
90
|
+
export interface StringField extends BaseField {
|
|
91
|
+
kind: "string";
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
/** Comma-separated string list. */
|
|
90
95
|
export interface StringListField extends BaseField {
|
|
91
96
|
kind: "stringList";
|
|
@@ -164,6 +169,7 @@ export type SettingsField =
|
|
|
164
169
|
| BoolField
|
|
165
170
|
| EnumField
|
|
166
171
|
| NumberField
|
|
172
|
+
| StringField
|
|
167
173
|
| StringListField
|
|
168
174
|
| ModelPickerField
|
|
169
175
|
| CustomField;
|
|
@@ -248,6 +254,8 @@ export function formatValue(value: unknown, field: SettingsField): string {
|
|
|
248
254
|
return value ? "on" : "off";
|
|
249
255
|
case "number":
|
|
250
256
|
return String(value ?? "");
|
|
257
|
+
case "string":
|
|
258
|
+
return typeof value === "string" && value ? value : "none";
|
|
251
259
|
case "stringList": {
|
|
252
260
|
const arr = Array.isArray(value) ? value : [];
|
|
253
261
|
return arr.length > 0 ? arr.map(String).join(", ") : "none";
|
|
@@ -271,6 +279,7 @@ export function sourceBadge(displayValue: string, source: ValueSource): string {
|
|
|
271
279
|
|
|
272
280
|
/** Format the value used to prefill editors and compare concrete choices. */
|
|
273
281
|
export function formatEditValue(value: unknown, field: SettingsField): string {
|
|
282
|
+
if (field.kind === "string") return typeof value === "string" ? value : "";
|
|
274
283
|
if (field.kind === "stringList") {
|
|
275
284
|
const arr = Array.isArray(value) ? value : [];
|
|
276
285
|
return arr.map(String).join(", ");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-tree-sitter",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "SuPi Tree-sitter library — structural AST analysis for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"web-tree-sitter": "^0.26.8",
|
|
37
|
-
"@mrclrchtr/supi-code-runtime": "4.
|
|
38
|
-
"@mrclrchtr/supi-core": "4.
|
|
37
|
+
"@mrclrchtr/supi-code-runtime": "4.2.0",
|
|
38
|
+
"@mrclrchtr/supi-core": "4.2.0"
|
|
39
39
|
},
|
|
40
40
|
"bundledDependencies": [
|
|
41
41
|
"@mrclrchtr/supi-code-runtime",
|
package/src/language.ts
CHANGED
|
@@ -25,7 +25,6 @@ const EXTENSION_GRAMMAR: Record<string, GrammarId> = {
|
|
|
25
25
|
".pyi": "python",
|
|
26
26
|
".rs": "rust",
|
|
27
27
|
".go": "go",
|
|
28
|
-
".mod": "go",
|
|
29
28
|
".c": "c",
|
|
30
29
|
".h": "c",
|
|
31
30
|
".cpp": "cpp",
|
|
@@ -39,9 +38,11 @@ const EXTENSION_GRAMMAR: Record<string, GrammarId> = {
|
|
|
39
38
|
".kt": "kotlin",
|
|
40
39
|
".kts": "kotlin",
|
|
41
40
|
".rb": "ruby",
|
|
41
|
+
".gemspec": "ruby",
|
|
42
42
|
".sh": "bash",
|
|
43
43
|
".bash": "bash",
|
|
44
44
|
".zsh": "bash",
|
|
45
|
+
".ksh": "bash",
|
|
45
46
|
".html": "html",
|
|
46
47
|
".htm": "html",
|
|
47
48
|
".xhtml": "html",
|
package/src/operation-support.ts
CHANGED
|
@@ -2,6 +2,24 @@ import { getSupportedExtensionGrammarEntries, isJsTsGrammar } from "./language.t
|
|
|
2
2
|
import { supportsCallSitesGrammar } from "./tool/call-sites.ts";
|
|
3
3
|
import type { GrammarId, SupportedExtension } from "./types.ts";
|
|
4
4
|
|
|
5
|
+
const OUTLINE_GRAMMARS: ReadonlySet<GrammarId> = new Set([
|
|
6
|
+
"javascript",
|
|
7
|
+
"typescript",
|
|
8
|
+
"tsx",
|
|
9
|
+
"python",
|
|
10
|
+
"rust",
|
|
11
|
+
"go",
|
|
12
|
+
"c",
|
|
13
|
+
"cpp",
|
|
14
|
+
"java",
|
|
15
|
+
"kotlin",
|
|
16
|
+
"ruby",
|
|
17
|
+
"bash",
|
|
18
|
+
"html",
|
|
19
|
+
"r",
|
|
20
|
+
"sql",
|
|
21
|
+
]);
|
|
22
|
+
|
|
5
23
|
/** Structural collection operations whose language support defines an AST Scan universe. */
|
|
6
24
|
const STRUCTURAL_SEARCH_OPERATIONS = ["outline", "imports", "exports", "call-sites"] as const;
|
|
7
25
|
|
|
@@ -31,6 +49,7 @@ export function supportsGrammarOperation(
|
|
|
31
49
|
case "call-sites":
|
|
32
50
|
return supportsCallSitesGrammar(grammar);
|
|
33
51
|
case "outline":
|
|
52
|
+
return OUTLINE_GRAMMARS.has(grammar);
|
|
34
53
|
case "imports":
|
|
35
54
|
case "exports":
|
|
36
55
|
return isJsTsGrammar(grammar);
|
package/src/syntax-node.ts
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { nodeToRange } from "../coordinates.ts";
|
|
2
|
+
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
3
|
+
import type { OutlineItem } from "../types.ts";
|
|
4
|
+
|
|
5
|
+
/** Extract C and C++ declarations from their grammar-specific node shapes. */
|
|
6
|
+
export function extractCFamilyOutlineItems(
|
|
7
|
+
node: SyntaxNodeLike,
|
|
8
|
+
source: string,
|
|
9
|
+
): OutlineItem[] | undefined {
|
|
10
|
+
switch (node.type) {
|
|
11
|
+
case "class_specifier":
|
|
12
|
+
return one(typeContainer(node, "class", source));
|
|
13
|
+
case "struct_specifier":
|
|
14
|
+
return one(typeContainer(node, "struct", source));
|
|
15
|
+
case "union_specifier":
|
|
16
|
+
return one(typeContainer(node, "union", source));
|
|
17
|
+
case "enum_specifier":
|
|
18
|
+
return one(enumContainer(node, source));
|
|
19
|
+
case "type_definition":
|
|
20
|
+
return typeDefinitions(node, source);
|
|
21
|
+
case "alias_declaration":
|
|
22
|
+
return one(named(node, "type", source));
|
|
23
|
+
case "concept_definition":
|
|
24
|
+
return one(named(node, "concept", source));
|
|
25
|
+
case "namespace_definition": {
|
|
26
|
+
const item = namespaceItem(node, source);
|
|
27
|
+
return item ? [item] : collectDeclarations(node.childForFieldName("body"), source);
|
|
28
|
+
}
|
|
29
|
+
case "function_definition":
|
|
30
|
+
return one(declaratorItem(node, "function", source));
|
|
31
|
+
case "declaration":
|
|
32
|
+
return declarationItems(node, "variable", "function", source);
|
|
33
|
+
default:
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function one(item: OutlineItem | null): OutlineItem[] {
|
|
39
|
+
return item ? [item] : [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function named(node: SyntaxNodeLike, kind: string, source: string): OutlineItem | null {
|
|
43
|
+
const name = node.childForFieldName("name");
|
|
44
|
+
return name ? { name: name.text, kind, range: nodeToRange(node, source) } : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function typeContainer(
|
|
48
|
+
node: SyntaxNodeLike,
|
|
49
|
+
kind: "class" | "struct" | "union",
|
|
50
|
+
source: string,
|
|
51
|
+
): OutlineItem | null {
|
|
52
|
+
const name = node.childForFieldName("name");
|
|
53
|
+
if (!name) return null;
|
|
54
|
+
return {
|
|
55
|
+
name: name.text,
|
|
56
|
+
kind,
|
|
57
|
+
range: nodeToRange(node, source),
|
|
58
|
+
children: collectTypeMembers(node.childForFieldName("body"), source),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function enumContainer(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
63
|
+
const name = node.childForFieldName("name");
|
|
64
|
+
if (!name) return null;
|
|
65
|
+
return {
|
|
66
|
+
name: name.text,
|
|
67
|
+
kind: "enum",
|
|
68
|
+
range: nodeToRange(node, source),
|
|
69
|
+
children: enumMembers(node, source),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function enumMembers(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
74
|
+
const body = node.childForFieldName("body");
|
|
75
|
+
return (
|
|
76
|
+
body?.children.flatMap((child) =>
|
|
77
|
+
child.type === "enumerator" ? one(named(child, "enum-member", source)) : [],
|
|
78
|
+
) ?? []
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function typeDefinitions(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
83
|
+
const type = node.childForFieldName("type");
|
|
84
|
+
const kind = cTypeKind(type?.type);
|
|
85
|
+
const aliases = declaratorNodes(node).flatMap((declarator) => declaratorNames(declarator));
|
|
86
|
+
if (aliases.length === 0) return [];
|
|
87
|
+
const range = nodeToRange(node, source);
|
|
88
|
+
const children =
|
|
89
|
+
kind === "enum" && type
|
|
90
|
+
? enumMembers(type, source)
|
|
91
|
+
: type
|
|
92
|
+
? collectTypeMembers(type.childForFieldName("body"), source)
|
|
93
|
+
: undefined;
|
|
94
|
+
return aliases.map((name) => ({ name, kind, range, ...(children ? { children } : {}) }));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function cTypeKind(type: string | undefined): string {
|
|
98
|
+
switch (type) {
|
|
99
|
+
case "class_specifier":
|
|
100
|
+
return "class";
|
|
101
|
+
case "struct_specifier":
|
|
102
|
+
return "struct";
|
|
103
|
+
case "union_specifier":
|
|
104
|
+
return "union";
|
|
105
|
+
case "enum_specifier":
|
|
106
|
+
return "enum";
|
|
107
|
+
default:
|
|
108
|
+
return "type";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function namespaceItem(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
113
|
+
const name = node.childForFieldName("name");
|
|
114
|
+
if (!name) return null;
|
|
115
|
+
return {
|
|
116
|
+
name: name.text,
|
|
117
|
+
kind: "namespace",
|
|
118
|
+
range: nodeToRange(node, source),
|
|
119
|
+
children: collectDeclarations(node.childForFieldName("body"), source),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function collectDeclarations(
|
|
124
|
+
parent: SyntaxNodeLike | null | undefined,
|
|
125
|
+
source: string,
|
|
126
|
+
): OutlineItem[] {
|
|
127
|
+
if (!parent) return [];
|
|
128
|
+
return parent.children.flatMap((child) => {
|
|
129
|
+
const items = extractCFamilyOutlineItems(child, source);
|
|
130
|
+
return items ?? collectDeclarations(child, source);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function collectTypeMembers(
|
|
135
|
+
body: SyntaxNodeLike | null | undefined,
|
|
136
|
+
source: string,
|
|
137
|
+
): OutlineItem[] {
|
|
138
|
+
if (!body) return [];
|
|
139
|
+
return body.children.flatMap((child) => typeMemberItems(child, source));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function typeMemberItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
143
|
+
switch (node.type) {
|
|
144
|
+
case "field_declaration":
|
|
145
|
+
case "declaration":
|
|
146
|
+
return declarationItems(node, "field", "method", source);
|
|
147
|
+
case "function_definition":
|
|
148
|
+
return one(declaratorItem(node, "method", source));
|
|
149
|
+
case "class_specifier":
|
|
150
|
+
return one(typeContainer(node, "class", source));
|
|
151
|
+
case "struct_specifier":
|
|
152
|
+
return one(typeContainer(node, "struct", source));
|
|
153
|
+
case "union_specifier":
|
|
154
|
+
return one(typeContainer(node, "union", source));
|
|
155
|
+
case "enum_specifier":
|
|
156
|
+
return one(enumContainer(node, source));
|
|
157
|
+
case "type_definition":
|
|
158
|
+
return typeDefinitions(node, source);
|
|
159
|
+
case "alias_declaration":
|
|
160
|
+
return one(named(node, "type", source));
|
|
161
|
+
case "friend_declaration":
|
|
162
|
+
return [];
|
|
163
|
+
default:
|
|
164
|
+
return node.children.flatMap((child) => typeMemberItems(child, source));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function declarationItems(
|
|
169
|
+
node: SyntaxNodeLike,
|
|
170
|
+
valueKind: string,
|
|
171
|
+
functionKind: string,
|
|
172
|
+
source: string,
|
|
173
|
+
): OutlineItem[] {
|
|
174
|
+
const range = nodeToRange(node, source);
|
|
175
|
+
return declaratorNodes(node).flatMap((declarator) =>
|
|
176
|
+
declaratorNames(declarator).map((name) => ({
|
|
177
|
+
name,
|
|
178
|
+
kind: isFunctionDeclarator(declarator) ? functionKind : valueKind,
|
|
179
|
+
range,
|
|
180
|
+
})),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function declaratorItem(node: SyntaxNodeLike, kind: string, source: string): OutlineItem | null {
|
|
185
|
+
const declarator = node.childForFieldName("declarator");
|
|
186
|
+
const name = declarator ? declaratorNames(declarator)[0] : undefined;
|
|
187
|
+
return name ? { name, kind, range: nodeToRange(node, source) } : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function declaratorNodes(node: SyntaxNodeLike): SyntaxNodeLike[] {
|
|
191
|
+
const declarators = node.childrenForFieldName("declarator");
|
|
192
|
+
if (declarators.length > 0) return declarators;
|
|
193
|
+
const declarator = node.childForFieldName("declarator");
|
|
194
|
+
return declarator ? [declarator] : [];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function declaratorNames(node: SyntaxNodeLike): string[] {
|
|
198
|
+
if (DECLARATOR_NAME_TYPES.has(node.type)) return [node.text];
|
|
199
|
+
if (node.type === "structured_binding_declarator") {
|
|
200
|
+
return node.children.filter((child) => child.type === "identifier").map((child) => child.text);
|
|
201
|
+
}
|
|
202
|
+
const nested = node.childrenForFieldName("declarator");
|
|
203
|
+
if (nested.length > 0) return nested.flatMap(declaratorNames);
|
|
204
|
+
const declarator = node.childForFieldName("declarator");
|
|
205
|
+
return declarator ? declaratorNames(declarator) : [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isFunctionDeclarator(node: SyntaxNodeLike): boolean {
|
|
209
|
+
if (node.type === "function_declarator") {
|
|
210
|
+
const subject = node.childForFieldName("declarator");
|
|
211
|
+
return subject?.type !== "parenthesized_declarator" || !containsPointer(subject);
|
|
212
|
+
}
|
|
213
|
+
const declarator = node.childForFieldName("declarator");
|
|
214
|
+
return declarator ? isFunctionDeclarator(declarator) : false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function containsPointer(node: SyntaxNodeLike): boolean {
|
|
218
|
+
if (node.type === "pointer_declarator" || node.type === "reference_declarator") return true;
|
|
219
|
+
return node.children.some(containsPointer);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const DECLARATOR_NAME_TYPES = new Set([
|
|
223
|
+
"identifier",
|
|
224
|
+
"field_identifier",
|
|
225
|
+
"type_identifier",
|
|
226
|
+
"qualified_identifier",
|
|
227
|
+
"template_function",
|
|
228
|
+
"operator_name",
|
|
229
|
+
"destructor_name",
|
|
230
|
+
]);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { nodeToRange } from "../coordinates.ts";
|
|
2
|
+
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
3
|
+
import type { OutlineItem } from "../types.ts";
|
|
4
|
+
|
|
5
|
+
/** Extract HTML id declarations and SQL schema declarations. */
|
|
6
|
+
export function extractHtmlSqlOutlineItems(
|
|
7
|
+
node: SyntaxNodeLike,
|
|
8
|
+
source: string,
|
|
9
|
+
): OutlineItem[] | undefined {
|
|
10
|
+
if (HTML_ELEMENT_TYPES.has(node.type)) return htmlElementItems(node, source);
|
|
11
|
+
|
|
12
|
+
switch (node.type) {
|
|
13
|
+
case "create_schema":
|
|
14
|
+
return one(sqlItem(node, "schema", source, sqlSchemaChildren(node, source)));
|
|
15
|
+
case "create_type":
|
|
16
|
+
return one(sqlType(node, source));
|
|
17
|
+
case "create_table":
|
|
18
|
+
return one(sqlItem(node, "table", source, sqlColumns(node, source)));
|
|
19
|
+
case "create_view":
|
|
20
|
+
case "create_materialized_view":
|
|
21
|
+
return one(sqlItem(node, "view", source));
|
|
22
|
+
case "create_index":
|
|
23
|
+
return one(sqlItem(node, "index", source));
|
|
24
|
+
case "create_sequence":
|
|
25
|
+
return one(sqlItem(node, "sequence", source));
|
|
26
|
+
case "create_function":
|
|
27
|
+
return one(sqlItem(node, "function", source));
|
|
28
|
+
case "create_trigger":
|
|
29
|
+
return one(sqlItem(node, "trigger", source));
|
|
30
|
+
case "create_database":
|
|
31
|
+
return one(sqlItem(node, "database", source));
|
|
32
|
+
case "create_role":
|
|
33
|
+
return one(sqlItem(node, "role", source));
|
|
34
|
+
case "create_extension":
|
|
35
|
+
return one(sqlItem(node, "extension", source));
|
|
36
|
+
default:
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function htmlElementItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
42
|
+
const children = node.children.flatMap((child) =>
|
|
43
|
+
HTML_ELEMENT_TYPES.has(child.type) ? htmlElementItems(child, source) : [],
|
|
44
|
+
);
|
|
45
|
+
const tag = node.type === "self_closing_tag" ? node : directChild(node, "start_tag");
|
|
46
|
+
const id = tag && htmlId(tag);
|
|
47
|
+
if (!id) return children;
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
name: id,
|
|
51
|
+
kind: "element",
|
|
52
|
+
range: nodeToRange(node, source),
|
|
53
|
+
children,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function htmlId(tag: SyntaxNodeLike): string | null {
|
|
59
|
+
for (const attribute of tag.children) {
|
|
60
|
+
if (attribute.type !== "attribute") continue;
|
|
61
|
+
const name = directChild(attribute, "attribute_name");
|
|
62
|
+
if (name?.text.toLowerCase() !== "id") continue;
|
|
63
|
+
return firstDescendant(attribute, "attribute_value")?.text || null;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function firstDescendant(node: SyntaxNodeLike, type: string): SyntaxNodeLike | null {
|
|
69
|
+
for (const child of node.children) {
|
|
70
|
+
if (child.type === type) return child;
|
|
71
|
+
const nested = firstDescendant(child, type);
|
|
72
|
+
if (nested) return nested;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sqlType(node: SyntaxNodeLike, source: string): OutlineItem | null {
|
|
78
|
+
const enumElements = directChild(node, "enum_elements");
|
|
79
|
+
const children = enumElements
|
|
80
|
+
? enumElements.children
|
|
81
|
+
.filter((child) => child.type === "literal")
|
|
82
|
+
.map((child) => ({
|
|
83
|
+
name: stripSqlQuotes(child.text),
|
|
84
|
+
kind: "enum-member",
|
|
85
|
+
range: nodeToRange(child, source),
|
|
86
|
+
}))
|
|
87
|
+
: sqlColumns(node, source);
|
|
88
|
+
return sqlItem(node, enumElements ? "enum" : "type", source, children);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function sqlColumns(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
92
|
+
const definitions = node.children.flatMap((child) => {
|
|
93
|
+
if (child.type === "column_definitions") return [child];
|
|
94
|
+
if (child.type !== "table_partition") return [];
|
|
95
|
+
return child.children.filter((nested) => nested.type === "column_definitions");
|
|
96
|
+
});
|
|
97
|
+
return definitions.flatMap((group) =>
|
|
98
|
+
group.children.flatMap((child) => {
|
|
99
|
+
if (child.type === "column_definition") {
|
|
100
|
+
return [...one(sqlMember(child, "field", source)), ...sqlInlineConstraints(child, source)];
|
|
101
|
+
}
|
|
102
|
+
if (child.type === "constraints") {
|
|
103
|
+
return child.children.flatMap((constraint) =>
|
|
104
|
+
constraint.type === "constraint" ? one(sqlMember(constraint, "constraint", source)) : [],
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sqlInlineConstraints(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
113
|
+
const marker = node.children.findIndex((child) => child.type === "keyword_constraint");
|
|
114
|
+
if (marker < 0) return [];
|
|
115
|
+
const name = node.children.slice(marker + 1).find((child) => child.type === "literal");
|
|
116
|
+
return name
|
|
117
|
+
? [{ name: stripSqlQuotes(name.text), kind: "constraint", range: nodeToRange(name, source) }]
|
|
118
|
+
: [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function sqlSchemaChildren(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
122
|
+
return node.children.flatMap((child) => extractHtmlSqlOutlineItems(child, source) ?? []);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function sqlItem(
|
|
126
|
+
node: SyntaxNodeLike,
|
|
127
|
+
kind: string,
|
|
128
|
+
source: string,
|
|
129
|
+
children?: OutlineItem[],
|
|
130
|
+
): OutlineItem | null {
|
|
131
|
+
const name = sqlDeclarationName(node);
|
|
132
|
+
if (!name) return null;
|
|
133
|
+
return {
|
|
134
|
+
name,
|
|
135
|
+
kind,
|
|
136
|
+
range: nodeToRange(node, source),
|
|
137
|
+
...(children ? { children } : {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sqlMember(node: SyntaxNodeLike, kind: string, source: string): OutlineItem | null {
|
|
142
|
+
const name = node.childForFieldName("name") ?? directChild(node, "identifier");
|
|
143
|
+
return name ? { name: stripSqlQuotes(name.text), kind, range: nodeToRange(node, source) } : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function sqlDeclarationName(node: SyntaxNodeLike): string | null {
|
|
147
|
+
if (node.type === "create_index") {
|
|
148
|
+
const indexName = node.childForFieldName("column");
|
|
149
|
+
return indexName ? stripSqlQuotes(indexName.text) : null;
|
|
150
|
+
}
|
|
151
|
+
const reference = directChild(node, "object_reference");
|
|
152
|
+
if (reference) {
|
|
153
|
+
const name = reference.childForFieldName("name") ?? lastDirectChild(reference, "identifier");
|
|
154
|
+
if (name) return name.text;
|
|
155
|
+
}
|
|
156
|
+
return directChild(node, "identifier")?.text ?? null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function directChild(node: SyntaxNodeLike, type: string): SyntaxNodeLike | null {
|
|
160
|
+
return node.children.find((child) => child.type === type) ?? null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function lastDirectChild(node: SyntaxNodeLike, type: string): SyntaxNodeLike | null {
|
|
164
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) {
|
|
165
|
+
const child = node.children[index];
|
|
166
|
+
if (child?.type === type) return child;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function stripSqlQuotes(value: string): string {
|
|
172
|
+
return value.startsWith("'") && value.endsWith("'")
|
|
173
|
+
? value.slice(1, -1).replaceAll("''", "'")
|
|
174
|
+
: value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function one(item: OutlineItem | null): OutlineItem[] {
|
|
178
|
+
return item ? [item] : [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const HTML_ELEMENT_TYPES = new Set([
|
|
182
|
+
"element",
|
|
183
|
+
"script_element",
|
|
184
|
+
"self_closing_tag",
|
|
185
|
+
"style_element",
|
|
186
|
+
]);
|