@zenithbuild/language 0.6.17 → 0.7.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 +14 -0
- package/language-configuration.json +30 -3
- package/out/extension.cjs +12 -10
- package/out/server.mjs +360 -26
- package/package.json +5 -1
- package/snippets/zenith.code-snippets +52 -0
- package/syntaxes/zenith.tmLanguage.json +60 -2
package/README.md
CHANGED
|
@@ -7,3 +7,17 @@ This package provides:
|
|
|
7
7
|
- the `text.html.zenith` TextMate grammar
|
|
8
8
|
- canonical Zenith snippets
|
|
9
9
|
- VS Code integration with `@zenithbuild/language-server`
|
|
10
|
+
- compiler-backed diagnostics plus the first `ZEN-DOM-*` quick fixes
|
|
11
|
+
- doc-backed hover and completion coverage for canonical Zenith APIs and documented `on:*` events
|
|
12
|
+
|
|
13
|
+
Build the packaged extension bundle:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun run build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Package a downloadable VS Code-compatible artifact:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun run package:vsix
|
|
23
|
+
```
|
|
@@ -14,13 +14,40 @@
|
|
|
14
14
|
{ "open": "[", "close": "]" },
|
|
15
15
|
{ "open": "(", "close": ")" },
|
|
16
16
|
{ "open": "\"", "close": "\"" },
|
|
17
|
-
{ "open": "'", "close": "'" }
|
|
17
|
+
{ "open": "'", "close": "'" },
|
|
18
|
+
{ "open": "`", "close": "`" },
|
|
19
|
+
{ "open": "<!--", "close": "-->", "notIn": ["string"] }
|
|
18
20
|
],
|
|
19
21
|
"surroundingPairs": [
|
|
20
22
|
["{", "}"],
|
|
21
23
|
["[", "]"],
|
|
22
24
|
["(", ")"],
|
|
23
25
|
["\"", "\""],
|
|
24
|
-
["'", "'"]
|
|
25
|
-
|
|
26
|
+
["'", "'"],
|
|
27
|
+
["`", "`"],
|
|
28
|
+
["<", ">"]
|
|
29
|
+
],
|
|
30
|
+
"colorizedBracketPairs": [
|
|
31
|
+
["{", "}"],
|
|
32
|
+
["[", "]"],
|
|
33
|
+
["(", ")"]
|
|
34
|
+
],
|
|
35
|
+
"wordPattern": "[A-Za-z_$][\\w$]*(?::[A-Za-z_][\\w-]*)*",
|
|
36
|
+
"indentationRules": {
|
|
37
|
+
"increaseIndentPattern": "<(?!area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr|/|!)[A-Za-z][\\w:-]*[^/>]*>(?!.*</)|\\{[^}]*$|\\([^)]*$",
|
|
38
|
+
"decreaseIndentPattern": "^\\s*(<\\/[A-Za-z][\\w:-]*>|\\}|\\))"
|
|
39
|
+
},
|
|
40
|
+
"onEnterRules": [
|
|
41
|
+
{
|
|
42
|
+
"beforeText": "<(?!area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)([A-Za-z][\\w:-]*)(?:[^/>]*?)>",
|
|
43
|
+
"afterText": "</\\1>",
|
|
44
|
+
"action": { "indent": "indentOutdent", "appendText": "\t" }
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"folding": {
|
|
48
|
+
"markers": {
|
|
49
|
+
"start": "^\\s*<!--\\s*#region\\b",
|
|
50
|
+
"end": "^\\s*<!--\\s*#endregion\\b"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
26
53
|
}
|
package/out/extension.cjs
CHANGED
|
@@ -56,14 +56,13 @@ async function startLanguageClient(context) {
|
|
|
56
56
|
const serverPath = getConfiguredServerPath(context);
|
|
57
57
|
const serverOptions = {
|
|
58
58
|
run: {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
options: { env: process.env }
|
|
59
|
+
module: serverPath,
|
|
60
|
+
transport: import_node.TransportKind.stdio
|
|
62
61
|
},
|
|
63
62
|
debug: {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
options: {
|
|
63
|
+
module: serverPath,
|
|
64
|
+
transport: import_node.TransportKind.stdio,
|
|
65
|
+
options: { execArgv: ["--inspect=6010"] }
|
|
67
66
|
}
|
|
68
67
|
};
|
|
69
68
|
const clientOptions = {
|
|
@@ -84,10 +83,7 @@ async function restartLanguageClient(context) {
|
|
|
84
83
|
}
|
|
85
84
|
await startLanguageClient(context);
|
|
86
85
|
}
|
|
87
|
-
function activate(context) {
|
|
88
|
-
void startLanguageClient(context).catch((error) => {
|
|
89
|
-
void vscode.window.showErrorMessage(`Zenith: failed to start language server: ${String(error)}`);
|
|
90
|
-
});
|
|
86
|
+
async function activate(context) {
|
|
91
87
|
context.subscriptions.push(
|
|
92
88
|
vscode.commands.registerCommand("zenith.restartServer", async () => {
|
|
93
89
|
await restartLanguageClient(context);
|
|
@@ -102,6 +98,12 @@ function activate(context) {
|
|
|
102
98
|
await restartLanguageClient(context);
|
|
103
99
|
})
|
|
104
100
|
);
|
|
101
|
+
try {
|
|
102
|
+
await startLanguageClient(context);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
void vscode.window.showErrorMessage(`Zenith: failed to start language server: ${String(error)}`);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
105
107
|
}
|
|
106
108
|
function deactivate() {
|
|
107
109
|
return client?.stop();
|
package/out/server.mjs
CHANGED
|
@@ -4,18 +4,175 @@ import {
|
|
|
4
4
|
DidChangeConfigurationNotification,
|
|
5
5
|
ProposedFeatures,
|
|
6
6
|
TextDocumentSyncKind
|
|
7
|
-
} from "vscode-languageserver/node";
|
|
7
|
+
} from "vscode-languageserver/node.js";
|
|
8
8
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
9
9
|
import { TextDocuments } from "vscode-languageserver";
|
|
10
10
|
|
|
11
|
+
// src/code-actions.ts
|
|
12
|
+
import {
|
|
13
|
+
CodeActionKind
|
|
14
|
+
} from "vscode-languageserver/node.js";
|
|
15
|
+
var DOM_QUERY_SUPPRESS = "// zen-allow:dom-query explain interop reason";
|
|
16
|
+
var DOM_LISTENER_TODO = "// TODO(zenith): replace addEventListener with zenOn(target, eventName, handler)";
|
|
17
|
+
var DOM_LISTENER_CLEANUP = "// TODO(zenith): register the disposer with ctx.cleanup(...) inside zenMount";
|
|
18
|
+
var DOM_WRAPPER_TODO = "// TODO(zenith): replace this guard with zenWindow() / zenDocument()";
|
|
19
|
+
function getCodeActions(text, uri, diagnostics) {
|
|
20
|
+
const actions = [];
|
|
21
|
+
for (const diagnostic of diagnostics) {
|
|
22
|
+
const code = typeof diagnostic.code === "string" ? diagnostic.code : "";
|
|
23
|
+
if (!code) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (code === "ZEN-DOM-QUERY") {
|
|
27
|
+
const action = createDomQuerySuppressAction(text, uri, diagnostic);
|
|
28
|
+
if (action) {
|
|
29
|
+
actions.push(action);
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (code === "ZEN-DOM-LISTENER") {
|
|
34
|
+
const action = createCommentInsertionAction(
|
|
35
|
+
text,
|
|
36
|
+
uri,
|
|
37
|
+
diagnostic,
|
|
38
|
+
[DOM_LISTENER_TODO, DOM_LISTENER_CLEANUP],
|
|
39
|
+
"Zenith: Add zenOn migration note"
|
|
40
|
+
);
|
|
41
|
+
if (action) {
|
|
42
|
+
actions.push(action);
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (code === "ZEN-DOM-WRAPPER") {
|
|
47
|
+
const replacementAction = createGlobalThisReplacementAction(text, uri, diagnostic);
|
|
48
|
+
if (replacementAction) {
|
|
49
|
+
actions.push(replacementAction);
|
|
50
|
+
}
|
|
51
|
+
const noteAction = createCommentInsertionAction(
|
|
52
|
+
text,
|
|
53
|
+
uri,
|
|
54
|
+
diagnostic,
|
|
55
|
+
[DOM_WRAPPER_TODO],
|
|
56
|
+
"Zenith: Add zenWindow/zenDocument migration note"
|
|
57
|
+
);
|
|
58
|
+
if (noteAction) {
|
|
59
|
+
actions.push(noteAction);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return actions;
|
|
64
|
+
}
|
|
65
|
+
function createDomQuerySuppressAction(text, uri, diagnostic) {
|
|
66
|
+
const lineIndex = diagnostic.range.start.line;
|
|
67
|
+
if (lineIndex > 0) {
|
|
68
|
+
const previousLine = getLine(text, lineIndex - 1);
|
|
69
|
+
if (previousLine?.includes("zen-allow:dom-query")) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return createCommentInsertionAction(
|
|
74
|
+
text,
|
|
75
|
+
uri,
|
|
76
|
+
diagnostic,
|
|
77
|
+
[DOM_QUERY_SUPPRESS],
|
|
78
|
+
"Zenith: Suppress DOM query with zen-allow comment"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function createGlobalThisReplacementAction(text, uri, diagnostic) {
|
|
82
|
+
const lineIndex = diagnostic.range.start.line;
|
|
83
|
+
const line = getLine(text, lineIndex);
|
|
84
|
+
if (!line) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (line.includes("globalThis.window")) {
|
|
88
|
+
return createReplacementAction(
|
|
89
|
+
uri,
|
|
90
|
+
diagnostic,
|
|
91
|
+
{
|
|
92
|
+
start: { line: lineIndex, character: line.indexOf("globalThis.window") },
|
|
93
|
+
end: { line: lineIndex, character: line.indexOf("globalThis.window") + "globalThis.window".length }
|
|
94
|
+
},
|
|
95
|
+
"zenWindow()",
|
|
96
|
+
"Zenith: Replace globalThis.window with zenWindow()"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (line.includes("globalThis.document")) {
|
|
100
|
+
return createReplacementAction(
|
|
101
|
+
uri,
|
|
102
|
+
diagnostic,
|
|
103
|
+
{
|
|
104
|
+
start: { line: lineIndex, character: line.indexOf("globalThis.document") },
|
|
105
|
+
end: { line: lineIndex, character: line.indexOf("globalThis.document") + "globalThis.document".length }
|
|
106
|
+
},
|
|
107
|
+
"zenDocument()",
|
|
108
|
+
"Zenith: Replace globalThis.document with zenDocument()"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function createCommentInsertionAction(text, uri, diagnostic, commentLines, title) {
|
|
114
|
+
const lineIndex = diagnostic.range.start.line;
|
|
115
|
+
const line = getLine(text, lineIndex);
|
|
116
|
+
if (line === void 0) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
120
|
+
const previousLine = lineIndex > 0 ? getLine(text, lineIndex - 1) : void 0;
|
|
121
|
+
if (previousLine && commentLines.every((commentLine) => previousLine.includes(commentLine.trim()))) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const newText = commentLines.map((commentLine) => `${indent}${commentLine}`).join("\n") + "\n";
|
|
125
|
+
return createReplacementAction(
|
|
126
|
+
uri,
|
|
127
|
+
diagnostic,
|
|
128
|
+
{
|
|
129
|
+
start: { line: lineIndex, character: 0 },
|
|
130
|
+
end: { line: lineIndex, character: 0 }
|
|
131
|
+
},
|
|
132
|
+
newText,
|
|
133
|
+
title
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
function createReplacementAction(uri, diagnostic, range, newText, title) {
|
|
137
|
+
const edits = [{ range, newText }];
|
|
138
|
+
const documentEdit = {
|
|
139
|
+
textDocument: {
|
|
140
|
+
uri,
|
|
141
|
+
version: null
|
|
142
|
+
},
|
|
143
|
+
edits
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
title,
|
|
147
|
+
kind: CodeActionKind.QuickFix,
|
|
148
|
+
diagnostics: [diagnostic],
|
|
149
|
+
edit: {
|
|
150
|
+
documentChanges: [documentEdit]
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function getLine(text, lineIndex) {
|
|
155
|
+
const lines = text.split("\n");
|
|
156
|
+
return lines[lineIndex];
|
|
157
|
+
}
|
|
158
|
+
|
|
11
159
|
// src/completions.ts
|
|
12
160
|
import {
|
|
13
161
|
CompletionItemKind,
|
|
14
|
-
InsertTextFormat
|
|
15
|
-
|
|
162
|
+
InsertTextFormat,
|
|
163
|
+
MarkupKind
|
|
164
|
+
} from "vscode-languageserver/node.js";
|
|
16
165
|
|
|
17
166
|
// src/docs.ts
|
|
18
167
|
var DOCS_BASE_URL = "https://github.com/zenithbuild/framework/blob/master/";
|
|
168
|
+
function createEventDoc(label, summary, example) {
|
|
169
|
+
return {
|
|
170
|
+
label,
|
|
171
|
+
summary,
|
|
172
|
+
example,
|
|
173
|
+
docPath: "docs/documentation/syntax/events.md"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
19
176
|
var SYMBOL_DOCS = {
|
|
20
177
|
zenEffect: {
|
|
21
178
|
label: "zenEffect",
|
|
@@ -76,7 +233,128 @@ var SYMBOL_DOCS = {
|
|
|
76
233
|
summary: "Deterministic multi-node collection helper that replaces selector scans.",
|
|
77
234
|
example: "const nodes = collectRefs(linkRefA, linkRefB, linkRefC)",
|
|
78
235
|
docPath: "docs/documentation/reactivity/dom-and-environment.md"
|
|
79
|
-
}
|
|
236
|
+
},
|
|
237
|
+
"on:esc": {
|
|
238
|
+
...createEventDoc(
|
|
239
|
+
"on:esc",
|
|
240
|
+
"Escape-filtered keydown alias that routes through Zenith\u2019s document-level esc dispatch.",
|
|
241
|
+
"<button on:esc={closeMenu}>Close</button>"
|
|
242
|
+
)
|
|
243
|
+
},
|
|
244
|
+
"on:hoverin": {
|
|
245
|
+
...createEventDoc(
|
|
246
|
+
"on:hoverin",
|
|
247
|
+
"Hover sugar alias for pointerenter when hover logic needs real event wiring.",
|
|
248
|
+
"<div on:hoverin={handleEnter}></div>"
|
|
249
|
+
)
|
|
250
|
+
},
|
|
251
|
+
"on:hoverout": {
|
|
252
|
+
...createEventDoc(
|
|
253
|
+
"on:hoverout",
|
|
254
|
+
"Hover sugar alias for pointerleave when hover logic needs real event wiring.",
|
|
255
|
+
"<div on:hoverout={handleLeave}></div>"
|
|
256
|
+
)
|
|
257
|
+
},
|
|
258
|
+
"on:click": createEventDoc(
|
|
259
|
+
"on:click",
|
|
260
|
+
"Canonical mouse click binding in Zenith\u2019s universal on:* event model.",
|
|
261
|
+
"<button on:click={handleClick}>Press</button>"
|
|
262
|
+
),
|
|
263
|
+
"on:doubleclick": createEventDoc(
|
|
264
|
+
"on:doubleclick",
|
|
265
|
+
"Canonical alias that normalizes doubleclick bindings to the emitted dblclick event.",
|
|
266
|
+
"<button on:doubleclick={handleDoubleClick}>Press</button>"
|
|
267
|
+
),
|
|
268
|
+
"on:dblclick": createEventDoc(
|
|
269
|
+
"on:dblclick",
|
|
270
|
+
"Canonical double-click binding using the normalized dblclick event name.",
|
|
271
|
+
"<button on:dblclick={handleDoubleClick}>Press</button>"
|
|
272
|
+
),
|
|
273
|
+
"on:keydown": createEventDoc(
|
|
274
|
+
"on:keydown",
|
|
275
|
+
"Canonical keyboard keydown binding in Zenith\u2019s universal on:* event model.",
|
|
276
|
+
"<div on:keydown={handleKeydown}></div>"
|
|
277
|
+
),
|
|
278
|
+
"on:keyup": createEventDoc(
|
|
279
|
+
"on:keyup",
|
|
280
|
+
"Canonical keyboard keyup binding in Zenith\u2019s universal on:* event model.",
|
|
281
|
+
"<div on:keyup={handleKeyup}></div>"
|
|
282
|
+
),
|
|
283
|
+
"on:submit": createEventDoc(
|
|
284
|
+
"on:submit",
|
|
285
|
+
"Canonical form submit binding in Zenith\u2019s universal on:* event model.",
|
|
286
|
+
"<form on:submit={handleSubmit}></form>"
|
|
287
|
+
),
|
|
288
|
+
"on:input": createEventDoc(
|
|
289
|
+
"on:input",
|
|
290
|
+
"Canonical input binding for immediate form-value updates.",
|
|
291
|
+
"<input on:input={handleInput} />"
|
|
292
|
+
),
|
|
293
|
+
"on:change": createEventDoc(
|
|
294
|
+
"on:change",
|
|
295
|
+
"Canonical change binding for committed form-value updates.",
|
|
296
|
+
"<input on:change={handleChange} />"
|
|
297
|
+
),
|
|
298
|
+
"on:focus": createEventDoc(
|
|
299
|
+
"on:focus",
|
|
300
|
+
"Canonical focus binding for element focus transitions.",
|
|
301
|
+
"<input on:focus={handleFocus} />"
|
|
302
|
+
),
|
|
303
|
+
"on:blur": createEventDoc(
|
|
304
|
+
"on:blur",
|
|
305
|
+
"Canonical blur binding for element focus exit transitions.",
|
|
306
|
+
"<input on:blur={handleBlur} />"
|
|
307
|
+
),
|
|
308
|
+
"on:pointerdown": createEventDoc(
|
|
309
|
+
"on:pointerdown",
|
|
310
|
+
"Canonical pointerdown binding from the recommended pointer event set.",
|
|
311
|
+
"<div on:pointerdown={handlePointerDown}></div>"
|
|
312
|
+
),
|
|
313
|
+
"on:pointerup": createEventDoc(
|
|
314
|
+
"on:pointerup",
|
|
315
|
+
"Canonical pointerup binding from the recommended pointer event set.",
|
|
316
|
+
"<div on:pointerup={handlePointerUp}></div>"
|
|
317
|
+
),
|
|
318
|
+
"on:pointermove": createEventDoc(
|
|
319
|
+
"on:pointermove",
|
|
320
|
+
"Canonical pointermove binding from the recommended pointer event set.",
|
|
321
|
+
"<svg on:pointermove={handlePointerMove}></svg>"
|
|
322
|
+
),
|
|
323
|
+
"on:pointerenter": createEventDoc(
|
|
324
|
+
"on:pointerenter",
|
|
325
|
+
"Direct pointerenter binding remains fully supported alongside on:hoverin.",
|
|
326
|
+
"<div on:pointerenter={handleEnter}></div>"
|
|
327
|
+
),
|
|
328
|
+
"on:pointerleave": createEventDoc(
|
|
329
|
+
"on:pointerleave",
|
|
330
|
+
"Direct pointerleave binding remains fully supported alongside on:hoverout.",
|
|
331
|
+
"<div on:pointerleave={handleLeave}></div>"
|
|
332
|
+
),
|
|
333
|
+
"on:dragstart": createEventDoc(
|
|
334
|
+
"on:dragstart",
|
|
335
|
+
"Canonical dragstart binding from Zenith\u2019s recommended drag event set.",
|
|
336
|
+
'<div draggable="true" on:dragstart={handleDragStart}></div>'
|
|
337
|
+
),
|
|
338
|
+
"on:dragover": createEventDoc(
|
|
339
|
+
"on:dragover",
|
|
340
|
+
"Canonical dragover binding from Zenith\u2019s recommended drag event set.",
|
|
341
|
+
"<div on:dragover={handleDragOver}></div>"
|
|
342
|
+
),
|
|
343
|
+
"on:drop": createEventDoc(
|
|
344
|
+
"on:drop",
|
|
345
|
+
"Canonical drop binding from Zenith\u2019s recommended drag event set.",
|
|
346
|
+
"<div on:drop={handleDrop}></div>"
|
|
347
|
+
),
|
|
348
|
+
"on:scroll": createEventDoc(
|
|
349
|
+
"on:scroll",
|
|
350
|
+
"Canonical scroll binding from Zenith\u2019s recommended event set.",
|
|
351
|
+
"<div on:scroll={handleScroll}></div>"
|
|
352
|
+
),
|
|
353
|
+
"on:contextmenu": createEventDoc(
|
|
354
|
+
"on:contextmenu",
|
|
355
|
+
"Canonical contextmenu binding from Zenith\u2019s recommended mouse event set.",
|
|
356
|
+
"<div on:contextmenu={handleContextMenu}></div>"
|
|
357
|
+
)
|
|
80
358
|
};
|
|
81
359
|
var canonicalScriptSymbols = [
|
|
82
360
|
"zenMount",
|
|
@@ -92,6 +370,7 @@ var canonicalScriptSymbols = [
|
|
|
92
370
|
];
|
|
93
371
|
var canonicalEventAttributes = [
|
|
94
372
|
"on:click",
|
|
373
|
+
"on:doubleclick",
|
|
95
374
|
"on:dblclick",
|
|
96
375
|
"on:keydown",
|
|
97
376
|
"on:keyup",
|
|
@@ -264,20 +543,34 @@ function positionAt(text, offset) {
|
|
|
264
543
|
// src/completions.ts
|
|
265
544
|
var blockedFrameworkTokens = ["react", "vue", "svelte", "rfce"];
|
|
266
545
|
function createScriptCompletion(label) {
|
|
546
|
+
const docs = getSymbolDoc(label);
|
|
267
547
|
return {
|
|
268
548
|
label,
|
|
269
549
|
kind: CompletionItemKind.Function,
|
|
270
|
-
detail: "Zenith canonical primitive",
|
|
550
|
+
detail: docs?.summary ?? "Zenith canonical primitive",
|
|
551
|
+
...docs ? {
|
|
552
|
+
documentation: {
|
|
553
|
+
kind: MarkupKind.Markdown,
|
|
554
|
+
value: `Docs: [${docs.docPath}](${getDocUrl(docs.docPath)})`
|
|
555
|
+
}
|
|
556
|
+
} : {},
|
|
271
557
|
insertText: label
|
|
272
558
|
};
|
|
273
559
|
}
|
|
274
560
|
function createEventCompletion(label) {
|
|
561
|
+
const docs = getSymbolDoc(label);
|
|
275
562
|
return {
|
|
276
563
|
label,
|
|
277
564
|
kind: CompletionItemKind.Property,
|
|
278
|
-
detail: "Canonical Zenith DOM event binding",
|
|
565
|
+
detail: docs?.summary ?? "Canonical Zenith DOM event binding",
|
|
566
|
+
...docs ? {
|
|
567
|
+
documentation: {
|
|
568
|
+
kind: MarkupKind.Markdown,
|
|
569
|
+
value: `Docs: [${docs.docPath}](${getDocUrl(docs.docPath)})`
|
|
570
|
+
}
|
|
571
|
+
} : {},
|
|
279
572
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
280
|
-
insertText: `${label}={
|
|
573
|
+
insertText: `${label}={\${1:handler}}`
|
|
281
574
|
};
|
|
282
575
|
}
|
|
283
576
|
function createPropHandlerCompletion() {
|
|
@@ -286,7 +579,7 @@ function createPropHandlerCompletion() {
|
|
|
286
579
|
kind: CompletionItemKind.Snippet,
|
|
287
580
|
detail: "Pass handler props through components, then bind them back to on:* in component markup.",
|
|
288
581
|
insertTextFormat: InsertTextFormat.Snippet,
|
|
289
|
-
insertText: "onClick={$1:handler}"
|
|
582
|
+
insertText: "onClick={${1:handler}}"
|
|
290
583
|
};
|
|
291
584
|
}
|
|
292
585
|
function getCompletionItems(text, position) {
|
|
@@ -309,7 +602,6 @@ function filterFrameworkNoise(items, typedPrefix) {
|
|
|
309
602
|
return items.filter((item) => {
|
|
310
603
|
const haystack = [
|
|
311
604
|
item.label,
|
|
312
|
-
item.detail ?? "",
|
|
313
605
|
typeof item.insertText === "string" ? item.insertText : ""
|
|
314
606
|
].join(" ").toLowerCase();
|
|
315
607
|
return blockedFrameworkTokens.every((token) => !haystack.includes(token));
|
|
@@ -319,46 +611,80 @@ function filterFrameworkNoise(items, typedPrefix) {
|
|
|
319
611
|
// src/diagnostics.ts
|
|
320
612
|
import { fileURLToPath } from "node:url";
|
|
321
613
|
import { compile } from "@zenithbuild/compiler";
|
|
322
|
-
import {
|
|
614
|
+
import {
|
|
615
|
+
DiagnosticSeverity,
|
|
616
|
+
DiagnosticTag
|
|
617
|
+
} from "vscode-languageserver/node.js";
|
|
323
618
|
async function collectDiagnosticsFromSource(source, filePath, strictDomLints) {
|
|
324
619
|
try {
|
|
325
620
|
const result = compile({ source, filePath });
|
|
326
621
|
if (result.schemaVersion !== 1) {
|
|
327
622
|
return [compilerContractDiagnostic(`Unsupported compiler schemaVersion: ${String(result.schemaVersion)}`)];
|
|
328
623
|
}
|
|
329
|
-
|
|
330
|
-
return warnings.map((warning) => ({
|
|
331
|
-
source: "zenith",
|
|
332
|
-
code: warning.code,
|
|
333
|
-
message: warning.message,
|
|
334
|
-
severity: resolveSeverity(warning, strictDomLints),
|
|
335
|
-
range: toRange(warning.range)
|
|
336
|
-
}));
|
|
624
|
+
return mapCompilerEnvelopeToDiagnostics(result, strictDomLints);
|
|
337
625
|
} catch (error) {
|
|
338
626
|
return [compilerContractDiagnostic(String(error))];
|
|
339
627
|
}
|
|
340
628
|
}
|
|
629
|
+
function mapCompilerEnvelopeToDiagnostics(result, strictDomLints) {
|
|
630
|
+
const diagnostics = Array.isArray(result.diagnostics) ? result.diagnostics : [];
|
|
631
|
+
if (diagnostics.length > 0) {
|
|
632
|
+
return diagnostics.map((diagnostic) => {
|
|
633
|
+
const tags = mapTags(diagnostic.tags);
|
|
634
|
+
return {
|
|
635
|
+
source: diagnostic.source ?? "zenith",
|
|
636
|
+
code: diagnostic.code,
|
|
637
|
+
message: diagnostic.message,
|
|
638
|
+
severity: resolveSeverity(diagnostic, strictDomLints),
|
|
639
|
+
range: toRange(diagnostic.range),
|
|
640
|
+
...tags ? { tags } : {}
|
|
641
|
+
};
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
|
|
645
|
+
return warnings.map((warning) => ({
|
|
646
|
+
source: "zenith",
|
|
647
|
+
code: warning.code,
|
|
648
|
+
message: warning.message,
|
|
649
|
+
severity: resolveSeverity(warning, strictDomLints),
|
|
650
|
+
range: toRange(warning.range)
|
|
651
|
+
}));
|
|
652
|
+
}
|
|
341
653
|
function resolveDocumentPath(uri) {
|
|
342
654
|
if (uri.startsWith("file://")) {
|
|
343
655
|
return fileURLToPath(uri);
|
|
344
656
|
}
|
|
345
657
|
return uri.replace(/^[a-z]+:\/\//i, "/virtual/");
|
|
346
658
|
}
|
|
347
|
-
function resolveSeverity(
|
|
348
|
-
if (strictDomLints &&
|
|
659
|
+
function resolveSeverity(diagnostic, strictDomLints) {
|
|
660
|
+
if (strictDomLints && diagnostic.code.startsWith("ZEN-DOM-")) {
|
|
349
661
|
return DiagnosticSeverity.Error;
|
|
350
662
|
}
|
|
351
|
-
if (
|
|
663
|
+
if (diagnostic.severity === "error") {
|
|
352
664
|
return DiagnosticSeverity.Error;
|
|
353
665
|
}
|
|
354
|
-
if (
|
|
666
|
+
if (diagnostic.severity === "hint") {
|
|
355
667
|
return DiagnosticSeverity.Hint;
|
|
356
668
|
}
|
|
357
|
-
if (
|
|
669
|
+
if (diagnostic.severity === "information") {
|
|
358
670
|
return DiagnosticSeverity.Information;
|
|
359
671
|
}
|
|
360
672
|
return DiagnosticSeverity.Warning;
|
|
361
673
|
}
|
|
674
|
+
function mapTags(tags) {
|
|
675
|
+
if (!Array.isArray(tags) || tags.length === 0) {
|
|
676
|
+
return void 0;
|
|
677
|
+
}
|
|
678
|
+
return tags.flatMap((tag) => {
|
|
679
|
+
if (tag === "deprecated") {
|
|
680
|
+
return [DiagnosticTag.Deprecated];
|
|
681
|
+
}
|
|
682
|
+
if (tag === "unnecessary") {
|
|
683
|
+
return [DiagnosticTag.Unnecessary];
|
|
684
|
+
}
|
|
685
|
+
return [];
|
|
686
|
+
});
|
|
687
|
+
}
|
|
362
688
|
function toRange(range) {
|
|
363
689
|
if (!range) {
|
|
364
690
|
return {
|
|
@@ -391,7 +717,7 @@ function compilerContractDiagnostic(message) {
|
|
|
391
717
|
}
|
|
392
718
|
|
|
393
719
|
// src/hover.ts
|
|
394
|
-
import { MarkupKind } from "vscode-languageserver/node";
|
|
720
|
+
import { MarkupKind as MarkupKind2 } from "vscode-languageserver/node.js";
|
|
395
721
|
function getHover(text, position) {
|
|
396
722
|
const symbol = getWord(text, position);
|
|
397
723
|
const docs = getSymbolDoc(symbol);
|
|
@@ -414,7 +740,7 @@ function getHover(text, position) {
|
|
|
414
740
|
return {
|
|
415
741
|
range,
|
|
416
742
|
contents: {
|
|
417
|
-
kind:
|
|
743
|
+
kind: MarkupKind2.Markdown,
|
|
418
744
|
value: markdown
|
|
419
745
|
}
|
|
420
746
|
};
|
|
@@ -546,7 +872,8 @@ function startLanguageServer() {
|
|
|
546
872
|
completionProvider: {
|
|
547
873
|
triggerCharacters: [":", "<", "{"]
|
|
548
874
|
},
|
|
549
|
-
hoverProvider: true
|
|
875
|
+
hoverProvider: true,
|
|
876
|
+
codeActionProvider: true
|
|
550
877
|
}
|
|
551
878
|
};
|
|
552
879
|
});
|
|
@@ -575,6 +902,13 @@ function startLanguageServer() {
|
|
|
575
902
|
}
|
|
576
903
|
return getHover(document.getText(), params.position);
|
|
577
904
|
});
|
|
905
|
+
connection.onCodeAction((params) => {
|
|
906
|
+
const document = documents.get(params.textDocument.uri);
|
|
907
|
+
if (!document) {
|
|
908
|
+
return [];
|
|
909
|
+
}
|
|
910
|
+
return getCodeActions(document.getText(), params.textDocument.uri, params.context.diagnostics);
|
|
911
|
+
});
|
|
578
912
|
documents.onDidOpen((event) => {
|
|
579
913
|
void scheduler.flush(event.document.uri);
|
|
580
914
|
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@zenithbuild/language",
|
|
3
3
|
"displayName": "Zenith Language Support",
|
|
4
4
|
"description": "Canonical Zenith syntax highlighting, snippets, and VS Code integration",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.7.0",
|
|
6
6
|
"publisher": "ZenithBuild",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"private": false,
|
|
@@ -48,11 +48,15 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "bun run build:server && rm -rf out && mkdir -p out && bun x esbuild src/extension.ts --bundle --outfile=out/extension.cjs --external:vscode --external:vscode-languageclient/node --format=cjs --platform=node && cp ../language-server/dist/server.mjs out/server.mjs",
|
|
50
50
|
"build:server": "bun run --cwd ../language-server build",
|
|
51
|
+
"package:vsix": "bun run build && node scripts/package-vsix.mjs",
|
|
51
52
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
52
53
|
"test": "node --test test/*.spec.mjs",
|
|
53
54
|
"prepublishOnly": "bun run build"
|
|
54
55
|
},
|
|
55
56
|
"dependencies": {
|
|
57
|
+
"@zenithbuild/compiler": "0.7.0",
|
|
58
|
+
"vscode-languageserver": "^9.0.1",
|
|
59
|
+
"vscode-languageserver-textdocument": "^1.0.11",
|
|
56
60
|
"vscode-languageclient": "^9.0.1"
|
|
57
61
|
},
|
|
58
62
|
"devDependencies": {
|
|
@@ -1,4 +1,56 @@
|
|
|
1
1
|
{
|
|
2
|
+
"zen-component": {
|
|
3
|
+
"prefix": "zen-component",
|
|
4
|
+
"body": [
|
|
5
|
+
"<script lang=\"ts\">",
|
|
6
|
+
"\t$0",
|
|
7
|
+
"</script>",
|
|
8
|
+
"",
|
|
9
|
+
"<${1:main} class=\"${2:root}\">",
|
|
10
|
+
"\t",
|
|
11
|
+
"</${1:main}>"
|
|
12
|
+
],
|
|
13
|
+
"description": "Zenith component skeleton with script and markup"
|
|
14
|
+
},
|
|
15
|
+
"zen-server-script": {
|
|
16
|
+
"prefix": "zen-server-script",
|
|
17
|
+
"body": [
|
|
18
|
+
"<script server lang=\"ts\">",
|
|
19
|
+
"\t$0",
|
|
20
|
+
"</script>"
|
|
21
|
+
],
|
|
22
|
+
"description": "Server-side script block"
|
|
23
|
+
},
|
|
24
|
+
"zen-setup-script": {
|
|
25
|
+
"prefix": "zen-setup-script",
|
|
26
|
+
"body": [
|
|
27
|
+
"<script setup=\"ts\">",
|
|
28
|
+
"import ${1:Component} from \"${2:@/components/${1:Component}.zen}\"",
|
|
29
|
+
"",
|
|
30
|
+
"$0",
|
|
31
|
+
"</script>"
|
|
32
|
+
],
|
|
33
|
+
"description": "Setup script block with component import"
|
|
34
|
+
},
|
|
35
|
+
"zen-guard-load": {
|
|
36
|
+
"prefix": "zen-guard-load",
|
|
37
|
+
"body": [
|
|
38
|
+
"<script server lang=\"ts\">",
|
|
39
|
+
"import { allow, redirect, deny, data } from '@zenithbuild/core/server'",
|
|
40
|
+
"",
|
|
41
|
+
"export const guard = async (ctx) => {",
|
|
42
|
+
"\t${1:// return allow(), redirect(url), or deny()}",
|
|
43
|
+
"\treturn allow()",
|
|
44
|
+
"}",
|
|
45
|
+
"",
|
|
46
|
+
"export const load = async (ctx) => {",
|
|
47
|
+
"\t$0",
|
|
48
|
+
"\treturn data({})",
|
|
49
|
+
"}",
|
|
50
|
+
"</script>"
|
|
51
|
+
],
|
|
52
|
+
"description": "Route guard and load pattern for protected routes"
|
|
53
|
+
},
|
|
2
54
|
"state-toggle": {
|
|
3
55
|
"prefix": "state-toggle",
|
|
4
56
|
"body": [
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
"scopeName": "text.html.zenith",
|
|
5
5
|
"patterns": [
|
|
6
6
|
{ "include": "#comment" },
|
|
7
|
+
{ "include": "#script-server-typescript" },
|
|
8
|
+
{ "include": "#script-setup-typescript" },
|
|
7
9
|
{ "include": "#script-typescript" },
|
|
10
|
+
{ "include": "#script-plain" },
|
|
8
11
|
{ "include": "#style-tag" },
|
|
9
12
|
{ "include": "#closing-tag" },
|
|
10
13
|
{ "include": "#opening-tag" },
|
|
@@ -16,6 +19,44 @@
|
|
|
16
19
|
"end": "-->",
|
|
17
20
|
"name": "comment.block.zenith"
|
|
18
21
|
},
|
|
22
|
+
"script-server-typescript": {
|
|
23
|
+
"begin": "(<)(script)\\b(?=[^>]*\\bserver\\b)(?=[^>]*\\blang\\s*=\\s*(['\"])(?:ts|typescript)\\3)([^>]*)(>)",
|
|
24
|
+
"beginCaptures": {
|
|
25
|
+
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
26
|
+
"2": { "name": "entity.name.tag.script.zenith" },
|
|
27
|
+
"4": { "name": "meta.tag.attributes.zenith" },
|
|
28
|
+
"5": { "name": "punctuation.definition.tag.end.zenith" }
|
|
29
|
+
},
|
|
30
|
+
"end": "(</)(script)(>)",
|
|
31
|
+
"endCaptures": {
|
|
32
|
+
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
33
|
+
"2": { "name": "entity.name.tag.script.zenith" },
|
|
34
|
+
"3": { "name": "punctuation.definition.tag.end.zenith" }
|
|
35
|
+
},
|
|
36
|
+
"contentName": "meta.embedded.block.typescript source.ts",
|
|
37
|
+
"patterns": [
|
|
38
|
+
{ "include": "source.ts" }
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"script-setup-typescript": {
|
|
42
|
+
"begin": "(<)(script)\\b(?=[^>]*\\bsetup\\s*=\\s*(['\"])(?:ts|typescript)\\3)([^>]*)(>)",
|
|
43
|
+
"beginCaptures": {
|
|
44
|
+
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
45
|
+
"2": { "name": "entity.name.tag.script.zenith" },
|
|
46
|
+
"4": { "name": "meta.tag.attributes.zenith" },
|
|
47
|
+
"5": { "name": "punctuation.definition.tag.end.zenith" }
|
|
48
|
+
},
|
|
49
|
+
"end": "(</)(script)(>)",
|
|
50
|
+
"endCaptures": {
|
|
51
|
+
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
52
|
+
"2": { "name": "entity.name.tag.script.zenith" },
|
|
53
|
+
"3": { "name": "punctuation.definition.tag.end.zenith" }
|
|
54
|
+
},
|
|
55
|
+
"contentName": "meta.embedded.block.typescript source.ts",
|
|
56
|
+
"patterns": [
|
|
57
|
+
{ "include": "source.ts" }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
19
60
|
"script-typescript": {
|
|
20
61
|
"begin": "(<)(script)\\b(?=[^>]*\\blang\\s*=\\s*(['\"])(?:ts|typescript)\\3)([^>]*)(>)",
|
|
21
62
|
"beginCaptures": {
|
|
@@ -35,6 +76,23 @@
|
|
|
35
76
|
{ "include": "source.ts" }
|
|
36
77
|
]
|
|
37
78
|
},
|
|
79
|
+
"script-plain": {
|
|
80
|
+
"begin": "(<)(script)\\b([^>]*)(>)",
|
|
81
|
+
"beginCaptures": {
|
|
82
|
+
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
83
|
+
"2": { "name": "entity.name.tag.script.zenith" },
|
|
84
|
+
"3": { "name": "meta.tag.attributes.zenith" },
|
|
85
|
+
"4": { "name": "punctuation.definition.tag.end.zenith" }
|
|
86
|
+
},
|
|
87
|
+
"end": "(</)(script)(>)",
|
|
88
|
+
"endCaptures": {
|
|
89
|
+
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
90
|
+
"2": { "name": "entity.name.tag.script.zenith" },
|
|
91
|
+
"3": { "name": "punctuation.definition.tag.end.zenith" }
|
|
92
|
+
},
|
|
93
|
+
"contentName": "meta.embedded.block.script.zenith",
|
|
94
|
+
"patterns": []
|
|
95
|
+
},
|
|
38
96
|
"style-tag": {
|
|
39
97
|
"begin": "(<)(style)\\b([^>]*)(>)",
|
|
40
98
|
"beginCaptures": {
|
|
@@ -55,7 +113,7 @@
|
|
|
55
113
|
]
|
|
56
114
|
},
|
|
57
115
|
"closing-tag": {
|
|
58
|
-
"match": "(</)([A-Za-z][\\w:-]*)(>)",
|
|
116
|
+
"match": "(</)([-A-Za-z][\\w:-]*)(>)",
|
|
59
117
|
"captures": {
|
|
60
118
|
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
61
119
|
"2": { "name": "entity.name.tag.zenith" },
|
|
@@ -63,7 +121,7 @@
|
|
|
63
121
|
}
|
|
64
122
|
},
|
|
65
123
|
"opening-tag": {
|
|
66
|
-
"begin": "(<)([A-Za-z][\\w:-]*)",
|
|
124
|
+
"begin": "(<)([-A-Za-z][\\w:-]*)",
|
|
67
125
|
"beginCaptures": {
|
|
68
126
|
"1": { "name": "punctuation.definition.tag.begin.zenith" },
|
|
69
127
|
"2": { "name": "entity.name.tag.zenith" }
|