@monimesl/dot-cli 0.1.0-alpha.9 → 1.0.0-beta.1
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 +72 -59
- package/dist/cli.js +539 -341
- package/dist/cli.js.map +1 -1
- package/dist/{server-BfPAnsSf.js → server-DMnWDdJ6.js} +570 -478
- package/dist/server-DMnWDdJ6.js.map +1 -0
- package/dist/templates/showcase/dot.ts +53 -16
- package/dist/templates/showcase/pages/basics/bars/controller.ts +47 -0
- package/dist/templates/showcase/pages/basics/bars/state.ts +7 -0
- package/dist/templates/showcase/pages/basics/bars/view.dml +36 -0
- package/dist/templates/showcase/pages/{text → basics/bindings}/controller.ts +5 -0
- package/dist/templates/showcase/pages/basics/bindings/state.ts +14 -0
- package/dist/templates/showcase/pages/basics/bindings/view.dml +41 -0
- package/dist/templates/showcase/pages/basics/colors/controller.ts +112 -0
- package/dist/templates/showcase/pages/basics/colors/palette.ts +52 -0
- package/dist/templates/showcase/pages/basics/colors/state.ts +17 -0
- package/dist/templates/showcase/pages/basics/colors/view.dml +142 -0
- package/dist/templates/showcase/pages/basics/concert/controller.ts +16 -0
- package/dist/templates/showcase/pages/basics/concert/state.ts +7 -0
- package/dist/templates/showcase/pages/basics/concert/view.dml +20 -0
- package/dist/templates/showcase/pages/basics/control-flow/controller.ts +18 -0
- package/dist/templates/showcase/pages/basics/control-flow/state.ts +16 -0
- package/dist/templates/showcase/pages/basics/control-flow/view.dml +55 -0
- package/dist/templates/showcase/pages/basics/events/controller.ts +43 -0
- package/dist/templates/showcase/pages/basics/events/state.ts +11 -0
- package/dist/templates/showcase/pages/basics/events/view.dml +51 -0
- package/dist/templates/showcase/pages/basics/hooks/controller.ts +43 -0
- package/dist/templates/showcase/pages/basics/hooks/state.ts +10 -0
- package/dist/templates/showcase/pages/basics/hooks/view.dml +37 -0
- package/dist/templates/showcase/pages/basics/navigation/controller.ts +41 -0
- package/dist/templates/showcase/pages/basics/navigation/state.ts +11 -0
- package/dist/templates/showcase/pages/basics/navigation/view.dml +48 -0
- package/dist/templates/showcase/pages/basics/pick-genre/controller.ts +18 -0
- package/dist/templates/showcase/pages/basics/pick-genre/state.ts +3 -0
- package/dist/templates/showcase/pages/basics/pick-genre/view.dml +10 -0
- package/dist/templates/showcase/pages/basics/share/concerts.ts +31 -0
- package/dist/templates/showcase/pages/basics/share/controller.ts +12 -0
- package/dist/templates/showcase/pages/basics/share/state.ts +5 -0
- package/dist/templates/showcase/pages/basics/share/view.dml +21 -0
- package/dist/templates/showcase/pages/{state → basics/state}/controller.ts +1 -0
- package/dist/templates/showcase/pages/basics/state/view.dml +46 -0
- package/dist/templates/showcase/pages/{carousel → components/carousel}/controller.ts +2 -1
- package/dist/templates/showcase/pages/{carousel → components/carousel}/state.ts +5 -5
- package/dist/templates/showcase/pages/{carousel → components/carousel}/view.dml +7 -5
- package/dist/templates/showcase/pages/{feedback → components/feedback}/controller.ts +13 -2
- package/dist/templates/showcase/pages/components/feedback/state.ts +41 -0
- package/dist/templates/showcase/pages/{feedback → components/feedback}/view.dml +38 -9
- package/dist/templates/showcase/pages/{forms → components/forms}/controller.ts +23 -19
- package/dist/templates/showcase/pages/{forms → components/forms}/state.ts +6 -3
- package/dist/templates/showcase/pages/components/forms/view.dml +97 -0
- package/dist/templates/showcase/pages/components/icons/controller.ts +15 -0
- package/dist/templates/showcase/pages/components/icons/state.ts +75 -0
- package/dist/templates/showcase/pages/components/icons/view.dml +36 -0
- package/dist/templates/showcase/pages/{images → components/images}/controller.ts +4 -1
- package/dist/templates/showcase/pages/components/images/view.dml +83 -0
- package/dist/templates/showcase/pages/{layout → components/layout}/controller.ts +3 -2
- package/dist/templates/showcase/pages/components/layout/view.dml +143 -0
- package/dist/templates/showcase/pages/components/lists/controller.ts +18 -0
- package/dist/templates/showcase/pages/{lists → components/lists}/state.ts +43 -30
- package/dist/templates/showcase/pages/components/lists/view.dml +88 -0
- package/dist/templates/showcase/pages/{payments → components/money}/controller.ts +7 -8
- package/dist/templates/showcase/pages/components/money/view.dml +54 -0
- package/dist/templates/showcase/pages/components/reading/controller.ts +6 -0
- package/dist/templates/showcase/pages/components/reading/state.ts +23 -0
- package/dist/templates/showcase/pages/components/reading/view.dml +42 -0
- package/dist/templates/showcase/pages/{lists → components/sheets}/controller.ts +10 -11
- package/dist/templates/showcase/pages/components/sheets/state.ts +26 -0
- package/dist/templates/showcase/pages/components/sheets/view.dml +38 -0
- package/dist/templates/showcase/pages/components/styles/controller.ts +6 -0
- package/dist/templates/showcase/pages/components/styles/state.ts +3 -0
- package/dist/templates/showcase/pages/components/styles/view.dml +101 -0
- package/dist/templates/showcase/pages/components/tabs/controller.ts +41 -0
- package/dist/templates/showcase/pages/{filters → components/tabs}/state.ts +32 -23
- package/dist/templates/showcase/pages/components/tabs/view.dml +57 -0
- package/dist/templates/showcase/pages/components/text/controller.ts +10 -0
- package/dist/templates/showcase/pages/components/text/state.ts +3 -0
- package/dist/templates/showcase/pages/components/text/view.dml +95 -0
- package/dist/templates/showcase/pages/dash/account/controller.ts +32 -0
- package/dist/templates/showcase/pages/dash/account/state.ts +9 -0
- package/dist/templates/showcase/pages/dash/account/view.dml +33 -0
- package/dist/templates/showcase/pages/{cache → dash/cache}/controller.ts +9 -4
- package/dist/templates/showcase/pages/dash/cache/view.dml +48 -0
- package/dist/templates/showcase/pages/dash/calls/controller.ts +67 -0
- package/dist/templates/showcase/pages/dash/calls/state.ts +9 -0
- package/dist/templates/showcase/pages/dash/calls/view.dml +69 -0
- package/dist/templates/showcase/pages/dash/contacts/controller.ts +30 -0
- package/dist/templates/showcase/pages/dash/contacts/state.ts +6 -0
- package/dist/templates/showcase/pages/dash/contacts/view.dml +33 -0
- package/dist/templates/showcase/pages/dash/crypto/controller.ts +38 -0
- package/dist/templates/showcase/pages/dash/crypto/state.ts +10 -0
- package/dist/templates/showcase/pages/dash/crypto/view.dml +48 -0
- package/dist/templates/showcase/pages/{files → dash/files}/controller.ts +7 -2
- package/dist/templates/showcase/pages/{files → dash/files}/state.ts +4 -0
- package/dist/templates/showcase/pages/dash/files/view.dml +78 -0
- package/dist/templates/showcase/pages/dash/media/controller.ts +65 -0
- package/dist/templates/showcase/pages/dash/media/state.ts +12 -0
- package/dist/templates/showcase/pages/dash/media/view.dml +53 -0
- package/dist/templates/showcase/pages/{network → dash/network}/controller.ts +84 -20
- package/dist/templates/showcase/pages/{network → dash/network}/state.ts +14 -1
- package/dist/templates/showcase/pages/dash/network/view.dml +108 -0
- package/dist/templates/showcase/pages/dash/payments/cases.ts +445 -0
- package/dist/templates/showcase/pages/dash/payments/controller.ts +149 -0
- package/dist/templates/showcase/pages/dash/payments/state.ts +33 -0
- package/dist/templates/showcase/pages/dash/payments/view.dml +71 -0
- package/dist/templates/showcase/pages/{secrets → dash/secrets}/controller.ts +2 -1
- package/dist/templates/showcase/pages/dash/secrets/view.dml +26 -0
- package/dist/templates/showcase/pages/dash/system/controller.ts +89 -0
- package/dist/templates/showcase/pages/dash/system/state.ts +13 -0
- package/dist/templates/showcase/pages/dash/system/view.dml +96 -0
- package/dist/templates/showcase/pages/dash/ui/controller.ts +70 -0
- package/dist/templates/showcase/pages/dash/ui/state.ts +8 -0
- package/dist/templates/showcase/pages/dash/ui/view.dml +31 -0
- package/dist/templates/showcase/pages/index/controller.ts +1 -1
- package/dist/templates/showcase/pages/index/state.ts +175 -60
- package/dist/templates/showcase/pages/index/view.dml +6 -3
- package/package.json +6 -6
- package/dist/server-BfPAnsSf.js.map +0 -1
- package/dist/templates/showcase/pages/cache/view.dml +0 -36
- package/dist/templates/showcase/pages/device/controller.ts +0 -80
- package/dist/templates/showcase/pages/device/state.ts +0 -7
- package/dist/templates/showcase/pages/device/view.dml +0 -49
- package/dist/templates/showcase/pages/feedback/state.ts +0 -8
- package/dist/templates/showcase/pages/files/view.dml +0 -50
- package/dist/templates/showcase/pages/filters/controller.ts +0 -27
- package/dist/templates/showcase/pages/filters/view.dml +0 -44
- package/dist/templates/showcase/pages/forms/view.dml +0 -49
- package/dist/templates/showcase/pages/images/view.dml +0 -53
- package/dist/templates/showcase/pages/layout/view.dml +0 -103
- package/dist/templates/showcase/pages/lists/view.dml +0 -59
- package/dist/templates/showcase/pages/navigation/controller.ts +0 -65
- package/dist/templates/showcase/pages/navigation/state.ts +0 -17
- package/dist/templates/showcase/pages/navigation/view.dml +0 -34
- package/dist/templates/showcase/pages/network/view.dml +0 -67
- package/dist/templates/showcase/pages/payments/view.dml +0 -45
- package/dist/templates/showcase/pages/secrets/view.dml +0 -24
- package/dist/templates/showcase/pages/state/view.dml +0 -33
- package/dist/templates/showcase/pages/text/state.ts +0 -8
- package/dist/templates/showcase/pages/text/view.dml +0 -58
- /package/dist/templates/showcase/pages/{state → basics/state}/state.ts +0 -0
- /package/dist/templates/showcase/pages/{images → components/images}/state.ts +0 -0
- /package/dist/templates/showcase/pages/{layout → components/layout}/state.ts +0 -0
- /package/dist/templates/showcase/pages/{payments → components/money}/state.ts +0 -0
- /package/dist/templates/showcase/pages/{cache → dash/cache}/state.ts +0 -0
- /package/dist/templates/showcase/pages/{secrets → dash/secrets}/state.ts +0 -0
|
@@ -6,7 +6,7 @@ import { existsSync } from "node:fs";
|
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
7
|
import { parseDmlToIR } from "@monimesl/dot-dml-parser";
|
|
8
8
|
import { ScriptParseError, parseExpression, parseLogicModule, parseStateModule } from "@monimesl/dot-script-parser";
|
|
9
|
-
import { SourceError, builtinRegistry, eventName, eventProp, isEventProp } from "@monimesl/dot-core";
|
|
9
|
+
import { SourceError, builtinRegistry, eventName, eventProp, isEventProp, isWordsOr, otherThanWords } from "@monimesl/dot-core";
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
11
|
import ts from "typescript";
|
|
12
12
|
//#region \0rolldown/runtime.js
|
|
@@ -10144,161 +10144,57 @@ function getWellformedEdit(textEdit) {
|
|
|
10144
10144
|
return textEdit;
|
|
10145
10145
|
}
|
|
10146
10146
|
//#endregion
|
|
10147
|
-
//#region src/lsp/
|
|
10148
|
-
const
|
|
10149
|
-
|
|
10150
|
-
|
|
10147
|
+
//#region src/lsp/descriptions.ts
|
|
10148
|
+
const CONTROL_FLOW_DOCS = {
|
|
10149
|
+
If: "Shows its children when `condition` is true.",
|
|
10150
|
+
ElseIf: "Goes after an `<If>` or `<ElseIf>`. Shows its children when every condition before it is false and `condition` is true.",
|
|
10151
|
+
Else: "Goes after an `<If>` or `<ElseIf>`. Shows its children when every condition before it is false.",
|
|
10152
|
+
For: "Repeats its children for each item in `each`. Inside, the item is named `item` and its position `index`. `key` tells items apart: a property of each item, `*this` for the item itself, or `*index` to match items by position."
|
|
10151
10153
|
};
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
|
|
10166
|
-
const owner = shapeOf(expr.object, scope);
|
|
10167
|
-
if (!owner) return;
|
|
10168
|
-
const { property } = expr;
|
|
10169
|
-
if (property.type === "Literal" && typeof property.value === "string") return membersOf(owner)[property.value];
|
|
10170
|
-
return owner.kind === "list" ? owner.item : void 0;
|
|
10171
|
-
}
|
|
10172
|
-
default: return;
|
|
10173
|
-
}
|
|
10174
|
-
}
|
|
10175
|
-
function membersOf(shape) {
|
|
10176
|
-
switch (shape.kind) {
|
|
10177
|
-
case "object": return shape.properties;
|
|
10178
|
-
case "list": return { length: NUMBER };
|
|
10179
|
-
case "value": return {};
|
|
10154
|
+
const CONTROL_FLOW_ATTRIBUTE_DOCS = {
|
|
10155
|
+
condition: "A single `{{ }}` binding",
|
|
10156
|
+
each: "The list to repeat, as a single `{{ }}` binding",
|
|
10157
|
+
item: "The name for each item (default `item`)",
|
|
10158
|
+
index: "The name for each position (default `index`)",
|
|
10159
|
+
key: "A property of each item, `*this` or `*index`"
|
|
10160
|
+
};
|
|
10161
|
+
function describeComponent(component) {
|
|
10162
|
+
const lines = [`**<${component.name}>** · ${describeChildren(component)}`];
|
|
10163
|
+
const props = Object.entries(component.props);
|
|
10164
|
+
if (props.length > 0) lines.push("", ...props.map(([name, type]) => `- \`${name}\`: ${describeType(type)}`));
|
|
10165
|
+
if (component.events.length > 0) {
|
|
10166
|
+
const eventProps = component.events.map((event) => `\`${eventProp(event)}\``);
|
|
10167
|
+
lines.push("", `Events: ${eventProps.join(", ")}`);
|
|
10180
10168
|
}
|
|
10169
|
+
return lines.join("\n");
|
|
10181
10170
|
}
|
|
10182
|
-
function
|
|
10183
|
-
|
|
10184
|
-
}
|
|
10185
|
-
|
|
10186
|
-
const loops = /* @__PURE__ */ new Map();
|
|
10187
|
-
for (const variable of variables) if (variable.role === "index") loops.set(variable.name, NUMBER);
|
|
10188
|
-
else loops.set(variable.name, itemShape(variable.list, {
|
|
10189
|
-
state,
|
|
10190
|
-
loops
|
|
10191
|
-
}));
|
|
10192
|
-
return {
|
|
10193
|
-
state,
|
|
10194
|
-
loops
|
|
10195
|
-
};
|
|
10196
|
-
}
|
|
10197
|
-
function itemShape(each, scope) {
|
|
10198
|
-
const list = parseBinding(each.replace(/^\s*{{|}}\s*$/g, ""));
|
|
10199
|
-
const shape = list && shapeOf(list, scope);
|
|
10200
|
-
return shape?.kind === "list" ? shape.item : void 0;
|
|
10201
|
-
}
|
|
10202
|
-
function withLoop(scope, item, itemShape, index) {
|
|
10203
|
-
const loops = new Map(scope.loops).set(item, itemShape).set(index, NUMBER);
|
|
10204
|
-
return {
|
|
10205
|
-
state: scope.state,
|
|
10206
|
-
loops
|
|
10207
|
-
};
|
|
10208
|
-
}
|
|
10209
|
-
//#endregion
|
|
10210
|
-
//#region src/lsp/check-members.ts
|
|
10211
|
-
const BINDING = /{{([\s\S]*?)}}/g;
|
|
10212
|
-
function checkMembers(text, state) {
|
|
10213
|
-
let nodes;
|
|
10214
|
-
try {
|
|
10215
|
-
nodes = parseDmlToIR(text, {
|
|
10216
|
-
locations: true,
|
|
10217
|
-
whitespace: "preserve"
|
|
10218
|
-
});
|
|
10219
|
-
} catch {
|
|
10220
|
-
return [];
|
|
10221
|
-
}
|
|
10222
|
-
const problems = [];
|
|
10223
|
-
checkNodes(text, nodes, {
|
|
10224
|
-
state,
|
|
10225
|
-
loops: /* @__PURE__ */ new Map()
|
|
10226
|
-
}, problems);
|
|
10227
|
-
return problems;
|
|
10171
|
+
function describeType(type) {
|
|
10172
|
+
if (typeof type === "string") return type;
|
|
10173
|
+
if (isWordsOr(type)) return `${describeType(type.words)} | ${otherThanWords(type)}`;
|
|
10174
|
+
return type.map((value) => `"${value}"`).join(" | ");
|
|
10228
10175
|
}
|
|
10229
|
-
function
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
checkBindings(text, node.content, from, scope, problems);
|
|
10234
|
-
continue;
|
|
10235
|
-
}
|
|
10236
|
-
for (const value of Object.values(node.attributes)) checkBindings(text, value, from, scope, problems);
|
|
10237
|
-
if (node.tag !== "For") {
|
|
10238
|
-
checkNodes(text, node.children, scope, problems);
|
|
10239
|
-
continue;
|
|
10240
|
-
}
|
|
10241
|
-
const { each = "", item = "item", index = "index", key } = node.attributes;
|
|
10242
|
-
const itemOfList = itemShape(each, scope);
|
|
10243
|
-
if (itemOfList?.kind === "object" && key !== void 0 && !key.startsWith("*") && !(key in itemOfList.properties)) problems.push({
|
|
10244
|
-
message: `<For> \`key\` "${key}" is not a property of \`${itemOfList.type}\` (properties: ${describeMembers(itemOfList)})`,
|
|
10245
|
-
offset: offsetOf(text, `key="${key}"`, from)
|
|
10246
|
-
});
|
|
10247
|
-
checkNodes(text, node.children, withLoop(scope, item, itemOfList, index), problems);
|
|
10248
|
-
}
|
|
10176
|
+
function describeLoopVariable(variable, shape) {
|
|
10177
|
+
const list = variable.list ? ` \`${variable.list}\`` : "";
|
|
10178
|
+
if (variable.role === "item") return `each item of${list}${typeSuffix(shape)}`;
|
|
10179
|
+
return `the position in${list}`;
|
|
10249
10180
|
}
|
|
10250
|
-
function
|
|
10251
|
-
|
|
10252
|
-
const expr = parseBinding(source);
|
|
10253
|
-
if (!expr) continue;
|
|
10254
|
-
const bindingOffset = offsetOf(text, binding, from);
|
|
10255
|
-
for (const { name, owner } of unknownMembers(expr, scope)) problems.push({
|
|
10256
|
-
message: `\`${name}\` is not a property of \`${owner.type}\` (properties: ${describeMembers(owner)})`,
|
|
10257
|
-
offset: memberOffset(bindingOffset, binding, name)
|
|
10258
|
-
});
|
|
10259
|
-
}
|
|
10181
|
+
function typeSuffix(shape) {
|
|
10182
|
+
return shape ? `: \`${shape.type}\`` : "";
|
|
10260
10183
|
}
|
|
10261
|
-
function
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
case "
|
|
10265
|
-
case "
|
|
10266
|
-
case "Member": {
|
|
10267
|
-
const inner = [...found(expr.object), ...found(expr.property)];
|
|
10268
|
-
const { property } = expr;
|
|
10269
|
-
if (property.type !== "Literal" || typeof property.value !== "string") return inner;
|
|
10270
|
-
const owner = shapeOf(expr.object, scope);
|
|
10271
|
-
if (owner && owner.kind !== "value" && !(property.value in membersOf(owner))) return [...inner, {
|
|
10272
|
-
name: property.value,
|
|
10273
|
-
owner
|
|
10274
|
-
}];
|
|
10275
|
-
return inner;
|
|
10276
|
-
}
|
|
10277
|
-
case "Unary": return found(expr.argument);
|
|
10278
|
-
case "Binary": return [...found(expr.left), ...found(expr.right)];
|
|
10279
|
-
case "Conditional": return [
|
|
10280
|
-
...found(expr.test),
|
|
10281
|
-
...found(expr.consequent),
|
|
10282
|
-
...found(expr.alternate)
|
|
10283
|
-
];
|
|
10184
|
+
function describeChildren(component) {
|
|
10185
|
+
switch (component.children) {
|
|
10186
|
+
case "none": return "no children";
|
|
10187
|
+
case "text": return "text and inline components";
|
|
10188
|
+
case "elements": return "any components";
|
|
10284
10189
|
}
|
|
10285
10190
|
}
|
|
10286
|
-
function offsetOf(text, written, from) {
|
|
10287
|
-
const at = text.indexOf(written, from);
|
|
10288
|
-
return at < 0 ? from : at;
|
|
10289
|
-
}
|
|
10290
|
-
function memberOffset(bindingOffset, binding, name) {
|
|
10291
|
-
const escapedName = name.replaceAll("$", "\\$");
|
|
10292
|
-
const match = new RegExp(`(?<=\\.\\s*)${escapedName}(?![\\w$])`).exec(binding);
|
|
10293
|
-
return match ? bindingOffset + match.index : bindingOffset;
|
|
10294
|
-
}
|
|
10295
10191
|
//#endregion
|
|
10296
10192
|
//#region src/lsp/markup.ts
|
|
10297
10193
|
const TAG_NAME = /[A-Za-z0-9_.:-]/;
|
|
10298
|
-
const
|
|
10194
|
+
const ATTRIBUTE_NAME = /[^\s=/>"']/;
|
|
10299
10195
|
const WHITESPACE = /\s/;
|
|
10300
10196
|
function markupContextAt(text, offset) {
|
|
10301
|
-
const
|
|
10197
|
+
const openElements = [];
|
|
10302
10198
|
let index = 0;
|
|
10303
10199
|
while (index < offset) if (text.startsWith("<!--", index)) {
|
|
10304
10200
|
const end = text.indexOf("-->", index + 4);
|
|
@@ -10311,34 +10207,34 @@ function markupContextAt(text, offset) {
|
|
|
10311
10207
|
if (offset <= nameEnd || close < 0 || close >= offset) return {
|
|
10312
10208
|
kind: "closeTag",
|
|
10313
10209
|
start: nameStart,
|
|
10314
|
-
open:
|
|
10210
|
+
open: openElements.at(-1)
|
|
10315
10211
|
};
|
|
10316
|
-
closeElement(
|
|
10212
|
+
closeElement(openElements, text.slice(nameStart, nameEnd));
|
|
10317
10213
|
index = close + 1;
|
|
10318
10214
|
} else if (text[index] === "<") {
|
|
10319
|
-
const tag = readTag(text, index, offset,
|
|
10215
|
+
const tag = readTag(text, index, offset, openElements);
|
|
10320
10216
|
if (tag.context) return tag.context;
|
|
10321
|
-
if (tag.element && !tag.selfClosing)
|
|
10217
|
+
if (tag.element && !tag.selfClosing) openElements.push(tag.element);
|
|
10322
10218
|
index = tag.end;
|
|
10323
10219
|
} else if (text.startsWith("{{", index)) {
|
|
10324
10220
|
const end = text.indexOf("}}", index + 2);
|
|
10325
10221
|
if (end < 0 || offset <= end) return {
|
|
10326
10222
|
kind: "binding",
|
|
10327
10223
|
start: index + 2,
|
|
10328
|
-
loopVariables: loopVariables(
|
|
10224
|
+
loopVariables: loopVariables(openElements)
|
|
10329
10225
|
};
|
|
10330
10226
|
index = end + 2;
|
|
10331
10227
|
} else index++;
|
|
10332
10228
|
return { kind: "text" };
|
|
10333
10229
|
}
|
|
10334
|
-
function readTag(text, start, offset,
|
|
10230
|
+
function readTag(text, start, offset, openElements) {
|
|
10335
10231
|
const nameStart = start + 1;
|
|
10336
10232
|
const nameEnd = readWhile(text, nameStart, TAG_NAME);
|
|
10337
10233
|
if (offset <= nameEnd) return {
|
|
10338
10234
|
context: {
|
|
10339
10235
|
kind: "tagName",
|
|
10340
10236
|
start: nameStart,
|
|
10341
|
-
parents: [...
|
|
10237
|
+
parents: [...openElements]
|
|
10342
10238
|
},
|
|
10343
10239
|
end: nameEnd
|
|
10344
10240
|
};
|
|
@@ -10373,7 +10269,7 @@ function readTag(text, start, offset, open) {
|
|
|
10373
10269
|
end: index + 1
|
|
10374
10270
|
};
|
|
10375
10271
|
const attributeStart = index;
|
|
10376
|
-
index =
|
|
10272
|
+
index = readWhile(text, index + 1, ATTRIBUTE_NAME);
|
|
10377
10273
|
const name = text.slice(attributeStart, index);
|
|
10378
10274
|
if (offset <= index) return {
|
|
10379
10275
|
context: {
|
|
@@ -10393,7 +10289,7 @@ function readTag(text, start, offset, open) {
|
|
|
10393
10289
|
if (quote === "\"" || quote === "'") {
|
|
10394
10290
|
const close = text.indexOf(quote, valueStart + 1);
|
|
10395
10291
|
if (close < 0 || offset <= close) return {
|
|
10396
|
-
context: valueContext(text.slice(valueStart + 1, offset), valueStart + 1, element, name,
|
|
10292
|
+
context: valueContext(text.slice(valueStart + 1, offset), valueStart + 1, element, name, openElements),
|
|
10397
10293
|
end: offset
|
|
10398
10294
|
};
|
|
10399
10295
|
attributes[name] = text.slice(valueStart + 1, close);
|
|
@@ -10406,7 +10302,7 @@ function readTag(text, start, offset, open) {
|
|
|
10406
10302
|
start: valueStart,
|
|
10407
10303
|
element,
|
|
10408
10304
|
attribute: name,
|
|
10409
|
-
loopVariables: loopVariables(
|
|
10305
|
+
loopVariables: loopVariables(openElements)
|
|
10410
10306
|
},
|
|
10411
10307
|
end: valueEnd
|
|
10412
10308
|
};
|
|
@@ -10415,8 +10311,8 @@ function readTag(text, start, offset, open) {
|
|
|
10415
10311
|
}
|
|
10416
10312
|
}
|
|
10417
10313
|
}
|
|
10418
|
-
function valueContext(valueBeforeCursor, valueStart, element, attribute,
|
|
10419
|
-
const variables = loopVariables(
|
|
10314
|
+
function valueContext(valueBeforeCursor, valueStart, element, attribute, openElements) {
|
|
10315
|
+
const variables = loopVariables(openElements);
|
|
10420
10316
|
const bindingStart = valueBeforeCursor.lastIndexOf("{{");
|
|
10421
10317
|
if (bindingStart >= 0 && !valueBeforeCursor.includes("}}", bindingStart)) return {
|
|
10422
10318
|
kind: "binding",
|
|
@@ -10431,18 +10327,18 @@ function valueContext(valueBeforeCursor, valueStart, element, attribute, open) {
|
|
|
10431
10327
|
loopVariables: variables
|
|
10432
10328
|
};
|
|
10433
10329
|
}
|
|
10434
|
-
function closeElement(
|
|
10435
|
-
for (let index =
|
|
10436
|
-
|
|
10330
|
+
function closeElement(openElements, tag) {
|
|
10331
|
+
for (let index = openElements.length - 1; index >= 0; index--) if (openElements[index].tag === tag) {
|
|
10332
|
+
openElements.length = index;
|
|
10437
10333
|
return;
|
|
10438
10334
|
}
|
|
10439
10335
|
}
|
|
10440
|
-
function loopVariables(
|
|
10336
|
+
function loopVariables(openElements) {
|
|
10441
10337
|
const variables = [];
|
|
10442
|
-
for (const element of
|
|
10338
|
+
for (const element of openElements) {
|
|
10443
10339
|
if (element.tag !== "For") continue;
|
|
10444
10340
|
const { item = "item", index = "index", each = "" } = element.attributes;
|
|
10445
|
-
const list = each
|
|
10341
|
+
const list = bindingSource(each);
|
|
10446
10342
|
variables.push({
|
|
10447
10343
|
name: item,
|
|
10448
10344
|
role: "item",
|
|
@@ -10455,6 +10351,9 @@ function loopVariables(open) {
|
|
|
10455
10351
|
}
|
|
10456
10352
|
return variables;
|
|
10457
10353
|
}
|
|
10354
|
+
function bindingSource(value) {
|
|
10355
|
+
return value.replace(/^\s*{{\s*|\s*}}\s*$/g, "");
|
|
10356
|
+
}
|
|
10458
10357
|
function readWhile(text, index, pattern) {
|
|
10459
10358
|
while (index < text.length && pattern.test(text[index])) index++;
|
|
10460
10359
|
return index;
|
|
@@ -10463,7 +10362,6 @@ function readUntil(text, index, pattern) {
|
|
|
10463
10362
|
while (index < text.length && !pattern.test(text[index])) index++;
|
|
10464
10363
|
return index;
|
|
10465
10364
|
}
|
|
10466
|
-
/** The start and end of the run of `pattern` characters around `offset`. */
|
|
10467
10365
|
function wordAt(text, offset, pattern) {
|
|
10468
10366
|
let start = offset;
|
|
10469
10367
|
while (start > 0 && pattern.test(text[start - 1])) start--;
|
|
@@ -10473,78 +10371,163 @@ function wordAt(text, offset, pattern) {
|
|
|
10473
10371
|
};
|
|
10474
10372
|
}
|
|
10475
10373
|
//#endregion
|
|
10476
|
-
//#region src/lsp/
|
|
10477
|
-
const
|
|
10478
|
-
|
|
10479
|
-
|
|
10374
|
+
//#region src/lsp/shapes.ts
|
|
10375
|
+
const NUMBER_SHAPE = {
|
|
10376
|
+
kind: "value",
|
|
10377
|
+
type: "number"
|
|
10378
|
+
};
|
|
10480
10379
|
const MEMBER_CHAIN = /([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*\.\s*$/;
|
|
10481
|
-
|
|
10380
|
+
function parseBinding(source) {
|
|
10381
|
+
try {
|
|
10382
|
+
return parseExpression(source);
|
|
10383
|
+
} catch (error) {
|
|
10384
|
+
if (error instanceof ScriptParseError) return;
|
|
10385
|
+
throw error;
|
|
10386
|
+
}
|
|
10387
|
+
}
|
|
10388
|
+
function shapeOf(expression, scope) {
|
|
10389
|
+
switch (expression.type) {
|
|
10390
|
+
case "Identifier":
|
|
10391
|
+
if (scope.loops.has(expression.name)) return scope.loops.get(expression.name);
|
|
10392
|
+
return scope.state[expression.name];
|
|
10393
|
+
case "Member": {
|
|
10394
|
+
const owner = shapeOf(expression.object, scope);
|
|
10395
|
+
if (!owner) return;
|
|
10396
|
+
const { property } = expression;
|
|
10397
|
+
if (property.type === "Literal" && typeof property.value === "string") return membersOf(owner)[property.value];
|
|
10398
|
+
if (owner.kind === "list") return owner.item;
|
|
10399
|
+
return;
|
|
10400
|
+
}
|
|
10401
|
+
default: return;
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
function membersOf(shape) {
|
|
10405
|
+
switch (shape.kind) {
|
|
10406
|
+
case "object": return shape.properties;
|
|
10407
|
+
case "list": return { length: NUMBER_SHAPE };
|
|
10408
|
+
case "value": return {};
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
function describeMembers(shape) {
|
|
10412
|
+
const names = Object.keys(membersOf(shape));
|
|
10413
|
+
if (names.length === 0) return "none";
|
|
10414
|
+
return names.join(", ");
|
|
10415
|
+
}
|
|
10416
|
+
function loopScope(variables, state) {
|
|
10417
|
+
const loops = /* @__PURE__ */ new Map();
|
|
10418
|
+
for (const variable of variables) if (variable.role === "index") loops.set(variable.name, NUMBER_SHAPE);
|
|
10419
|
+
else loops.set(variable.name, itemShape(variable.list, {
|
|
10420
|
+
state,
|
|
10421
|
+
loops
|
|
10422
|
+
}));
|
|
10423
|
+
return {
|
|
10424
|
+
state,
|
|
10425
|
+
loops
|
|
10426
|
+
};
|
|
10427
|
+
}
|
|
10428
|
+
function bindingScope(loopVariables, page) {
|
|
10429
|
+
const shapes = page.state?.shapes;
|
|
10430
|
+
if (!shapes) return;
|
|
10431
|
+
return loopScope(loopVariables, shapes);
|
|
10432
|
+
}
|
|
10433
|
+
function itemShape(each, scope) {
|
|
10434
|
+
const list = parseBinding(bindingSource(each));
|
|
10435
|
+
if (!list) return;
|
|
10436
|
+
const shape = shapeOf(list, scope);
|
|
10437
|
+
if (shape?.kind === "list") return shape.item;
|
|
10438
|
+
}
|
|
10439
|
+
function withLoop(scope, item, itemOfList, index) {
|
|
10440
|
+
const loops = new Map(scope.loops);
|
|
10441
|
+
loops.set(item, itemOfList);
|
|
10442
|
+
loops.set(index, NUMBER_SHAPE);
|
|
10443
|
+
return {
|
|
10444
|
+
state: scope.state,
|
|
10445
|
+
loops
|
|
10446
|
+
};
|
|
10447
|
+
}
|
|
10448
|
+
function isMember(text, bindingStart, wordStart) {
|
|
10449
|
+
return text.slice(bindingStart, wordStart).trimEnd().endsWith(".");
|
|
10450
|
+
}
|
|
10451
|
+
function ownerOf(text, bindingStart, wordStart, scope) {
|
|
10452
|
+
const chain = MEMBER_CHAIN.exec(text.slice(bindingStart, wordStart))?.[1];
|
|
10453
|
+
if (chain === void 0) return;
|
|
10454
|
+
const expression = parseBinding(chain);
|
|
10455
|
+
if (!expression || !scope) return;
|
|
10456
|
+
return shapeOf(expression, scope);
|
|
10457
|
+
}
|
|
10458
|
+
//#endregion
|
|
10459
|
+
//#region src/lsp/completion.ts
|
|
10460
|
+
const IMAGE_ATTRIBUTES = ["src", "image"];
|
|
10461
|
+
const IDENTIFIER_BEFORE_CURSOR = /[A-Za-z_$][\w$]*$/;
|
|
10482
10462
|
const CONTROL_FLOW_SNIPPETS = {
|
|
10483
10463
|
If: "If condition=\"{{ $1 }}\">$0</If>",
|
|
10484
10464
|
ElseIf: "ElseIf condition=\"{{ $1 }}\">$0</ElseIf>",
|
|
10485
10465
|
Else: "Else>$0</Else>",
|
|
10486
10466
|
For: "For each=\"{{ $1 }}\" key=\"$2\">$0</For>"
|
|
10487
10467
|
};
|
|
10488
|
-
|
|
10489
|
-
If: "Shows its children when `condition` is true.",
|
|
10490
|
-
ElseIf: "Follows an `<If>` or `<ElseIf>`: shows its children when the ones before are false and `condition` is true.",
|
|
10491
|
-
Else: "Follows an `<If>` or `<ElseIf>`: shows its children when the ones before are false.",
|
|
10492
|
-
For: "Repeats its children for each item of `each`, as `item` and `index`. `key` is a property of each item, `*this` for the item itself, or `*index` to match items by position."
|
|
10493
|
-
};
|
|
10494
|
-
const CONTROL_FLOW_ATTRIBUTE_DOCS = {
|
|
10495
|
-
condition: "A single `{{ }}` binding",
|
|
10496
|
-
each: "The list to repeat, as a single `{{ }}` binding",
|
|
10497
|
-
item: "The name of each item (default `item`)",
|
|
10498
|
-
index: "The name of each position (default `index`)",
|
|
10499
|
-
key: "A property of each item, `*this` or `*index`"
|
|
10500
|
-
};
|
|
10501
|
-
function complete(document, offset, page, { components }) {
|
|
10468
|
+
function complete(document, offset, page, components) {
|
|
10502
10469
|
const text = document.getText();
|
|
10503
10470
|
const context = markupContextAt(text, offset);
|
|
10504
|
-
const range = (start) => ({
|
|
10505
|
-
start: document.positionAt(start),
|
|
10506
|
-
end: document.positionAt(offset)
|
|
10507
|
-
});
|
|
10508
|
-
const replacing = (start, items) => items.map((item) => ({
|
|
10509
|
-
...item,
|
|
10510
|
-
textEdit: {
|
|
10511
|
-
range: range(start),
|
|
10512
|
-
newText: item.insertText ?? item.label
|
|
10513
|
-
}
|
|
10514
|
-
}));
|
|
10515
10471
|
switch (context.kind) {
|
|
10516
|
-
case "tagName":
|
|
10472
|
+
case "tagName": {
|
|
10473
|
+
const items = tagCompletions(context.parents, components);
|
|
10474
|
+
return replacingFrom(document, context.start, offset, items);
|
|
10475
|
+
}
|
|
10517
10476
|
case "closeTag":
|
|
10518
10477
|
if (!context.open) return [];
|
|
10519
10478
|
return [{
|
|
10520
10479
|
label: `/${context.open.tag}`,
|
|
10521
10480
|
kind: import_main.CompletionItemKind.Property,
|
|
10522
10481
|
textEdit: {
|
|
10523
|
-
range:
|
|
10482
|
+
range: rangeFrom(document, context.start, offset),
|
|
10524
10483
|
newText: `${context.open.tag}>`
|
|
10525
10484
|
}
|
|
10526
10485
|
}];
|
|
10527
|
-
case "attributeName":
|
|
10486
|
+
case "attributeName": {
|
|
10487
|
+
const items = attributeCompletions(context.element, components);
|
|
10488
|
+
return replacingFrom(document, context.start, offset, items);
|
|
10489
|
+
}
|
|
10528
10490
|
case "attributeValue": {
|
|
10529
|
-
const scope =
|
|
10530
|
-
|
|
10491
|
+
const scope = bindingScope(context.loopVariables, page);
|
|
10492
|
+
const items = valueCompletions(context.element, context.attribute, page, scope, components);
|
|
10493
|
+
return replacingFrom(document, context.start, offset, items);
|
|
10531
10494
|
}
|
|
10532
10495
|
case "binding": {
|
|
10533
10496
|
const typed = text.slice(context.start, offset);
|
|
10534
|
-
const wordStart = offset - (
|
|
10535
|
-
const scope =
|
|
10536
|
-
|
|
10497
|
+
const wordStart = offset - (IDENTIFIER_BEFORE_CURSOR.exec(typed)?.[0] ?? "").length;
|
|
10498
|
+
const scope = bindingScope(context.loopVariables, page);
|
|
10499
|
+
let items;
|
|
10500
|
+
if (isMember(text, context.start, wordStart)) items = memberCompletions(ownerOf(text, context.start, wordStart, scope));
|
|
10501
|
+
else items = bindingCompletions(context.loopVariables, page, scope);
|
|
10502
|
+
return replacingFrom(document, wordStart, offset, items);
|
|
10537
10503
|
}
|
|
10538
10504
|
default: return [];
|
|
10539
10505
|
}
|
|
10540
10506
|
}
|
|
10507
|
+
function rangeFrom(document, start, offset) {
|
|
10508
|
+
return {
|
|
10509
|
+
start: document.positionAt(start),
|
|
10510
|
+
end: document.positionAt(offset)
|
|
10511
|
+
};
|
|
10512
|
+
}
|
|
10513
|
+
function replacingFrom(document, start, offset, items) {
|
|
10514
|
+
return items.map((item) => ({
|
|
10515
|
+
...item,
|
|
10516
|
+
textEdit: {
|
|
10517
|
+
range: rangeFrom(document, start, offset),
|
|
10518
|
+
newText: item.insertText ?? item.label
|
|
10519
|
+
}
|
|
10520
|
+
}));
|
|
10521
|
+
}
|
|
10541
10522
|
function tagCompletions(parents, components) {
|
|
10542
10523
|
if (parents.length === 0) {
|
|
10543
10524
|
const root = components[ROOT_TAG];
|
|
10544
|
-
|
|
10525
|
+
if (!root) return [];
|
|
10526
|
+
return [componentItem(root, "0")];
|
|
10545
10527
|
}
|
|
10546
10528
|
const parent = [...parents].reverse().find((element) => !(element.tag in CONTROL_FLOW_TAGS));
|
|
10547
|
-
|
|
10529
|
+
let parentManifest;
|
|
10530
|
+
if (parent) parentManifest = components[parent.tag];
|
|
10548
10531
|
const items = Object.values(components).filter((component) => !parentManifest || canHaveChildren(parentManifest, component)).map((component) => componentItem(component, "1"));
|
|
10549
10532
|
for (const [tag, snippet] of Object.entries(CONTROL_FLOW_SNIPPETS)) items.push({
|
|
10550
10533
|
label: tag,
|
|
@@ -10561,7 +10544,8 @@ function tagCompletions(parents, components) {
|
|
|
10561
10544
|
return items;
|
|
10562
10545
|
}
|
|
10563
10546
|
function componentItem(component, sortGroup) {
|
|
10564
|
-
|
|
10547
|
+
let snippet = `${component.name}$1>$0</${component.name}>`;
|
|
10548
|
+
if (component.children === "none") snippet = `${component.name}$1 />`;
|
|
10565
10549
|
return {
|
|
10566
10550
|
label: component.name,
|
|
10567
10551
|
kind: import_main.CompletionItemKind.Class,
|
|
@@ -10576,18 +10560,18 @@ function componentItem(component, sortGroup) {
|
|
|
10576
10560
|
};
|
|
10577
10561
|
}
|
|
10578
10562
|
function attributeCompletions(element, components) {
|
|
10579
|
-
const
|
|
10563
|
+
const isWritten = (name) => name in element.attributes;
|
|
10580
10564
|
const controlFlow = CONTROL_FLOW_TAGS[element.tag];
|
|
10581
|
-
if (controlFlow) return controlFlow.attributes.filter((name) => !
|
|
10565
|
+
if (controlFlow) return controlFlow.attributes.filter((name) => !isWritten(name)).map((name) => ({
|
|
10582
10566
|
label: name,
|
|
10583
10567
|
kind: import_main.CompletionItemKind.Property,
|
|
10584
10568
|
detail: CONTROL_FLOW_ATTRIBUTE_DOCS[name] ?? name,
|
|
10585
|
-
insertText: name
|
|
10569
|
+
insertText: controlFlowAttributeSnippet(name),
|
|
10586
10570
|
insertTextFormat: import_main.InsertTextFormat.Snippet
|
|
10587
10571
|
}));
|
|
10588
10572
|
const component = components[element.tag];
|
|
10589
10573
|
if (!component) return [];
|
|
10590
|
-
const props = Object.entries(component.props).filter(([name]) => !
|
|
10574
|
+
const props = Object.entries(component.props).filter(([name]) => !isWritten(name)).map(([name, type]) => ({
|
|
10591
10575
|
label: name,
|
|
10592
10576
|
kind: import_main.CompletionItemKind.Property,
|
|
10593
10577
|
detail: describeType(type),
|
|
@@ -10595,7 +10579,7 @@ function attributeCompletions(element, components) {
|
|
|
10595
10579
|
insertTextFormat: import_main.InsertTextFormat.Snippet,
|
|
10596
10580
|
sortText: `0${name}`
|
|
10597
10581
|
}));
|
|
10598
|
-
const events = component.events.map((event) => eventProp(event)).filter((name) => !
|
|
10582
|
+
const events = component.events.map((event) => eventProp(event)).filter((name) => !isWritten(name)).map((name) => ({
|
|
10599
10583
|
label: name,
|
|
10600
10584
|
kind: import_main.CompletionItemKind.Event,
|
|
10601
10585
|
detail: "a mutation or action in controller.ts",
|
|
@@ -10605,27 +10589,20 @@ function attributeCompletions(element, components) {
|
|
|
10605
10589
|
}));
|
|
10606
10590
|
return [...props, ...events];
|
|
10607
10591
|
}
|
|
10592
|
+
function controlFlowAttributeSnippet(name) {
|
|
10593
|
+
if (name === "condition" || name === "each") return `${name}="{{ $1 }}"`;
|
|
10594
|
+
return `${name}="$1"`;
|
|
10595
|
+
}
|
|
10608
10596
|
function valueCompletions(element, attribute, page, scope, components) {
|
|
10609
|
-
if (element.tag === "For" && attribute === "key")
|
|
10610
|
-
const item = scope && itemShape(element.attributes.each ?? "", scope);
|
|
10611
|
-
return [
|
|
10612
|
-
...item?.kind === "object" ? memberCompletions(item) : [],
|
|
10613
|
-
{
|
|
10614
|
-
label: "*this",
|
|
10615
|
-
kind: import_main.CompletionItemKind.Value,
|
|
10616
|
-
detail: "the item itself"
|
|
10617
|
-
},
|
|
10618
|
-
{
|
|
10619
|
-
label: "*index",
|
|
10620
|
-
kind: import_main.CompletionItemKind.Value,
|
|
10621
|
-
detail: "match items by position"
|
|
10622
|
-
}
|
|
10623
|
-
];
|
|
10624
|
-
}
|
|
10597
|
+
if (element.tag === "For" && attribute === "key") return forKeyCompletions(element, scope);
|
|
10625
10598
|
const component = components[element.tag];
|
|
10626
10599
|
if (!component) return [];
|
|
10627
|
-
if (isEventProp(attribute) && component.events.includes(eventName(attribute))) return
|
|
10600
|
+
if (isEventProp(attribute) && component.events.includes(eventName(attribute))) return handlerCompletions(page);
|
|
10628
10601
|
const type = component.props[attribute];
|
|
10602
|
+
if (type && isWordsOr(type)) return type.words.map((value) => ({
|
|
10603
|
+
label: value,
|
|
10604
|
+
kind: import_main.CompletionItemKind.EnumMember
|
|
10605
|
+
}));
|
|
10629
10606
|
if (Array.isArray(type)) return type.map((value) => ({
|
|
10630
10607
|
label: value,
|
|
10631
10608
|
kind: import_main.CompletionItemKind.EnumMember
|
|
@@ -10640,27 +10617,52 @@ function valueCompletions(element, attribute, page, scope, components) {
|
|
|
10640
10617
|
}));
|
|
10641
10618
|
return [];
|
|
10642
10619
|
}
|
|
10643
|
-
function
|
|
10620
|
+
function forKeyCompletions(element, scope) {
|
|
10621
|
+
let properties = [];
|
|
10622
|
+
if (scope) {
|
|
10623
|
+
const item = itemShape(element.attributes.each ?? "", scope);
|
|
10624
|
+
if (item?.kind === "object") properties = memberCompletions(item);
|
|
10625
|
+
}
|
|
10626
|
+
return [
|
|
10627
|
+
...properties,
|
|
10628
|
+
{
|
|
10629
|
+
label: "*this",
|
|
10630
|
+
kind: import_main.CompletionItemKind.Value,
|
|
10631
|
+
detail: "the item itself"
|
|
10632
|
+
},
|
|
10633
|
+
{
|
|
10634
|
+
label: "*index",
|
|
10635
|
+
kind: import_main.CompletionItemKind.Value,
|
|
10636
|
+
detail: "match items by position"
|
|
10637
|
+
}
|
|
10638
|
+
];
|
|
10639
|
+
}
|
|
10640
|
+
function handlerCompletions(page) {
|
|
10644
10641
|
const { controller } = page;
|
|
10645
10642
|
if (!controller) return [];
|
|
10646
|
-
|
|
10643
|
+
const mutations = (controller.mutations ?? []).map((name) => ({
|
|
10647
10644
|
label: name,
|
|
10648
10645
|
kind: import_main.CompletionItemKind.Method,
|
|
10649
10646
|
detail: "mutation"
|
|
10650
|
-
}))
|
|
10647
|
+
}));
|
|
10648
|
+
const actions = (controller.actions ?? []).map((name) => ({
|
|
10651
10649
|
label: name,
|
|
10652
10650
|
kind: import_main.CompletionItemKind.Function,
|
|
10653
10651
|
detail: "action"
|
|
10654
|
-
}))
|
|
10652
|
+
}));
|
|
10653
|
+
return [...mutations, ...actions];
|
|
10655
10654
|
}
|
|
10656
10655
|
function bindingCompletions(loopVariables, page, scope) {
|
|
10657
10656
|
const items = /* @__PURE__ */ new Map();
|
|
10658
|
-
for (const key of page.state?.keys ?? [])
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10657
|
+
for (const key of page.state?.keys ?? []) {
|
|
10658
|
+
const shape = scope?.state[key];
|
|
10659
|
+
items.set(key, {
|
|
10660
|
+
label: key,
|
|
10661
|
+
kind: import_main.CompletionItemKind.Field,
|
|
10662
|
+
detail: shape ? `state: ${shape.type}` : "state",
|
|
10663
|
+
sortText: `1${key}`
|
|
10664
|
+
});
|
|
10665
|
+
}
|
|
10664
10666
|
for (const variable of loopVariables) items.set(variable.name, {
|
|
10665
10667
|
label: variable.name,
|
|
10666
10668
|
kind: import_main.CompletionItemKind.Variable,
|
|
@@ -10677,226 +10679,109 @@ function memberCompletions(owner) {
|
|
|
10677
10679
|
detail: shape.type
|
|
10678
10680
|
}));
|
|
10679
10681
|
}
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10682
|
+
//#endregion
|
|
10683
|
+
//#region src/lsp/check-members.ts
|
|
10684
|
+
const BINDING = /{{([\s\S]*?)}}/g;
|
|
10685
|
+
function checkMembers(text, state) {
|
|
10686
|
+
let nodes;
|
|
10687
|
+
try {
|
|
10688
|
+
nodes = parseDmlToIR(text, {
|
|
10689
|
+
locations: true,
|
|
10690
|
+
whitespace: "preserve"
|
|
10691
|
+
});
|
|
10692
|
+
} catch {
|
|
10693
|
+
return [];
|
|
10694
|
+
}
|
|
10695
|
+
const problems = [];
|
|
10696
|
+
checkNodes(text, nodes, {
|
|
10697
|
+
state,
|
|
10698
|
+
loops: /* @__PURE__ */ new Map()
|
|
10699
|
+
}, problems);
|
|
10700
|
+
return problems;
|
|
10683
10701
|
}
|
|
10684
|
-
function
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
return expr && scope && shapeOf(expr, scope);
|
|
10691
|
-
}
|
|
10692
|
-
function hover(document, offset, page, { components }) {
|
|
10693
|
-
const found = symbolAt(document.getText(), offset, page);
|
|
10694
|
-
if (!found) return null;
|
|
10695
|
-
const markdown = describeSymbol(found.symbol, page, components);
|
|
10696
|
-
if (!markdown) return null;
|
|
10697
|
-
return {
|
|
10698
|
-
contents: {
|
|
10699
|
-
kind: import_main.MarkupKind.Markdown,
|
|
10700
|
-
value: markdown
|
|
10701
|
-
},
|
|
10702
|
-
range: {
|
|
10703
|
-
start: document.positionAt(found.start),
|
|
10704
|
-
end: document.positionAt(found.end)
|
|
10705
|
-
}
|
|
10706
|
-
};
|
|
10707
|
-
}
|
|
10708
|
-
function definition(document, offset, page) {
|
|
10709
|
-
const found = symbolAt(document.getText(), offset, page);
|
|
10710
|
-
if (!found) return null;
|
|
10711
|
-
const { symbol } = found;
|
|
10712
|
-
if (symbol.kind === "handler" && page.controller) return locationIn(page.controller.file, page.controller.text, page.controller.offsets[symbol.name]);
|
|
10713
|
-
if (symbol.kind === "state" && page.state) return locationIn(page.state.file, page.state.text, page.state.offsets[symbol.name]);
|
|
10714
|
-
if (symbol.kind === "property" && symbol.shape.declaration) {
|
|
10715
|
-
const { file, line, character } = symbol.shape.declaration;
|
|
10716
|
-
const position = {
|
|
10717
|
-
line,
|
|
10718
|
-
character
|
|
10719
|
-
};
|
|
10720
|
-
return {
|
|
10721
|
-
uri: pathToFileURL(file).href,
|
|
10722
|
-
range: {
|
|
10723
|
-
start: position,
|
|
10724
|
-
end: position
|
|
10725
|
-
}
|
|
10726
|
-
};
|
|
10727
|
-
}
|
|
10728
|
-
if (symbol.kind === "static" && page.root) return locationIn(join(page.root, symbol.path), "", 0);
|
|
10729
|
-
return null;
|
|
10730
|
-
}
|
|
10731
|
-
function symbolAt(text, offset, page) {
|
|
10732
|
-
const context = markupContextAt(text, offset);
|
|
10733
|
-
switch (context.kind) {
|
|
10734
|
-
case "tagName":
|
|
10735
|
-
case "closeTag": {
|
|
10736
|
-
const { start, end } = wordAt(text, offset, TAG_NAME);
|
|
10737
|
-
return {
|
|
10738
|
-
symbol: {
|
|
10739
|
-
kind: "component",
|
|
10740
|
-
tag: text.slice(start, end)
|
|
10741
|
-
},
|
|
10742
|
-
start,
|
|
10743
|
-
end
|
|
10744
|
-
};
|
|
10745
|
-
}
|
|
10746
|
-
case "attributeName": {
|
|
10747
|
-
const { start, end } = wordAt(text, offset, ATTRIBUTE_NAME);
|
|
10748
|
-
const name = text.slice(start, end);
|
|
10749
|
-
return name ? {
|
|
10750
|
-
symbol: {
|
|
10751
|
-
kind: "attribute",
|
|
10752
|
-
tag: context.element.tag,
|
|
10753
|
-
name
|
|
10754
|
-
},
|
|
10755
|
-
start,
|
|
10756
|
-
end
|
|
10757
|
-
} : null;
|
|
10758
|
-
}
|
|
10759
|
-
case "attributeValue": {
|
|
10760
|
-
const { start, end } = wordAt(text, offset, VALUE);
|
|
10761
|
-
const value = text.slice(start, end);
|
|
10762
|
-
if (isEventProp(context.attribute)) return {
|
|
10763
|
-
symbol: {
|
|
10764
|
-
kind: "handler",
|
|
10765
|
-
name: value
|
|
10766
|
-
},
|
|
10767
|
-
start,
|
|
10768
|
-
end
|
|
10769
|
-
};
|
|
10770
|
-
if (page.statics?.includes(value)) return {
|
|
10771
|
-
symbol: {
|
|
10772
|
-
kind: "static",
|
|
10773
|
-
path: value
|
|
10774
|
-
},
|
|
10775
|
-
start,
|
|
10776
|
-
end
|
|
10777
|
-
};
|
|
10778
|
-
return null;
|
|
10702
|
+
function checkNodes(text, nodes, scope, problems) {
|
|
10703
|
+
for (const node of nodes) {
|
|
10704
|
+
const from = node.loc?.offset ?? 0;
|
|
10705
|
+
if (node.type === "Text") {
|
|
10706
|
+
checkBindings(text, node.content, from, scope, problems);
|
|
10707
|
+
continue;
|
|
10779
10708
|
}
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
const scope = shapeScope(context.loopVariables, page);
|
|
10785
|
-
if (isMember(text, context.start, start)) {
|
|
10786
|
-
const owner = ownerOf(text, context.start, start, scope);
|
|
10787
|
-
const shape = owner && membersOf(owner)[name];
|
|
10788
|
-
return owner && shape ? {
|
|
10789
|
-
symbol: {
|
|
10790
|
-
kind: "property",
|
|
10791
|
-
name,
|
|
10792
|
-
owner,
|
|
10793
|
-
shape
|
|
10794
|
-
},
|
|
10795
|
-
start,
|
|
10796
|
-
end
|
|
10797
|
-
} : null;
|
|
10798
|
-
}
|
|
10799
|
-
const variable = [...context.loopVariables].reverse().find((candidate) => candidate.name === name);
|
|
10800
|
-
if (variable) return {
|
|
10801
|
-
symbol: {
|
|
10802
|
-
kind: "loopVariable",
|
|
10803
|
-
variable,
|
|
10804
|
-
shape: scope?.loops.get(name)
|
|
10805
|
-
},
|
|
10806
|
-
start,
|
|
10807
|
-
end
|
|
10808
|
-
};
|
|
10809
|
-
return {
|
|
10810
|
-
symbol: {
|
|
10811
|
-
kind: "state",
|
|
10812
|
-
name
|
|
10813
|
-
},
|
|
10814
|
-
start,
|
|
10815
|
-
end
|
|
10816
|
-
};
|
|
10709
|
+
for (const value of Object.values(node.attributes)) checkBindings(text, value, from, scope, problems);
|
|
10710
|
+
if (node.tag !== "For") {
|
|
10711
|
+
checkNodes(text, node.children, scope, problems);
|
|
10712
|
+
continue;
|
|
10817
10713
|
}
|
|
10818
|
-
|
|
10714
|
+
const { each = "", item = "item", index = "index", key } = node.attributes;
|
|
10715
|
+
const itemOfList = itemShape(each, scope);
|
|
10716
|
+
if (key !== void 0 && !key.startsWith("*") && itemOfList?.kind === "object" && !(key in itemOfList.properties)) problems.push({
|
|
10717
|
+
message: `The <For> \`key\` "${key}" is not a property of \`${itemOfList.type}\`. It has: ${describeMembers(itemOfList)}`,
|
|
10718
|
+
offset: offsetOf(text, `key="${key}"`, from)
|
|
10719
|
+
});
|
|
10720
|
+
checkNodes(text, node.children, withLoop(scope, item, itemOfList, index), problems);
|
|
10819
10721
|
}
|
|
10820
10722
|
}
|
|
10821
|
-
function
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
if (CONTROL_FLOW_TAGS[symbol.tag]) {
|
|
10831
|
-
const doc = CONTROL_FLOW_ATTRIBUTE_DOCS[symbol.name];
|
|
10832
|
-
return doc ? `\`${symbol.name}\`: ${doc}` : null;
|
|
10833
|
-
}
|
|
10834
|
-
const component = components[symbol.tag];
|
|
10835
|
-
if (!component) return null;
|
|
10836
|
-
const type = component.props[symbol.name];
|
|
10837
|
-
if (type) return `\`${symbol.name}\`: ${describeType(type)}`;
|
|
10838
|
-
if (symbol.name.startsWith(DATA_PREFIX)) return `\`${symbol.name}\`: reaches this element's handlers as \`event.data.${dataKey(symbol.name)}\``;
|
|
10839
|
-
if (isEventProp(symbol.name) && component.events.includes(eventName(symbol.name))) return `\`${symbol.name}\`: runs a mutation or action in controller.ts on \`${eventName(symbol.name)}\``;
|
|
10840
|
-
return null;
|
|
10841
|
-
}
|
|
10842
|
-
case "handler": {
|
|
10843
|
-
const { controller } = page;
|
|
10844
|
-
if (controller?.mutations?.includes(symbol.name)) return `mutation \`${symbol.name}\` in controller.ts`;
|
|
10845
|
-
if (controller?.actions?.includes(symbol.name)) return `action \`${symbol.name}\` in controller.ts`;
|
|
10846
|
-
return null;
|
|
10847
|
-
}
|
|
10848
|
-
case "state": return page.state?.keys?.includes(symbol.name) ? `state \`${symbol.name}\` in state.ts${typeSuffix(page.state.shapes?.[symbol.name])}` : null;
|
|
10849
|
-
case "loopVariable": return `\`${symbol.variable.name}\`: ${describeLoopVariable(symbol.variable, symbol.shape)}`;
|
|
10850
|
-
case "property": return `\`${symbol.name}\`: \`${symbol.shape.type}\`, a property of \`${symbol.owner.type}\``;
|
|
10851
|
-
case "static": return `image \`${symbol.path}\``;
|
|
10723
|
+
function checkBindings(text, value, from, scope, problems) {
|
|
10724
|
+
for (const [binding, source = ""] of value.matchAll(BINDING)) {
|
|
10725
|
+
const expression = parseBinding(source);
|
|
10726
|
+
if (!expression) continue;
|
|
10727
|
+
const bindingOffset = offsetOf(text, binding, from);
|
|
10728
|
+
for (const { name, owner } of unknownMembers(expression, scope)) problems.push({
|
|
10729
|
+
message: `\`${name}\` is not a property of \`${owner.type}\`. It has: ${describeMembers(owner)}`,
|
|
10730
|
+
offset: memberOffset(bindingOffset, binding, name)
|
|
10731
|
+
});
|
|
10852
10732
|
}
|
|
10853
10733
|
}
|
|
10854
|
-
function
|
|
10855
|
-
const
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10734
|
+
function unknownMembers(expression, scope) {
|
|
10735
|
+
const unknownIn = (node) => unknownMembers(node, scope);
|
|
10736
|
+
switch (expression.type) {
|
|
10737
|
+
case "Literal":
|
|
10738
|
+
case "Identifier": return [];
|
|
10739
|
+
case "Member": {
|
|
10740
|
+
const inner = [...unknownIn(expression.object), ...unknownIn(expression.property)];
|
|
10741
|
+
const { property } = expression;
|
|
10742
|
+
if (property.type !== "Literal" || typeof property.value !== "string") return inner;
|
|
10743
|
+
const owner = shapeOf(expression.object, scope);
|
|
10744
|
+
if (owner && owner.kind !== "value" && !(property.value in membersOf(owner))) return [...inner, {
|
|
10745
|
+
name: property.value,
|
|
10746
|
+
owner
|
|
10747
|
+
}];
|
|
10748
|
+
return inner;
|
|
10749
|
+
}
|
|
10750
|
+
case "Unary": return unknownIn(expression.argument);
|
|
10751
|
+
case "Binary": return [...unknownIn(expression.left), ...unknownIn(expression.right)];
|
|
10752
|
+
case "Conditional": return [
|
|
10753
|
+
...unknownIn(expression.test),
|
|
10754
|
+
...unknownIn(expression.consequent),
|
|
10755
|
+
...unknownIn(expression.alternate)
|
|
10756
|
+
];
|
|
10866
10757
|
}
|
|
10867
10758
|
}
|
|
10868
|
-
function
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
function describeLoopVariable(variable, shape) {
|
|
10873
|
-
const list = variable.list ? ` \`${variable.list}\`` : "";
|
|
10874
|
-
return variable.role === "item" ? `each item of${list}${typeSuffix(shape)}` : `the position in${list}`;
|
|
10875
|
-
}
|
|
10876
|
-
function typeSuffix(shape) {
|
|
10877
|
-
return shape ? `: \`${shape.type}\`` : "";
|
|
10759
|
+
function offsetOf(text, written, from) {
|
|
10760
|
+
const foundAt = text.indexOf(written, from);
|
|
10761
|
+
if (foundAt < 0) return from;
|
|
10762
|
+
return foundAt;
|
|
10878
10763
|
}
|
|
10879
|
-
function
|
|
10880
|
-
|
|
10881
|
-
const
|
|
10882
|
-
return
|
|
10883
|
-
|
|
10884
|
-
range: {
|
|
10885
|
-
start: position,
|
|
10886
|
-
end: position
|
|
10887
|
-
}
|
|
10888
|
-
};
|
|
10764
|
+
function memberOffset(bindingOffset, binding, name) {
|
|
10765
|
+
const escapedName = name.replaceAll("$", "\\$");
|
|
10766
|
+
const match = new RegExp(`(?<=\\.\\s*)${escapedName}(?![\\w$])`).exec(binding);
|
|
10767
|
+
if (!match) return bindingOffset;
|
|
10768
|
+
return bindingOffset + match.index;
|
|
10889
10769
|
}
|
|
10890
|
-
|
|
10770
|
+
//#endregion
|
|
10771
|
+
//#region src/lsp/diagnostics.ts
|
|
10772
|
+
function diagnose(document, file, page, components) {
|
|
10891
10773
|
const diagnostics = [];
|
|
10892
10774
|
const context = {
|
|
10893
10775
|
file,
|
|
10894
10776
|
components,
|
|
10895
|
-
onWarning: (warning) =>
|
|
10777
|
+
onWarning: (warning) => {
|
|
10778
|
+
diagnostics.push(sourceDiagnostic(document, warning, import_main.DiagnosticSeverity.Warning));
|
|
10779
|
+
}
|
|
10896
10780
|
};
|
|
10897
10781
|
if (page.statics) context.statics = page.statics;
|
|
10898
10782
|
if (page.state?.keys) context.stateKeys = page.state.keys;
|
|
10899
|
-
const
|
|
10783
|
+
const mutations = page.controller?.mutations;
|
|
10784
|
+
const actions = page.controller?.actions;
|
|
10900
10785
|
if (mutations && actions) context.handlers = [...mutations, ...actions];
|
|
10901
10786
|
try {
|
|
10902
10787
|
compileView(document.getText(), context);
|
|
@@ -10912,15 +10797,15 @@ function sourceDiagnostic(document, problem, severity) {
|
|
|
10912
10797
|
}
|
|
10913
10798
|
function diagnosticAt(document, start, message, severity) {
|
|
10914
10799
|
const text = document.getText();
|
|
10915
|
-
|
|
10916
|
-
|
|
10800
|
+
const end = readUntil(text, start, /[\s>]/);
|
|
10801
|
+
const atLeastOneCharacter = Math.min(start + 1, text.length);
|
|
10917
10802
|
return {
|
|
10918
10803
|
severity,
|
|
10919
10804
|
source: "dot",
|
|
10920
10805
|
message,
|
|
10921
10806
|
range: {
|
|
10922
10807
|
start: document.positionAt(start),
|
|
10923
|
-
end: document.positionAt(Math.max(end,
|
|
10808
|
+
end: document.positionAt(Math.max(end, atLeastOneCharacter))
|
|
10924
10809
|
}
|
|
10925
10810
|
};
|
|
10926
10811
|
}
|
|
@@ -10997,13 +10882,13 @@ var StateTypes = class {
|
|
|
10997
10882
|
const key = root ?? file;
|
|
10998
10883
|
let project = this.#projects.get(key);
|
|
10999
10884
|
if (!project) {
|
|
11000
|
-
project = new
|
|
10885
|
+
project = new TypeScriptProject(root);
|
|
11001
10886
|
this.#projects.set(key, project);
|
|
11002
10887
|
}
|
|
11003
10888
|
return project.read(file, text);
|
|
11004
10889
|
}
|
|
11005
10890
|
};
|
|
11006
|
-
var
|
|
10891
|
+
var TypeScriptProject = class {
|
|
11007
10892
|
#options;
|
|
11008
10893
|
#fileNames;
|
|
11009
10894
|
#open = /* @__PURE__ */ new Map();
|
|
@@ -11022,7 +10907,8 @@ var Project = class {
|
|
|
11022
10907
|
},
|
|
11023
10908
|
getScriptSnapshot: (file) => {
|
|
11024
10909
|
const text = this.#open.get(file)?.text ?? ts.sys.readFile(file);
|
|
11025
|
-
|
|
10910
|
+
if (text === void 0) return;
|
|
10911
|
+
return ts.ScriptSnapshot.fromString(text);
|
|
11026
10912
|
},
|
|
11027
10913
|
getCurrentDirectory: () => root ?? process.cwd(),
|
|
11028
10914
|
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
@@ -11046,7 +10932,8 @@ var Project = class {
|
|
|
11046
10932
|
if (!program || !source) return;
|
|
11047
10933
|
const checker = program.getTypeChecker();
|
|
11048
10934
|
const module = checker.getSymbolAtLocation(source);
|
|
11049
|
-
|
|
10935
|
+
if (!module) return;
|
|
10936
|
+
let exported = checker.tryGetMemberInModuleExports("default", module);
|
|
11050
10937
|
if (!exported) return;
|
|
11051
10938
|
if (exported.flags & ts.SymbolFlags.Alias) exported = checker.getAliasedSymbol(exported);
|
|
11052
10939
|
const state = checker.getTypeOfSymbol(exported);
|
|
@@ -11058,9 +10945,9 @@ var Project = class {
|
|
|
11058
10945
|
function readConfig(root) {
|
|
11059
10946
|
const file = join(root, "tsconfig.json");
|
|
11060
10947
|
if (!existsSync(file)) return;
|
|
11061
|
-
const
|
|
11062
|
-
if (
|
|
11063
|
-
return ts.parseJsonConfigFileContent(
|
|
10948
|
+
const configFile = ts.readConfigFile(file, (path) => ts.sys.readFile(path));
|
|
10949
|
+
if (configFile.error) return;
|
|
10950
|
+
return ts.parseJsonConfigFileContent(configFile.config, ts.sys, root, void 0, file);
|
|
11064
10951
|
}
|
|
11065
10952
|
function propertyShape(checker, property, depth, seen) {
|
|
11066
10953
|
const shape = typeShape(checker, checker.getTypeOfSymbol(property), depth, seen);
|
|
@@ -11087,13 +10974,15 @@ function typeShape(checker, type, depth, seen) {
|
|
|
11087
10974
|
const inner = new Set(seen).add(present);
|
|
11088
10975
|
if (checker.isArrayType(present) || checker.isTupleType(present)) {
|
|
11089
10976
|
const item = checker.getIndexTypeOfType(present, ts.IndexKind.Number);
|
|
10977
|
+
let itemShape = {
|
|
10978
|
+
kind: "value",
|
|
10979
|
+
type: "unknown"
|
|
10980
|
+
};
|
|
10981
|
+
if (item) itemShape = typeShape(checker, item, depth + 1, inner);
|
|
11090
10982
|
return {
|
|
11091
10983
|
kind: "list",
|
|
11092
10984
|
type: name,
|
|
11093
|
-
item:
|
|
11094
|
-
kind: "value",
|
|
11095
|
-
type: "unknown"
|
|
11096
|
-
}
|
|
10985
|
+
item: itemShape
|
|
11097
10986
|
};
|
|
11098
10987
|
}
|
|
11099
10988
|
if (!isRecord(checker, present)) return {
|
|
@@ -11112,40 +11001,231 @@ function isRecord(checker, type) {
|
|
|
11112
11001
|
return (type.isUnion() ? type.types : [type]).every((member) => (member.flags & ts.TypeFlags.Object) !== 0 && member.getCallSignatures().length === 0 && checker.getIndexInfosOfType(member).length === 0);
|
|
11113
11002
|
}
|
|
11114
11003
|
//#endregion
|
|
11004
|
+
//#region src/lsp/symbols.ts
|
|
11005
|
+
const IDENTIFIER = /[\w$]/;
|
|
11006
|
+
const VALUE = /[^\s"']/;
|
|
11007
|
+
function hover(document, offset, page, components) {
|
|
11008
|
+
const found = symbolAt(document.getText(), offset, page);
|
|
11009
|
+
if (!found) return null;
|
|
11010
|
+
const markdown = describeSymbol(found.symbol, page, components);
|
|
11011
|
+
if (!markdown) return null;
|
|
11012
|
+
return {
|
|
11013
|
+
contents: {
|
|
11014
|
+
kind: import_main.MarkupKind.Markdown,
|
|
11015
|
+
value: markdown
|
|
11016
|
+
},
|
|
11017
|
+
range: {
|
|
11018
|
+
start: document.positionAt(found.start),
|
|
11019
|
+
end: document.positionAt(found.end)
|
|
11020
|
+
}
|
|
11021
|
+
};
|
|
11022
|
+
}
|
|
11023
|
+
function definition(document, offset, page) {
|
|
11024
|
+
const found = symbolAt(document.getText(), offset, page);
|
|
11025
|
+
if (!found) return null;
|
|
11026
|
+
const { symbol } = found;
|
|
11027
|
+
if (symbol.kind === "handler" && page.controller) return definitionIn(page.controller, symbol.name);
|
|
11028
|
+
if (symbol.kind === "state" && page.state) return definitionIn(page.state, symbol.name);
|
|
11029
|
+
if (symbol.kind === "property" && symbol.shape.declaration) {
|
|
11030
|
+
const { file, line, character } = symbol.shape.declaration;
|
|
11031
|
+
const position = {
|
|
11032
|
+
line,
|
|
11033
|
+
character
|
|
11034
|
+
};
|
|
11035
|
+
return {
|
|
11036
|
+
uri: pathToFileURL(file).href,
|
|
11037
|
+
range: {
|
|
11038
|
+
start: position,
|
|
11039
|
+
end: position
|
|
11040
|
+
}
|
|
11041
|
+
};
|
|
11042
|
+
}
|
|
11043
|
+
if (symbol.kind === "static" && page.root) return locationIn(join(page.root, symbol.path), "", 0);
|
|
11044
|
+
return null;
|
|
11045
|
+
}
|
|
11046
|
+
function symbolAt(text, offset, page) {
|
|
11047
|
+
const context = markupContextAt(text, offset);
|
|
11048
|
+
switch (context.kind) {
|
|
11049
|
+
case "tagName":
|
|
11050
|
+
case "closeTag": {
|
|
11051
|
+
const { start, end } = wordAt(text, offset, TAG_NAME);
|
|
11052
|
+
return {
|
|
11053
|
+
symbol: {
|
|
11054
|
+
kind: "component",
|
|
11055
|
+
tag: text.slice(start, end)
|
|
11056
|
+
},
|
|
11057
|
+
start,
|
|
11058
|
+
end
|
|
11059
|
+
};
|
|
11060
|
+
}
|
|
11061
|
+
case "attributeName": {
|
|
11062
|
+
const { start, end } = wordAt(text, offset, ATTRIBUTE_NAME);
|
|
11063
|
+
const name = text.slice(start, end);
|
|
11064
|
+
if (!name) return null;
|
|
11065
|
+
return {
|
|
11066
|
+
symbol: {
|
|
11067
|
+
kind: "attribute",
|
|
11068
|
+
tag: context.element.tag,
|
|
11069
|
+
name
|
|
11070
|
+
},
|
|
11071
|
+
start,
|
|
11072
|
+
end
|
|
11073
|
+
};
|
|
11074
|
+
}
|
|
11075
|
+
case "attributeValue": {
|
|
11076
|
+
const { start, end } = wordAt(text, offset, VALUE);
|
|
11077
|
+
const value = text.slice(start, end);
|
|
11078
|
+
if (isEventProp(context.attribute)) return {
|
|
11079
|
+
symbol: {
|
|
11080
|
+
kind: "handler",
|
|
11081
|
+
name: value
|
|
11082
|
+
},
|
|
11083
|
+
start,
|
|
11084
|
+
end
|
|
11085
|
+
};
|
|
11086
|
+
if (page.statics?.includes(value)) return {
|
|
11087
|
+
symbol: {
|
|
11088
|
+
kind: "static",
|
|
11089
|
+
path: value
|
|
11090
|
+
},
|
|
11091
|
+
start,
|
|
11092
|
+
end
|
|
11093
|
+
};
|
|
11094
|
+
return null;
|
|
11095
|
+
}
|
|
11096
|
+
case "binding": {
|
|
11097
|
+
const { start, end } = wordAt(text, offset, IDENTIFIER);
|
|
11098
|
+
const name = text.slice(start, end);
|
|
11099
|
+
if (!name) return null;
|
|
11100
|
+
const scope = bindingScope(context.loopVariables, page);
|
|
11101
|
+
if (isMember(text, context.start, start)) {
|
|
11102
|
+
const owner = ownerOf(text, context.start, start, scope);
|
|
11103
|
+
if (!owner) return null;
|
|
11104
|
+
const shape = membersOf(owner)[name];
|
|
11105
|
+
if (!shape) return null;
|
|
11106
|
+
return {
|
|
11107
|
+
symbol: {
|
|
11108
|
+
kind: "property",
|
|
11109
|
+
name,
|
|
11110
|
+
owner,
|
|
11111
|
+
shape
|
|
11112
|
+
},
|
|
11113
|
+
start,
|
|
11114
|
+
end
|
|
11115
|
+
};
|
|
11116
|
+
}
|
|
11117
|
+
const variable = [...context.loopVariables].reverse().find((candidate) => candidate.name === name);
|
|
11118
|
+
if (variable) return {
|
|
11119
|
+
symbol: {
|
|
11120
|
+
kind: "loopVariable",
|
|
11121
|
+
variable,
|
|
11122
|
+
shape: scope?.loops.get(name)
|
|
11123
|
+
},
|
|
11124
|
+
start,
|
|
11125
|
+
end
|
|
11126
|
+
};
|
|
11127
|
+
return {
|
|
11128
|
+
symbol: {
|
|
11129
|
+
kind: "state",
|
|
11130
|
+
name
|
|
11131
|
+
},
|
|
11132
|
+
start,
|
|
11133
|
+
end
|
|
11134
|
+
};
|
|
11135
|
+
}
|
|
11136
|
+
default: return null;
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
function describeSymbol(symbol, page, components) {
|
|
11140
|
+
switch (symbol.kind) {
|
|
11141
|
+
case "component": {
|
|
11142
|
+
const controlFlowDoc = CONTROL_FLOW_DOCS[symbol.tag];
|
|
11143
|
+
if (controlFlowDoc) return `**<${symbol.tag}>** · control flow\n\n${controlFlowDoc}`;
|
|
11144
|
+
const component = components[symbol.tag];
|
|
11145
|
+
if (!component) return null;
|
|
11146
|
+
return describeComponent(component);
|
|
11147
|
+
}
|
|
11148
|
+
case "attribute": return describeAttribute(symbol.tag, symbol.name, components);
|
|
11149
|
+
case "handler": {
|
|
11150
|
+
const { controller } = page;
|
|
11151
|
+
if (controller?.mutations?.includes(symbol.name)) return `mutation \`${symbol.name}\` in controller.ts`;
|
|
11152
|
+
if (controller?.actions?.includes(symbol.name)) return `action \`${symbol.name}\` in controller.ts`;
|
|
11153
|
+
return null;
|
|
11154
|
+
}
|
|
11155
|
+
case "state": {
|
|
11156
|
+
if (!page.state?.keys?.includes(symbol.name)) return null;
|
|
11157
|
+
const shape = page.state.shapes?.[symbol.name];
|
|
11158
|
+
return `state \`${symbol.name}\` in state.ts${typeSuffix(shape)}`;
|
|
11159
|
+
}
|
|
11160
|
+
case "loopVariable": return `\`${symbol.variable.name}\`: ${describeLoopVariable(symbol.variable, symbol.shape)}`;
|
|
11161
|
+
case "property": return `\`${symbol.name}\`: \`${symbol.shape.type}\`, a property of \`${symbol.owner.type}\``;
|
|
11162
|
+
case "static": return `image \`${symbol.path}\``;
|
|
11163
|
+
}
|
|
11164
|
+
}
|
|
11165
|
+
function describeAttribute(tag, name, components) {
|
|
11166
|
+
if (CONTROL_FLOW_TAGS[tag]) {
|
|
11167
|
+
const doc = CONTROL_FLOW_ATTRIBUTE_DOCS[name];
|
|
11168
|
+
if (!doc) return null;
|
|
11169
|
+
return `\`${name}\`: ${doc}`;
|
|
11170
|
+
}
|
|
11171
|
+
const component = components[tag];
|
|
11172
|
+
if (!component) return null;
|
|
11173
|
+
const type = component.props[name];
|
|
11174
|
+
if (type) return `\`${name}\`: ${describeType(type)}`;
|
|
11175
|
+
if (name.startsWith(DATA_PREFIX)) return `\`${name}\`: this element's handlers get it as \`event.data.${dataKey(name)}\``;
|
|
11176
|
+
if (isEventProp(name) && component.events.includes(eventName(name))) return `\`${name}\`: on \`${eventName(name)}\`, runs a mutation or action from controller.ts`;
|
|
11177
|
+
return null;
|
|
11178
|
+
}
|
|
11179
|
+
function definitionIn(source, name) {
|
|
11180
|
+
return locationIn(source.file, source.text, source.offsets[name]);
|
|
11181
|
+
}
|
|
11182
|
+
function locationIn(file, text, offset) {
|
|
11183
|
+
if (offset === void 0) return null;
|
|
11184
|
+
const position = TextDocument.create("", "typescript", 0, text).positionAt(offset);
|
|
11185
|
+
return {
|
|
11186
|
+
uri: pathToFileURL(file).href,
|
|
11187
|
+
range: {
|
|
11188
|
+
start: position,
|
|
11189
|
+
end: position
|
|
11190
|
+
}
|
|
11191
|
+
};
|
|
11192
|
+
}
|
|
11193
|
+
//#endregion
|
|
11115
11194
|
//#region src/lsp/server.ts
|
|
11116
|
-
const
|
|
11195
|
+
const WATCHED_FILES = [
|
|
11117
11196
|
"**/*.ts",
|
|
11118
11197
|
"**/dot.json",
|
|
11119
11198
|
"**/statics/**"
|
|
11120
11199
|
];
|
|
11121
11200
|
function startLanguageServer(connection) {
|
|
11122
11201
|
const documents = new import_main.TextDocuments(TextDocument);
|
|
11123
|
-
const
|
|
11202
|
+
const components = builtinRegistry;
|
|
11124
11203
|
const stateTypes = new StateTypes();
|
|
11125
11204
|
let canWatchFiles = false;
|
|
11126
11205
|
const readText = async (file) => {
|
|
11127
|
-
const
|
|
11128
|
-
if (
|
|
11129
|
-
|
|
11206
|
+
const openDocument = documents.get(pathToFileURL(file).href);
|
|
11207
|
+
if (openDocument) return openDocument.getText();
|
|
11208
|
+
if (!existsSync(file)) return;
|
|
11209
|
+
return readFile(file, "utf8");
|
|
11130
11210
|
};
|
|
11131
|
-
const
|
|
11211
|
+
const openView = async (uri) => {
|
|
11132
11212
|
const document = documents.get(uri);
|
|
11133
11213
|
if (!document || !uri.endsWith(".dml")) return;
|
|
11134
11214
|
const file = fileURLToPath(uri);
|
|
11135
11215
|
return {
|
|
11136
11216
|
document,
|
|
11137
|
-
|
|
11138
|
-
file
|
|
11217
|
+
file,
|
|
11218
|
+
page: await readPage(file, readText, stateTypes)
|
|
11139
11219
|
};
|
|
11140
11220
|
};
|
|
11141
11221
|
const validate = async (uri) => {
|
|
11142
11222
|
try {
|
|
11143
|
-
const
|
|
11144
|
-
if (!
|
|
11145
|
-
const diagnostics = diagnose(
|
|
11223
|
+
const view = await openView(uri);
|
|
11224
|
+
if (!view) return;
|
|
11225
|
+
const diagnostics = diagnose(view.document, view.file, view.page, components);
|
|
11146
11226
|
await connection.sendDiagnostics({
|
|
11147
11227
|
uri,
|
|
11148
|
-
version:
|
|
11228
|
+
version: view.document.version,
|
|
11149
11229
|
diagnostics
|
|
11150
11230
|
});
|
|
11151
11231
|
} catch (error) {
|
|
@@ -11174,10 +11254,14 @@ function startLanguageServer(connection) {
|
|
|
11174
11254
|
};
|
|
11175
11255
|
});
|
|
11176
11256
|
connection.onInitialized(() => {
|
|
11177
|
-
if (canWatchFiles)
|
|
11257
|
+
if (canWatchFiles) {
|
|
11258
|
+
const watchers = WATCHED_FILES.map((globPattern) => ({ globPattern }));
|
|
11259
|
+
connection.client.register(import_main.DidChangeWatchedFilesNotification.type, { watchers });
|
|
11260
|
+
}
|
|
11178
11261
|
});
|
|
11179
11262
|
documents.onDidChangeContent(({ document }) => {
|
|
11180
|
-
document.uri.endsWith(".dml")
|
|
11263
|
+
if (document.uri.endsWith(".dml")) validate(document.uri);
|
|
11264
|
+
else validateAll();
|
|
11181
11265
|
});
|
|
11182
11266
|
documents.onDidClose(({ document }) => {
|
|
11183
11267
|
connection.sendDiagnostics({
|
|
@@ -11188,20 +11272,28 @@ function startLanguageServer(connection) {
|
|
|
11188
11272
|
connection.onDidChangeWatchedFiles(() => {
|
|
11189
11273
|
validateAll();
|
|
11190
11274
|
});
|
|
11191
|
-
|
|
11192
|
-
const
|
|
11193
|
-
if (!
|
|
11194
|
-
return
|
|
11275
|
+
const openViewAt = async ({ textDocument, position }) => {
|
|
11276
|
+
const view = await openView(textDocument.uri);
|
|
11277
|
+
if (!view) return;
|
|
11278
|
+
return {
|
|
11279
|
+
...view,
|
|
11280
|
+
offset: view.document.offsetAt(position)
|
|
11281
|
+
};
|
|
11282
|
+
};
|
|
11283
|
+
connection.onCompletion(async (request) => {
|
|
11284
|
+
const view = await openViewAt(request);
|
|
11285
|
+
if (!view) return [];
|
|
11286
|
+
return complete(view.document, view.offset, view.page, components);
|
|
11195
11287
|
});
|
|
11196
|
-
connection.onHover(async (
|
|
11197
|
-
const
|
|
11198
|
-
if (!
|
|
11199
|
-
return hover(
|
|
11288
|
+
connection.onHover(async (request) => {
|
|
11289
|
+
const view = await openViewAt(request);
|
|
11290
|
+
if (!view) return null;
|
|
11291
|
+
return hover(view.document, view.offset, view.page, components);
|
|
11200
11292
|
});
|
|
11201
|
-
connection.onDefinition(async (
|
|
11202
|
-
const
|
|
11203
|
-
if (!
|
|
11204
|
-
return definition(
|
|
11293
|
+
connection.onDefinition(async (request) => {
|
|
11294
|
+
const view = await openViewAt(request);
|
|
11295
|
+
if (!view) return null;
|
|
11296
|
+
return definition(view.document, view.offset, view.page);
|
|
11205
11297
|
});
|
|
11206
11298
|
documents.listen(connection);
|
|
11207
11299
|
connection.listen();
|
|
@@ -11213,4 +11305,4 @@ function serveStdio() {
|
|
|
11213
11305
|
//#endregion
|
|
11214
11306
|
export { serveStdio };
|
|
11215
11307
|
|
|
11216
|
-
//# sourceMappingURL=server-
|
|
11308
|
+
//# sourceMappingURL=server-DMnWDdJ6.js.map
|