@monimesl/dot-cli 0.1.0-alpha.4 → 0.1.0-alpha.41
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 +84 -29
- package/dist/cli.js +686 -319
- package/dist/cli.js.map +1 -1
- package/dist/{server-DFOUQnLA.js → server-DMnWDdJ6.js} +582 -537
- package/dist/server-DMnWDdJ6.js.map +1 -0
- package/dist/templates/showcase/.env +1 -1
- package/dist/templates/showcase/dot.ts +53 -14
- 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/basics/bindings/controller.ts +15 -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/{state → basics/state}/state.ts +1 -1
- package/dist/templates/showcase/pages/basics/state/view.dml +46 -0
- package/dist/templates/showcase/pages/components/carousel/controller.ts +41 -0
- package/dist/templates/showcase/pages/components/carousel/state.ts +77 -0
- package/dist/templates/showcase/pages/components/carousel/view.dml +42 -0
- package/dist/templates/showcase/pages/components/feedback/controller.ts +32 -0
- package/dist/templates/showcase/pages/components/feedback/state.ts +41 -0
- package/dist/templates/showcase/pages/components/feedback/view.dml +116 -0
- package/dist/templates/showcase/pages/components/forms/controller.ts +72 -0
- package/dist/templates/showcase/pages/components/forms/state.ts +37 -0
- 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/components/images/controller.ts +44 -0
- package/dist/templates/showcase/pages/{images → components/images}/state.ts +3 -0
- package/dist/templates/showcase/pages/components/images/view.dml +83 -0
- package/dist/templates/showcase/pages/components/layout/controller.ts +61 -0
- package/dist/templates/showcase/pages/components/layout/state.ts +34 -0
- 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/components/lists/state.ts +130 -0
- package/dist/templates/showcase/pages/components/lists/view.dml +88 -0
- package/dist/templates/showcase/pages/components/money/controller.ts +45 -0
- package/dist/templates/showcase/pages/{payments → components/money}/state.ts +3 -0
- 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/components/sheets/controller.ts +36 -0
- 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/components/tabs/state.ts +127 -0
- 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/dash/cache/controller.ts +50 -0
- package/dist/templates/showcase/pages/dash/cache/state.ts +17 -0
- 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/dash/files/controller.ts +71 -0
- package/dist/templates/showcase/pages/dash/files/state.ts +23 -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/dash/network/controller.ts +215 -0
- package/dist/templates/showcase/pages/dash/network/state.ts +35 -0
- package/dist/templates/showcase/pages/dash/network/view.dml +108 -0
- package/dist/templates/showcase/pages/dash/payments/controller.ts +83 -0
- package/dist/templates/showcase/pages/dash/payments/state.ts +10 -0
- package/dist/templates/showcase/pages/dash/payments/view.dml +38 -0
- package/dist/templates/showcase/pages/dash/secrets/controller.ts +44 -0
- package/dist/templates/showcase/pages/dash/secrets/state.ts +5 -0
- 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 +181 -54
- package/dist/templates/showcase/pages/index/view.dml +8 -5
- package/dist/templates/showcase/statics/logo.png +0 -0
- package/package.json +6 -6
- package/dist/server-DFOUQnLA.js.map +0 -1
- package/dist/templates/showcase/pages/carousel/controller.ts +0 -10
- package/dist/templates/showcase/pages/carousel/state.ts +0 -40
- package/dist/templates/showcase/pages/carousel/view.dml +0 -37
- package/dist/templates/showcase/pages/device/controller.ts +0 -70
- package/dist/templates/showcase/pages/device/state.ts +0 -7
- package/dist/templates/showcase/pages/device/view.dml +0 -44
- package/dist/templates/showcase/pages/feedback/controller.ts +0 -16
- package/dist/templates/showcase/pages/feedback/state.ts +0 -7
- package/dist/templates/showcase/pages/feedback/view.dml +0 -87
- package/dist/templates/showcase/pages/files/controller.ts +0 -47
- package/dist/templates/showcase/pages/files/state.ts +0 -11
- package/dist/templates/showcase/pages/files/view.dml +0 -35
- package/dist/templates/showcase/pages/filters/controller.ts +0 -28
- package/dist/templates/showcase/pages/filters/state.ts +0 -72
- package/dist/templates/showcase/pages/filters/view.dml +0 -43
- package/dist/templates/showcase/pages/forms/controller.ts +0 -44
- package/dist/templates/showcase/pages/forms/state.ts +0 -15
- package/dist/templates/showcase/pages/forms/view.dml +0 -39
- package/dist/templates/showcase/pages/images/controller.ts +0 -15
- package/dist/templates/showcase/pages/images/view.dml +0 -38
- package/dist/templates/showcase/pages/layout/controller.ts +0 -26
- package/dist/templates/showcase/pages/layout/state.ts +0 -14
- package/dist/templates/showcase/pages/layout/view.dml +0 -80
- package/dist/templates/showcase/pages/lists/controller.ts +0 -36
- package/dist/templates/showcase/pages/lists/state.ts +0 -96
- package/dist/templates/showcase/pages/lists/view.dml +0 -59
- package/dist/templates/showcase/pages/navigation/controller.ts +0 -64
- 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/controller.ts +0 -107
- package/dist/templates/showcase/pages/network/state.ts +0 -15
- package/dist/templates/showcase/pages/network/view.dml +0 -56
- package/dist/templates/showcase/pages/payments/controller.ts +0 -42
- package/dist/templates/showcase/pages/payments/view.dml +0 -35
- package/dist/templates/showcase/pages/state/view.dml +0 -33
- package/dist/templates/showcase/pages/text/controller.ts +0 -10
- package/dist/templates/showcase/pages/text/state.ts +0 -7
- package/dist/templates/showcase/pages/text/view.dml +0 -49
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as findDotRoot } from "./cli.js";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import { CONTROL_FLOW_TAGS, DATA_PREFIX, ROOT_TAG, canHaveChildren, compileView, dataKey, listStatics } from "@monimesl/dot-compiler";
|
|
@@ -5,7 +6,7 @@ import { existsSync } from "node:fs";
|
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
6
7
|
import { parseDmlToIR } from "@monimesl/dot-dml-parser";
|
|
7
8
|
import { ScriptParseError, parseExpression, parseLogicModule, parseStateModule } from "@monimesl/dot-script-parser";
|
|
8
|
-
import { SourceError, builtinRegistry, eventName, eventProp, isEventProp } from "@monimesl/dot-core";
|
|
9
|
+
import { SourceError, builtinRegistry, eventName, eventProp, isEventProp, isWordsOr, otherThanWords } from "@monimesl/dot-core";
|
|
9
10
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
11
|
import ts from "typescript";
|
|
11
12
|
//#region \0rolldown/runtime.js
|
|
@@ -10143,165 +10144,57 @@ function getWellformedEdit(textEdit) {
|
|
|
10143
10144
|
return textEdit;
|
|
10144
10145
|
}
|
|
10145
10146
|
//#endregion
|
|
10146
|
-
//#region src/lsp/
|
|
10147
|
-
const
|
|
10148
|
-
|
|
10149
|
-
|
|
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."
|
|
10150
10153
|
};
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
const owner = shapeOf(expr.object, scope);
|
|
10166
|
-
if (!owner) return;
|
|
10167
|
-
const { property } = expr;
|
|
10168
|
-
if (property.type === "Literal" && typeof property.value === "string") return membersOf(owner)[property.value];
|
|
10169
|
-
return owner.kind === "list" ? owner.item : void 0;
|
|
10170
|
-
}
|
|
10171
|
-
default: return;
|
|
10172
|
-
}
|
|
10173
|
-
}
|
|
10174
|
-
function membersOf(shape) {
|
|
10175
|
-
switch (shape.kind) {
|
|
10176
|
-
case "object": return shape.properties;
|
|
10177
|
-
case "list": return { length: NUMBER };
|
|
10178
|
-
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(", ")}`);
|
|
10179
10168
|
}
|
|
10169
|
+
return lines.join("\n");
|
|
10180
10170
|
}
|
|
10181
|
-
function
|
|
10182
|
-
|
|
10183
|
-
}
|
|
10184
|
-
|
|
10185
|
-
const loops = /* @__PURE__ */ new Map();
|
|
10186
|
-
for (const variable of variables) if (variable.role === "index") loops.set(variable.name, NUMBER);
|
|
10187
|
-
else loops.set(variable.name, itemShape(variable.list, {
|
|
10188
|
-
state,
|
|
10189
|
-
loops
|
|
10190
|
-
}));
|
|
10191
|
-
return {
|
|
10192
|
-
state,
|
|
10193
|
-
loops
|
|
10194
|
-
};
|
|
10195
|
-
}
|
|
10196
|
-
function itemShape(each, scope) {
|
|
10197
|
-
const list = parseBinding(each.replace(/^\s*{{|}}\s*$/g, ""));
|
|
10198
|
-
const shape = list && shapeOf(list, scope);
|
|
10199
|
-
return shape?.kind === "list" ? shape.item : void 0;
|
|
10200
|
-
}
|
|
10201
|
-
function withLoop(scope, item, itemShape, index) {
|
|
10202
|
-
const loops = new Map(scope.loops).set(item, itemShape).set(index, NUMBER);
|
|
10203
|
-
return {
|
|
10204
|
-
state: scope.state,
|
|
10205
|
-
loops
|
|
10206
|
-
};
|
|
10207
|
-
}
|
|
10208
|
-
//#endregion
|
|
10209
|
-
//#region src/lsp/check-members.ts
|
|
10210
|
-
const BINDING = /{{([\s\S]*?)}}/g;
|
|
10211
|
-
/**
|
|
10212
|
-
* Finds the bindings that read a property their value doesn't have, going by the types in state.ts:
|
|
10213
|
-
* `{{ tap.nmae }}` where each tap is a `Tap`. A value whose type isn't known is not checked.
|
|
10214
|
-
*/
|
|
10215
|
-
function checkMembers(text, state) {
|
|
10216
|
-
let nodes;
|
|
10217
|
-
try {
|
|
10218
|
-
nodes = parseDmlToIR(text, {
|
|
10219
|
-
locations: true,
|
|
10220
|
-
whitespace: "preserve"
|
|
10221
|
-
});
|
|
10222
|
-
} catch {
|
|
10223
|
-
return [];
|
|
10224
|
-
}
|
|
10225
|
-
const problems = [];
|
|
10226
|
-
checkNodes(text, nodes, {
|
|
10227
|
-
state,
|
|
10228
|
-
loops: /* @__PURE__ */ new Map()
|
|
10229
|
-
}, problems);
|
|
10230
|
-
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(" | ");
|
|
10231
10175
|
}
|
|
10232
|
-
function
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
checkBindings(text, node.content, from, scope, problems);
|
|
10237
|
-
continue;
|
|
10238
|
-
}
|
|
10239
|
-
for (const value of Object.values(node.attributes)) checkBindings(text, value, from, scope, problems);
|
|
10240
|
-
if (node.tag !== "For") {
|
|
10241
|
-
checkNodes(text, node.children, scope, problems);
|
|
10242
|
-
continue;
|
|
10243
|
-
}
|
|
10244
|
-
const { each = "", item = "item", index = "index", key } = node.attributes;
|
|
10245
|
-
const itemOfList = itemShape(each, scope);
|
|
10246
|
-
if (itemOfList?.kind === "object" && key !== void 0 && !key.startsWith("*") && !(key in itemOfList.properties)) problems.push({
|
|
10247
|
-
message: `<For> \`key\` "${key}" is not a property of \`${itemOfList.type}\` (properties: ${describeMembers(itemOfList)})`,
|
|
10248
|
-
offset: offsetOf(text, `key="${key}"`, from)
|
|
10249
|
-
});
|
|
10250
|
-
checkNodes(text, node.children, withLoop(scope, item, itemOfList, index), problems);
|
|
10251
|
-
}
|
|
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}`;
|
|
10252
10180
|
}
|
|
10253
|
-
function
|
|
10254
|
-
|
|
10255
|
-
const expr = parseBinding(source);
|
|
10256
|
-
if (!expr) continue;
|
|
10257
|
-
const bindingOffset = offsetOf(text, binding, from);
|
|
10258
|
-
for (const { name, owner } of unknownMembers(expr, scope)) problems.push({
|
|
10259
|
-
message: `\`${name}\` is not a property of \`${owner.type}\` (properties: ${describeMembers(owner)})`,
|
|
10260
|
-
offset: memberOffset(bindingOffset, binding, name)
|
|
10261
|
-
});
|
|
10262
|
-
}
|
|
10181
|
+
function typeSuffix(shape) {
|
|
10182
|
+
return shape ? `: \`${shape.type}\`` : "";
|
|
10263
10183
|
}
|
|
10264
|
-
function
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
case "
|
|
10268
|
-
case "
|
|
10269
|
-
case "Member": {
|
|
10270
|
-
const inner = [...found(expr.object), ...found(expr.property)];
|
|
10271
|
-
const { property } = expr;
|
|
10272
|
-
if (property.type !== "Literal" || typeof property.value !== "string") return inner;
|
|
10273
|
-
const owner = shapeOf(expr.object, scope);
|
|
10274
|
-
if (owner && owner.kind !== "value" && !(property.value in membersOf(owner))) return [...inner, {
|
|
10275
|
-
name: property.value,
|
|
10276
|
-
owner
|
|
10277
|
-
}];
|
|
10278
|
-
return inner;
|
|
10279
|
-
}
|
|
10280
|
-
case "Unary": return found(expr.argument);
|
|
10281
|
-
case "Binary": return [...found(expr.left), ...found(expr.right)];
|
|
10282
|
-
case "Conditional": return [
|
|
10283
|
-
...found(expr.test),
|
|
10284
|
-
...found(expr.consequent),
|
|
10285
|
-
...found(expr.alternate)
|
|
10286
|
-
];
|
|
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";
|
|
10287
10189
|
}
|
|
10288
10190
|
}
|
|
10289
|
-
function offsetOf(text, written, from) {
|
|
10290
|
-
const at = text.indexOf(written, from);
|
|
10291
|
-
return at < 0 ? from : at;
|
|
10292
|
-
}
|
|
10293
|
-
function memberOffset(bindingOffset, binding, name) {
|
|
10294
|
-
const escapedName = name.replaceAll("$", "\\$");
|
|
10295
|
-
const match = new RegExp(`(?<=\\.\\s*)${escapedName}(?![\\w$])`).exec(binding);
|
|
10296
|
-
return match ? bindingOffset + match.index : bindingOffset;
|
|
10297
|
-
}
|
|
10298
10191
|
//#endregion
|
|
10299
10192
|
//#region src/lsp/markup.ts
|
|
10300
|
-
const
|
|
10301
|
-
const
|
|
10193
|
+
const TAG_NAME = /[A-Za-z0-9_.:-]/;
|
|
10194
|
+
const ATTRIBUTE_NAME = /[^\s=/>"']/;
|
|
10302
10195
|
const WHITESPACE = /\s/;
|
|
10303
10196
|
function markupContextAt(text, offset) {
|
|
10304
|
-
const
|
|
10197
|
+
const openElements = [];
|
|
10305
10198
|
let index = 0;
|
|
10306
10199
|
while (index < offset) if (text.startsWith("<!--", index)) {
|
|
10307
10200
|
const end = text.indexOf("-->", index + 4);
|
|
@@ -10309,39 +10202,39 @@ function markupContextAt(text, offset) {
|
|
|
10309
10202
|
index = end + 3;
|
|
10310
10203
|
} else if (text.startsWith("</", index)) {
|
|
10311
10204
|
const nameStart = index + 2;
|
|
10312
|
-
const nameEnd = readWhile(text, nameStart,
|
|
10205
|
+
const nameEnd = readWhile(text, nameStart, TAG_NAME);
|
|
10313
10206
|
const close = text.indexOf(">", nameEnd);
|
|
10314
10207
|
if (offset <= nameEnd || close < 0 || close >= offset) return {
|
|
10315
10208
|
kind: "closeTag",
|
|
10316
10209
|
start: nameStart,
|
|
10317
|
-
open:
|
|
10210
|
+
open: openElements.at(-1)
|
|
10318
10211
|
};
|
|
10319
|
-
closeElement(
|
|
10212
|
+
closeElement(openElements, text.slice(nameStart, nameEnd));
|
|
10320
10213
|
index = close + 1;
|
|
10321
10214
|
} else if (text[index] === "<") {
|
|
10322
|
-
const tag = readTag(text, index, offset,
|
|
10215
|
+
const tag = readTag(text, index, offset, openElements);
|
|
10323
10216
|
if (tag.context) return tag.context;
|
|
10324
|
-
if (tag.element && !tag.selfClosing)
|
|
10217
|
+
if (tag.element && !tag.selfClosing) openElements.push(tag.element);
|
|
10325
10218
|
index = tag.end;
|
|
10326
10219
|
} else if (text.startsWith("{{", index)) {
|
|
10327
10220
|
const end = text.indexOf("}}", index + 2);
|
|
10328
10221
|
if (end < 0 || offset <= end) return {
|
|
10329
10222
|
kind: "binding",
|
|
10330
10223
|
start: index + 2,
|
|
10331
|
-
loopVariables: loopVariables(
|
|
10224
|
+
loopVariables: loopVariables(openElements)
|
|
10332
10225
|
};
|
|
10333
10226
|
index = end + 2;
|
|
10334
10227
|
} else index++;
|
|
10335
10228
|
return { kind: "text" };
|
|
10336
10229
|
}
|
|
10337
|
-
function readTag(text, start, offset,
|
|
10230
|
+
function readTag(text, start, offset, openElements) {
|
|
10338
10231
|
const nameStart = start + 1;
|
|
10339
|
-
const nameEnd = readWhile(text, nameStart,
|
|
10232
|
+
const nameEnd = readWhile(text, nameStart, TAG_NAME);
|
|
10340
10233
|
if (offset <= nameEnd) return {
|
|
10341
10234
|
context: {
|
|
10342
10235
|
kind: "tagName",
|
|
10343
10236
|
start: nameStart,
|
|
10344
|
-
parents: [...
|
|
10237
|
+
parents: [...openElements]
|
|
10345
10238
|
},
|
|
10346
10239
|
end: nameEnd
|
|
10347
10240
|
};
|
|
@@ -10376,7 +10269,7 @@ function readTag(text, start, offset, open) {
|
|
|
10376
10269
|
end: index + 1
|
|
10377
10270
|
};
|
|
10378
10271
|
const attributeStart = index;
|
|
10379
|
-
index =
|
|
10272
|
+
index = readWhile(text, index + 1, ATTRIBUTE_NAME);
|
|
10380
10273
|
const name = text.slice(attributeStart, index);
|
|
10381
10274
|
if (offset <= index) return {
|
|
10382
10275
|
context: {
|
|
@@ -10396,7 +10289,7 @@ function readTag(text, start, offset, open) {
|
|
|
10396
10289
|
if (quote === "\"" || quote === "'") {
|
|
10397
10290
|
const close = text.indexOf(quote, valueStart + 1);
|
|
10398
10291
|
if (close < 0 || offset <= close) return {
|
|
10399
|
-
context: valueContext(text.slice(valueStart + 1, offset), valueStart + 1, element, name,
|
|
10292
|
+
context: valueContext(text.slice(valueStart + 1, offset), valueStart + 1, element, name, openElements),
|
|
10400
10293
|
end: offset
|
|
10401
10294
|
};
|
|
10402
10295
|
attributes[name] = text.slice(valueStart + 1, close);
|
|
@@ -10409,7 +10302,7 @@ function readTag(text, start, offset, open) {
|
|
|
10409
10302
|
start: valueStart,
|
|
10410
10303
|
element,
|
|
10411
10304
|
attribute: name,
|
|
10412
|
-
loopVariables: loopVariables(
|
|
10305
|
+
loopVariables: loopVariables(openElements)
|
|
10413
10306
|
},
|
|
10414
10307
|
end: valueEnd
|
|
10415
10308
|
};
|
|
@@ -10418,8 +10311,8 @@ function readTag(text, start, offset, open) {
|
|
|
10418
10311
|
}
|
|
10419
10312
|
}
|
|
10420
10313
|
}
|
|
10421
|
-
function valueContext(valueBeforeCursor, valueStart, element, attribute,
|
|
10422
|
-
const variables = loopVariables(
|
|
10314
|
+
function valueContext(valueBeforeCursor, valueStart, element, attribute, openElements) {
|
|
10315
|
+
const variables = loopVariables(openElements);
|
|
10423
10316
|
const bindingStart = valueBeforeCursor.lastIndexOf("{{");
|
|
10424
10317
|
if (bindingStart >= 0 && !valueBeforeCursor.includes("}}", bindingStart)) return {
|
|
10425
10318
|
kind: "binding",
|
|
@@ -10434,18 +10327,18 @@ function valueContext(valueBeforeCursor, valueStart, element, attribute, open) {
|
|
|
10434
10327
|
loopVariables: variables
|
|
10435
10328
|
};
|
|
10436
10329
|
}
|
|
10437
|
-
function closeElement(
|
|
10438
|
-
for (let index =
|
|
10439
|
-
|
|
10330
|
+
function closeElement(openElements, tag) {
|
|
10331
|
+
for (let index = openElements.length - 1; index >= 0; index--) if (openElements[index].tag === tag) {
|
|
10332
|
+
openElements.length = index;
|
|
10440
10333
|
return;
|
|
10441
10334
|
}
|
|
10442
10335
|
}
|
|
10443
|
-
function loopVariables(
|
|
10336
|
+
function loopVariables(openElements) {
|
|
10444
10337
|
const variables = [];
|
|
10445
|
-
for (const element of
|
|
10338
|
+
for (const element of openElements) {
|
|
10446
10339
|
if (element.tag !== "For") continue;
|
|
10447
10340
|
const { item = "item", index = "index", each = "" } = element.attributes;
|
|
10448
|
-
const list = each
|
|
10341
|
+
const list = bindingSource(each);
|
|
10449
10342
|
variables.push({
|
|
10450
10343
|
name: item,
|
|
10451
10344
|
role: "item",
|
|
@@ -10458,6 +10351,9 @@ function loopVariables(open) {
|
|
|
10458
10351
|
}
|
|
10459
10352
|
return variables;
|
|
10460
10353
|
}
|
|
10354
|
+
function bindingSource(value) {
|
|
10355
|
+
return value.replace(/^\s*{{\s*|\s*}}\s*$/g, "");
|
|
10356
|
+
}
|
|
10461
10357
|
function readWhile(text, index, pattern) {
|
|
10462
10358
|
while (index < text.length && pattern.test(text[index])) index++;
|
|
10463
10359
|
return index;
|
|
@@ -10466,7 +10362,6 @@ function readUntil(text, index, pattern) {
|
|
|
10466
10362
|
while (index < text.length && !pattern.test(text[index])) index++;
|
|
10467
10363
|
return index;
|
|
10468
10364
|
}
|
|
10469
|
-
/** The start and end of the run of `pattern` characters around `offset`. */
|
|
10470
10365
|
function wordAt(text, offset, pattern) {
|
|
10471
10366
|
let start = offset;
|
|
10472
10367
|
while (start > 0 && pattern.test(text[start - 1])) start--;
|
|
@@ -10476,96 +10371,163 @@ function wordAt(text, offset, pattern) {
|
|
|
10476
10371
|
};
|
|
10477
10372
|
}
|
|
10478
10373
|
//#endregion
|
|
10479
|
-
//#region src/lsp/
|
|
10480
|
-
const
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10374
|
+
//#region src/lsp/shapes.ts
|
|
10375
|
+
const NUMBER_SHAPE = {
|
|
10376
|
+
kind: "value",
|
|
10377
|
+
type: "number"
|
|
10378
|
+
};
|
|
10484
10379
|
const MEMBER_CHAIN = /([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*\.\s*$/;
|
|
10485
|
-
|
|
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$]*$/;
|
|
10486
10462
|
const CONTROL_FLOW_SNIPPETS = {
|
|
10487
10463
|
If: "If condition=\"{{ $1 }}\">$0</If>",
|
|
10488
10464
|
ElseIf: "ElseIf condition=\"{{ $1 }}\">$0</ElseIf>",
|
|
10489
10465
|
Else: "Else>$0</Else>",
|
|
10490
10466
|
For: "For each=\"{{ $1 }}\" key=\"$2\">$0</For>"
|
|
10491
10467
|
};
|
|
10492
|
-
|
|
10493
|
-
If: "Shows its children when `condition` is true.",
|
|
10494
|
-
ElseIf: "Follows an `<If>` or `<ElseIf>`: shows its children when the ones before are false and `condition` is true.",
|
|
10495
|
-
Else: "Follows an `<If>` or `<ElseIf>`: shows its children when the ones before are false.",
|
|
10496
|
-
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."
|
|
10497
|
-
};
|
|
10498
|
-
const CONTROL_FLOW_ATTRIBUTE_DOCS = {
|
|
10499
|
-
condition: "A single `{{ }}` binding",
|
|
10500
|
-
each: "The list to repeat, as a single `{{ }}` binding",
|
|
10501
|
-
item: "The name of each item (default `item`)",
|
|
10502
|
-
index: "The name of each position (default `index`)",
|
|
10503
|
-
key: "A property of each item, `*this` or `*index`"
|
|
10504
|
-
};
|
|
10505
|
-
function complete(document, offset, page, { components }) {
|
|
10468
|
+
function complete(document, offset, page, components) {
|
|
10506
10469
|
const text = document.getText();
|
|
10507
10470
|
const context = markupContextAt(text, offset);
|
|
10508
|
-
const range = (start) => ({
|
|
10509
|
-
start: document.positionAt(start),
|
|
10510
|
-
end: document.positionAt(offset)
|
|
10511
|
-
});
|
|
10512
10471
|
switch (context.kind) {
|
|
10513
|
-
case "tagName":
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
newText: item.insertText ?? item.label
|
|
10518
|
-
}
|
|
10519
|
-
}));
|
|
10472
|
+
case "tagName": {
|
|
10473
|
+
const items = tagCompletions(context.parents, components);
|
|
10474
|
+
return replacingFrom(document, context.start, offset, items);
|
|
10475
|
+
}
|
|
10520
10476
|
case "closeTag":
|
|
10521
10477
|
if (!context.open) return [];
|
|
10522
10478
|
return [{
|
|
10523
10479
|
label: `/${context.open.tag}`,
|
|
10524
10480
|
kind: import_main.CompletionItemKind.Property,
|
|
10525
10481
|
textEdit: {
|
|
10526
|
-
range:
|
|
10482
|
+
range: rangeFrom(document, context.start, offset),
|
|
10527
10483
|
newText: `${context.open.tag}>`
|
|
10528
10484
|
}
|
|
10529
10485
|
}];
|
|
10530
|
-
case "attributeName":
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
newText: item.insertText ?? item.label
|
|
10535
|
-
}
|
|
10536
|
-
}));
|
|
10486
|
+
case "attributeName": {
|
|
10487
|
+
const items = attributeCompletions(context.element, components);
|
|
10488
|
+
return replacingFrom(document, context.start, offset, items);
|
|
10489
|
+
}
|
|
10537
10490
|
case "attributeValue": {
|
|
10538
|
-
const scope =
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
textEdit: {
|
|
10542
|
-
range: range(context.start),
|
|
10543
|
-
newText: item.label
|
|
10544
|
-
}
|
|
10545
|
-
}));
|
|
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);
|
|
10546
10494
|
}
|
|
10547
10495
|
case "binding": {
|
|
10548
10496
|
const typed = text.slice(context.start, offset);
|
|
10549
|
-
const wordStart = offset - (
|
|
10550
|
-
const scope =
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
newText: item.label
|
|
10556
|
-
}
|
|
10557
|
-
}));
|
|
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);
|
|
10558
10503
|
}
|
|
10559
10504
|
default: return [];
|
|
10560
10505
|
}
|
|
10561
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
|
+
}
|
|
10562
10522
|
function tagCompletions(parents, components) {
|
|
10563
10523
|
if (parents.length === 0) {
|
|
10564
10524
|
const root = components[ROOT_TAG];
|
|
10565
|
-
|
|
10525
|
+
if (!root) return [];
|
|
10526
|
+
return [componentItem(root, "0")];
|
|
10566
10527
|
}
|
|
10567
10528
|
const parent = [...parents].reverse().find((element) => !(element.tag in CONTROL_FLOW_TAGS));
|
|
10568
|
-
|
|
10529
|
+
let parentManifest;
|
|
10530
|
+
if (parent) parentManifest = components[parent.tag];
|
|
10569
10531
|
const items = Object.values(components).filter((component) => !parentManifest || canHaveChildren(parentManifest, component)).map((component) => componentItem(component, "1"));
|
|
10570
10532
|
for (const [tag, snippet] of Object.entries(CONTROL_FLOW_SNIPPETS)) items.push({
|
|
10571
10533
|
label: tag,
|
|
@@ -10582,7 +10544,8 @@ function tagCompletions(parents, components) {
|
|
|
10582
10544
|
return items;
|
|
10583
10545
|
}
|
|
10584
10546
|
function componentItem(component, sortGroup) {
|
|
10585
|
-
|
|
10547
|
+
let snippet = `${component.name}$1>$0</${component.name}>`;
|
|
10548
|
+
if (component.children === "none") snippet = `${component.name}$1 />`;
|
|
10586
10549
|
return {
|
|
10587
10550
|
label: component.name,
|
|
10588
10551
|
kind: import_main.CompletionItemKind.Class,
|
|
@@ -10597,18 +10560,18 @@ function componentItem(component, sortGroup) {
|
|
|
10597
10560
|
};
|
|
10598
10561
|
}
|
|
10599
10562
|
function attributeCompletions(element, components) {
|
|
10600
|
-
const
|
|
10563
|
+
const isWritten = (name) => name in element.attributes;
|
|
10601
10564
|
const controlFlow = CONTROL_FLOW_TAGS[element.tag];
|
|
10602
|
-
if (controlFlow) return controlFlow.attributes.filter((name) => !
|
|
10565
|
+
if (controlFlow) return controlFlow.attributes.filter((name) => !isWritten(name)).map((name) => ({
|
|
10603
10566
|
label: name,
|
|
10604
10567
|
kind: import_main.CompletionItemKind.Property,
|
|
10605
10568
|
detail: CONTROL_FLOW_ATTRIBUTE_DOCS[name] ?? name,
|
|
10606
|
-
insertText: name
|
|
10569
|
+
insertText: controlFlowAttributeSnippet(name),
|
|
10607
10570
|
insertTextFormat: import_main.InsertTextFormat.Snippet
|
|
10608
10571
|
}));
|
|
10609
10572
|
const component = components[element.tag];
|
|
10610
10573
|
if (!component) return [];
|
|
10611
|
-
const props = Object.entries(component.props).filter(([name]) => !
|
|
10574
|
+
const props = Object.entries(component.props).filter(([name]) => !isWritten(name)).map(([name, type]) => ({
|
|
10612
10575
|
label: name,
|
|
10613
10576
|
kind: import_main.CompletionItemKind.Property,
|
|
10614
10577
|
detail: describeType(type),
|
|
@@ -10616,7 +10579,7 @@ function attributeCompletions(element, components) {
|
|
|
10616
10579
|
insertTextFormat: import_main.InsertTextFormat.Snippet,
|
|
10617
10580
|
sortText: `0${name}`
|
|
10618
10581
|
}));
|
|
10619
|
-
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) => ({
|
|
10620
10583
|
label: name,
|
|
10621
10584
|
kind: import_main.CompletionItemKind.Event,
|
|
10622
10585
|
detail: "a mutation or action in controller.ts",
|
|
@@ -10626,27 +10589,20 @@ function attributeCompletions(element, components) {
|
|
|
10626
10589
|
}));
|
|
10627
10590
|
return [...props, ...events];
|
|
10628
10591
|
}
|
|
10592
|
+
function controlFlowAttributeSnippet(name) {
|
|
10593
|
+
if (name === "condition" || name === "each") return `${name}="{{ $1 }}"`;
|
|
10594
|
+
return `${name}="$1"`;
|
|
10595
|
+
}
|
|
10629
10596
|
function valueCompletions(element, attribute, page, scope, components) {
|
|
10630
|
-
if (element.tag === "For" && attribute === "key")
|
|
10631
|
-
const item = scope && itemShape(element.attributes.each ?? "", scope);
|
|
10632
|
-
return [
|
|
10633
|
-
...item?.kind === "object" ? memberCompletions(item) : [],
|
|
10634
|
-
{
|
|
10635
|
-
label: "*this",
|
|
10636
|
-
kind: import_main.CompletionItemKind.Value,
|
|
10637
|
-
detail: "the item itself"
|
|
10638
|
-
},
|
|
10639
|
-
{
|
|
10640
|
-
label: "*index",
|
|
10641
|
-
kind: import_main.CompletionItemKind.Value,
|
|
10642
|
-
detail: "match items by position"
|
|
10643
|
-
}
|
|
10644
|
-
];
|
|
10645
|
-
}
|
|
10597
|
+
if (element.tag === "For" && attribute === "key") return forKeyCompletions(element, scope);
|
|
10646
10598
|
const component = components[element.tag];
|
|
10647
10599
|
if (!component) return [];
|
|
10648
|
-
if (isEventProp(attribute) && component.events.includes(eventName(attribute))) return
|
|
10600
|
+
if (isEventProp(attribute) && component.events.includes(eventName(attribute))) return handlerCompletions(page);
|
|
10649
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
|
+
}));
|
|
10650
10606
|
if (Array.isArray(type)) return type.map((value) => ({
|
|
10651
10607
|
label: value,
|
|
10652
10608
|
kind: import_main.CompletionItemKind.EnumMember
|
|
@@ -10661,27 +10617,52 @@ function valueCompletions(element, attribute, page, scope, components) {
|
|
|
10661
10617
|
}));
|
|
10662
10618
|
return [];
|
|
10663
10619
|
}
|
|
10664
|
-
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) {
|
|
10665
10641
|
const { controller } = page;
|
|
10666
10642
|
if (!controller) return [];
|
|
10667
|
-
|
|
10643
|
+
const mutations = (controller.mutations ?? []).map((name) => ({
|
|
10668
10644
|
label: name,
|
|
10669
10645
|
kind: import_main.CompletionItemKind.Method,
|
|
10670
10646
|
detail: "mutation"
|
|
10671
|
-
}))
|
|
10647
|
+
}));
|
|
10648
|
+
const actions = (controller.actions ?? []).map((name) => ({
|
|
10672
10649
|
label: name,
|
|
10673
10650
|
kind: import_main.CompletionItemKind.Function,
|
|
10674
10651
|
detail: "action"
|
|
10675
|
-
}))
|
|
10652
|
+
}));
|
|
10653
|
+
return [...mutations, ...actions];
|
|
10676
10654
|
}
|
|
10677
10655
|
function bindingCompletions(loopVariables, page, scope) {
|
|
10678
10656
|
const items = /* @__PURE__ */ new Map();
|
|
10679
|
-
for (const key of page.state?.keys ?? [])
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
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
|
+
}
|
|
10685
10666
|
for (const variable of loopVariables) items.set(variable.name, {
|
|
10686
10667
|
label: variable.name,
|
|
10687
10668
|
kind: import_main.CompletionItemKind.Variable,
|
|
@@ -10698,232 +10679,109 @@ function memberCompletions(owner) {
|
|
|
10698
10679
|
detail: shape.type
|
|
10699
10680
|
}));
|
|
10700
10681
|
}
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
function hover(document, offset, page, { components }) {
|
|
10714
|
-
const found = symbolAt(document.getText(), offset, page);
|
|
10715
|
-
if (!found) return null;
|
|
10716
|
-
const markdown = describeSymbol(found.symbol, page, components);
|
|
10717
|
-
if (!markdown) return null;
|
|
10718
|
-
return {
|
|
10719
|
-
contents: {
|
|
10720
|
-
kind: import_main.MarkupKind.Markdown,
|
|
10721
|
-
value: markdown
|
|
10722
|
-
},
|
|
10723
|
-
range: {
|
|
10724
|
-
start: document.positionAt(found.start),
|
|
10725
|
-
end: document.positionAt(found.end)
|
|
10726
|
-
}
|
|
10727
|
-
};
|
|
10728
|
-
}
|
|
10729
|
-
function definition(document, offset, page) {
|
|
10730
|
-
const found = symbolAt(document.getText(), offset, page);
|
|
10731
|
-
if (!found) return null;
|
|
10732
|
-
const { symbol } = found;
|
|
10733
|
-
if (symbol.kind === "handler" && page.controller) return locationIn(page.controller.file, page.controller.text, page.controller.offsets[symbol.name]);
|
|
10734
|
-
if (symbol.kind === "state" && page.state) return locationIn(page.state.file, page.state.text, page.state.offsets[symbol.name]);
|
|
10735
|
-
if (symbol.kind === "property" && symbol.shape.declaration) {
|
|
10736
|
-
const { file, line, character } = symbol.shape.declaration;
|
|
10737
|
-
const position = {
|
|
10738
|
-
line,
|
|
10739
|
-
character
|
|
10740
|
-
};
|
|
10741
|
-
return {
|
|
10742
|
-
uri: pathToFileURL(file).href,
|
|
10743
|
-
range: {
|
|
10744
|
-
start: position,
|
|
10745
|
-
end: position
|
|
10746
|
-
}
|
|
10747
|
-
};
|
|
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 [];
|
|
10748
10694
|
}
|
|
10749
|
-
|
|
10750
|
-
|
|
10695
|
+
const problems = [];
|
|
10696
|
+
checkNodes(text, nodes, {
|
|
10697
|
+
state,
|
|
10698
|
+
loops: /* @__PURE__ */ new Map()
|
|
10699
|
+
}, problems);
|
|
10700
|
+
return problems;
|
|
10751
10701
|
}
|
|
10752
|
-
function
|
|
10753
|
-
const
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
return {
|
|
10759
|
-
symbol: {
|
|
10760
|
-
kind: "component",
|
|
10761
|
-
tag: text.slice(start, end)
|
|
10762
|
-
},
|
|
10763
|
-
start,
|
|
10764
|
-
end
|
|
10765
|
-
};
|
|
10766
|
-
}
|
|
10767
|
-
case "attributeName": {
|
|
10768
|
-
const { start, end } = wordAt(text, offset, ATTRIBUTE_NAME);
|
|
10769
|
-
const name = text.slice(start, end);
|
|
10770
|
-
return name ? {
|
|
10771
|
-
symbol: {
|
|
10772
|
-
kind: "attribute",
|
|
10773
|
-
tag: context.element.tag,
|
|
10774
|
-
name
|
|
10775
|
-
},
|
|
10776
|
-
start,
|
|
10777
|
-
end
|
|
10778
|
-
} : null;
|
|
10779
|
-
}
|
|
10780
|
-
case "attributeValue": {
|
|
10781
|
-
const { start, end } = wordAt(text, offset, VALUE);
|
|
10782
|
-
const value = text.slice(start, end);
|
|
10783
|
-
if (isEventProp(context.attribute)) return {
|
|
10784
|
-
symbol: {
|
|
10785
|
-
kind: "handler",
|
|
10786
|
-
name: value
|
|
10787
|
-
},
|
|
10788
|
-
start,
|
|
10789
|
-
end
|
|
10790
|
-
};
|
|
10791
|
-
if (page.statics?.includes(value)) return {
|
|
10792
|
-
symbol: {
|
|
10793
|
-
kind: "static",
|
|
10794
|
-
path: value
|
|
10795
|
-
},
|
|
10796
|
-
start,
|
|
10797
|
-
end
|
|
10798
|
-
};
|
|
10799
|
-
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;
|
|
10800
10708
|
}
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
const scope = shapeScope(context.loopVariables, page);
|
|
10806
|
-
if (isMember(text, context.start, start)) {
|
|
10807
|
-
const owner = ownerOf(text, context.start, start, scope);
|
|
10808
|
-
const shape = owner && membersOf(owner)[name];
|
|
10809
|
-
return owner && shape ? {
|
|
10810
|
-
symbol: {
|
|
10811
|
-
kind: "property",
|
|
10812
|
-
name,
|
|
10813
|
-
owner,
|
|
10814
|
-
shape
|
|
10815
|
-
},
|
|
10816
|
-
start,
|
|
10817
|
-
end
|
|
10818
|
-
} : null;
|
|
10819
|
-
}
|
|
10820
|
-
const variable = [...context.loopVariables].reverse().find((candidate) => candidate.name === name);
|
|
10821
|
-
if (variable) {
|
|
10822
|
-
const shape = scope?.loops.get(name);
|
|
10823
|
-
return {
|
|
10824
|
-
symbol: shape ? {
|
|
10825
|
-
kind: "loopVariable",
|
|
10826
|
-
variable,
|
|
10827
|
-
shape
|
|
10828
|
-
} : {
|
|
10829
|
-
kind: "loopVariable",
|
|
10830
|
-
variable
|
|
10831
|
-
},
|
|
10832
|
-
start,
|
|
10833
|
-
end
|
|
10834
|
-
};
|
|
10835
|
-
}
|
|
10836
|
-
return {
|
|
10837
|
-
symbol: {
|
|
10838
|
-
kind: "state",
|
|
10839
|
-
name
|
|
10840
|
-
},
|
|
10841
|
-
start,
|
|
10842
|
-
end
|
|
10843
|
-
};
|
|
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;
|
|
10844
10713
|
}
|
|
10845
|
-
|
|
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);
|
|
10846
10721
|
}
|
|
10847
10722
|
}
|
|
10848
|
-
function
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
if (CONTROL_FLOW_TAGS[symbol.tag]) {
|
|
10858
|
-
const doc = CONTROL_FLOW_ATTRIBUTE_DOCS[symbol.name];
|
|
10859
|
-
return doc ? `\`${symbol.name}\`: ${doc}` : null;
|
|
10860
|
-
}
|
|
10861
|
-
const component = components[symbol.tag];
|
|
10862
|
-
if (!component) return null;
|
|
10863
|
-
const type = component.props[symbol.name];
|
|
10864
|
-
if (type) return `\`${symbol.name}\`: ${describeType(type)}`;
|
|
10865
|
-
if (symbol.name.startsWith(DATA_PREFIX)) return `\`${symbol.name}\`: reaches this element's handlers as \`event.data.${dataKey(symbol.name)}\``;
|
|
10866
|
-
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)}\``;
|
|
10867
|
-
return null;
|
|
10868
|
-
}
|
|
10869
|
-
case "handler": {
|
|
10870
|
-
const { controller } = page;
|
|
10871
|
-
if (controller?.mutations?.includes(symbol.name)) return `mutation \`${symbol.name}\` in controller.ts`;
|
|
10872
|
-
if (controller?.actions?.includes(symbol.name)) return `action \`${symbol.name}\` in controller.ts`;
|
|
10873
|
-
return null;
|
|
10874
|
-
}
|
|
10875
|
-
case "state": return page.state?.keys?.includes(symbol.name) ? `state \`${symbol.name}\` in state.ts${typeSuffix(page.state.shapes?.[symbol.name])}` : null;
|
|
10876
|
-
case "loopVariable": return `\`${symbol.variable.name}\`: ${describeLoopVariable(symbol.variable, symbol.shape)}`;
|
|
10877
|
-
case "property": return `\`${symbol.name}\`: \`${symbol.shape.type}\`, a property of \`${symbol.owner.type}\``;
|
|
10878
|
-
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
|
+
});
|
|
10879
10732
|
}
|
|
10880
10733
|
}
|
|
10881
|
-
function
|
|
10882
|
-
const
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
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
|
+
];
|
|
10893
10757
|
}
|
|
10894
10758
|
}
|
|
10895
|
-
function
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
function describeLoopVariable(variable, shape) {
|
|
10900
|
-
const list = variable.list ? ` \`${variable.list}\`` : "";
|
|
10901
|
-
return variable.role === "item" ? `each item of${list}${typeSuffix(shape)}` : `the position in${list}`;
|
|
10902
|
-
}
|
|
10903
|
-
function typeSuffix(shape) {
|
|
10904
|
-
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;
|
|
10905
10763
|
}
|
|
10906
|
-
function
|
|
10907
|
-
|
|
10908
|
-
const
|
|
10909
|
-
return
|
|
10910
|
-
|
|
10911
|
-
range: {
|
|
10912
|
-
start: position,
|
|
10913
|
-
end: position
|
|
10914
|
-
}
|
|
10915
|
-
};
|
|
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;
|
|
10916
10769
|
}
|
|
10917
|
-
|
|
10770
|
+
//#endregion
|
|
10771
|
+
//#region src/lsp/diagnostics.ts
|
|
10772
|
+
function diagnose(document, file, page, components) {
|
|
10918
10773
|
const diagnostics = [];
|
|
10919
10774
|
const context = {
|
|
10920
10775
|
file,
|
|
10921
10776
|
components,
|
|
10922
|
-
onWarning: (warning) =>
|
|
10777
|
+
onWarning: (warning) => {
|
|
10778
|
+
diagnostics.push(sourceDiagnostic(document, warning, import_main.DiagnosticSeverity.Warning));
|
|
10779
|
+
}
|
|
10923
10780
|
};
|
|
10924
10781
|
if (page.statics) context.statics = page.statics;
|
|
10925
10782
|
if (page.state?.keys) context.stateKeys = page.state.keys;
|
|
10926
|
-
const
|
|
10783
|
+
const mutations = page.controller?.mutations;
|
|
10784
|
+
const actions = page.controller?.actions;
|
|
10927
10785
|
if (mutations && actions) context.handlers = [...mutations, ...actions];
|
|
10928
10786
|
try {
|
|
10929
10787
|
compileView(document.getText(), context);
|
|
@@ -10939,15 +10797,15 @@ function sourceDiagnostic(document, problem, severity) {
|
|
|
10939
10797
|
}
|
|
10940
10798
|
function diagnosticAt(document, start, message, severity) {
|
|
10941
10799
|
const text = document.getText();
|
|
10942
|
-
|
|
10943
|
-
|
|
10800
|
+
const end = readUntil(text, start, /[\s>]/);
|
|
10801
|
+
const atLeastOneCharacter = Math.min(start + 1, text.length);
|
|
10944
10802
|
return {
|
|
10945
10803
|
severity,
|
|
10946
10804
|
source: "dot",
|
|
10947
10805
|
message,
|
|
10948
10806
|
range: {
|
|
10949
10807
|
start: document.positionAt(start),
|
|
10950
|
-
end: document.positionAt(Math.max(end,
|
|
10808
|
+
end: document.positionAt(Math.max(end, atLeastOneCharacter))
|
|
10951
10809
|
}
|
|
10952
10810
|
};
|
|
10953
10811
|
}
|
|
@@ -10955,7 +10813,7 @@ function diagnosticAt(document, start, message, severity) {
|
|
|
10955
10813
|
//#region src/lsp/page.ts
|
|
10956
10814
|
async function readPage(viewFile, readText, stateTypes) {
|
|
10957
10815
|
const dir = dirname(viewFile);
|
|
10958
|
-
const root =
|
|
10816
|
+
const root = findDotRoot(dir);
|
|
10959
10817
|
const [state, controller, statics] = await Promise.all([
|
|
10960
10818
|
readState(join(dir, "state.ts"), root, readText, stateTypes),
|
|
10961
10819
|
readController(join(dir, "controller.ts"), readText),
|
|
@@ -10971,20 +10829,18 @@ async function readPage(viewFile, readText, stateTypes) {
|
|
|
10971
10829
|
async function readState(file, root, readText, stateTypes) {
|
|
10972
10830
|
const text = await readText(file);
|
|
10973
10831
|
if (text === void 0) return;
|
|
10974
|
-
let keys;
|
|
10975
|
-
let offsets;
|
|
10976
10832
|
try {
|
|
10977
|
-
|
|
10833
|
+
const { keys, keyOffsets } = parseStateModule(text, file);
|
|
10834
|
+
return {
|
|
10835
|
+
file,
|
|
10836
|
+
text,
|
|
10837
|
+
keys,
|
|
10838
|
+
offsets: keyOffsets,
|
|
10839
|
+
shapes: readShapes(stateTypes, root, file, text)
|
|
10840
|
+
};
|
|
10978
10841
|
} catch {
|
|
10979
10842
|
return;
|
|
10980
10843
|
}
|
|
10981
|
-
return {
|
|
10982
|
-
file,
|
|
10983
|
-
text,
|
|
10984
|
-
keys,
|
|
10985
|
-
offsets,
|
|
10986
|
-
shapes: readShapes(stateTypes, root, file, text)
|
|
10987
|
-
};
|
|
10988
10844
|
}
|
|
10989
10845
|
function readShapes(stateTypes, root, file, text) {
|
|
10990
10846
|
try {
|
|
@@ -11009,14 +10865,6 @@ async function readController(file, readText) {
|
|
|
11009
10865
|
return;
|
|
11010
10866
|
}
|
|
11011
10867
|
}
|
|
11012
|
-
function findRoot(dir) {
|
|
11013
|
-
while (true) {
|
|
11014
|
-
if (existsSync(join(dir, "dot.json"))) return dir;
|
|
11015
|
-
const parent = dirname(dir);
|
|
11016
|
-
if (parent === dir) return;
|
|
11017
|
-
dir = parent;
|
|
11018
|
-
}
|
|
11019
|
-
}
|
|
11020
10868
|
//#endregion
|
|
11021
10869
|
//#region src/lsp/state-types.ts
|
|
11022
10870
|
const MAX_DEPTH = 8;
|
|
@@ -11028,23 +10876,19 @@ const FALLBACK_OPTIONS = {
|
|
|
11028
10876
|
noEmit: true,
|
|
11029
10877
|
skipLibCheck: true
|
|
11030
10878
|
};
|
|
11031
|
-
/**
|
|
11032
|
-
* Reads the shapes of state.ts files. Each dot keeps one TypeScript language service, so reading
|
|
11033
|
-
* again after an edit rechecks only what changed.
|
|
11034
|
-
*/
|
|
11035
10879
|
var StateTypes = class {
|
|
11036
10880
|
#projects = /* @__PURE__ */ new Map();
|
|
11037
10881
|
read(root, file, text) {
|
|
11038
10882
|
const key = root ?? file;
|
|
11039
10883
|
let project = this.#projects.get(key);
|
|
11040
10884
|
if (!project) {
|
|
11041
|
-
project = new
|
|
10885
|
+
project = new TypeScriptProject(root);
|
|
11042
10886
|
this.#projects.set(key, project);
|
|
11043
10887
|
}
|
|
11044
10888
|
return project.read(file, text);
|
|
11045
10889
|
}
|
|
11046
10890
|
};
|
|
11047
|
-
var
|
|
10891
|
+
var TypeScriptProject = class {
|
|
11048
10892
|
#options;
|
|
11049
10893
|
#fileNames;
|
|
11050
10894
|
#open = /* @__PURE__ */ new Map();
|
|
@@ -11063,7 +10907,8 @@ var Project = class {
|
|
|
11063
10907
|
},
|
|
11064
10908
|
getScriptSnapshot: (file) => {
|
|
11065
10909
|
const text = this.#open.get(file)?.text ?? ts.sys.readFile(file);
|
|
11066
|
-
|
|
10910
|
+
if (text === void 0) return;
|
|
10911
|
+
return ts.ScriptSnapshot.fromString(text);
|
|
11067
10912
|
},
|
|
11068
10913
|
getCurrentDirectory: () => root ?? process.cwd(),
|
|
11069
10914
|
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
@@ -11087,7 +10932,8 @@ var Project = class {
|
|
|
11087
10932
|
if (!program || !source) return;
|
|
11088
10933
|
const checker = program.getTypeChecker();
|
|
11089
10934
|
const module = checker.getSymbolAtLocation(source);
|
|
11090
|
-
|
|
10935
|
+
if (!module) return;
|
|
10936
|
+
let exported = checker.tryGetMemberInModuleExports("default", module);
|
|
11091
10937
|
if (!exported) return;
|
|
11092
10938
|
if (exported.flags & ts.SymbolFlags.Alias) exported = checker.getAliasedSymbol(exported);
|
|
11093
10939
|
const state = checker.getTypeOfSymbol(exported);
|
|
@@ -11099,9 +10945,9 @@ var Project = class {
|
|
|
11099
10945
|
function readConfig(root) {
|
|
11100
10946
|
const file = join(root, "tsconfig.json");
|
|
11101
10947
|
if (!existsSync(file)) return;
|
|
11102
|
-
const
|
|
11103
|
-
if (
|
|
11104
|
-
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);
|
|
11105
10951
|
}
|
|
11106
10952
|
function propertyShape(checker, property, depth, seen) {
|
|
11107
10953
|
const shape = typeShape(checker, checker.getTypeOfSymbol(property), depth, seen);
|
|
@@ -11128,13 +10974,15 @@ function typeShape(checker, type, depth, seen) {
|
|
|
11128
10974
|
const inner = new Set(seen).add(present);
|
|
11129
10975
|
if (checker.isArrayType(present) || checker.isTupleType(present)) {
|
|
11130
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);
|
|
11131
10982
|
return {
|
|
11132
10983
|
kind: "list",
|
|
11133
10984
|
type: name,
|
|
11134
|
-
item:
|
|
11135
|
-
kind: "value",
|
|
11136
|
-
type: "unknown"
|
|
11137
|
-
}
|
|
10985
|
+
item: itemShape
|
|
11138
10986
|
};
|
|
11139
10987
|
}
|
|
11140
10988
|
if (!isRecord(checker, present)) return {
|
|
@@ -11153,45 +11001,231 @@ function isRecord(checker, type) {
|
|
|
11153
11001
|
return (type.isUnion() ? type.types : [type]).every((member) => (member.flags & ts.TypeFlags.Object) !== 0 && member.getCallSignatures().length === 0 && checker.getIndexInfosOfType(member).length === 0);
|
|
11154
11002
|
}
|
|
11155
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
|
|
11156
11194
|
//#region src/lsp/server.ts
|
|
11157
|
-
const
|
|
11195
|
+
const WATCHED_FILES = [
|
|
11158
11196
|
"**/*.ts",
|
|
11159
11197
|
"**/dot.json",
|
|
11160
11198
|
"**/statics/**"
|
|
11161
11199
|
];
|
|
11162
|
-
/**
|
|
11163
|
-
* Serves .dml files over the Language Server Protocol: completion, hover, go to definition and
|
|
11164
|
-
* the compiler's own errors and warnings. It runs from the dot's own installed `dotctl`, so what
|
|
11165
|
-
* it knows always matches the Dot version the dot builds with.
|
|
11166
|
-
*/
|
|
11167
11200
|
function startLanguageServer(connection) {
|
|
11168
11201
|
const documents = new import_main.TextDocuments(TextDocument);
|
|
11169
|
-
const
|
|
11202
|
+
const components = builtinRegistry;
|
|
11170
11203
|
const stateTypes = new StateTypes();
|
|
11171
11204
|
let canWatchFiles = false;
|
|
11172
11205
|
const readText = async (file) => {
|
|
11173
|
-
const
|
|
11174
|
-
if (
|
|
11175
|
-
|
|
11206
|
+
const openDocument = documents.get(pathToFileURL(file).href);
|
|
11207
|
+
if (openDocument) return openDocument.getText();
|
|
11208
|
+
if (!existsSync(file)) return;
|
|
11209
|
+
return readFile(file, "utf8");
|
|
11176
11210
|
};
|
|
11177
|
-
const
|
|
11211
|
+
const openView = async (uri) => {
|
|
11178
11212
|
const document = documents.get(uri);
|
|
11179
11213
|
if (!document || !uri.endsWith(".dml")) return;
|
|
11180
11214
|
const file = fileURLToPath(uri);
|
|
11181
11215
|
return {
|
|
11182
11216
|
document,
|
|
11183
|
-
|
|
11184
|
-
file
|
|
11217
|
+
file,
|
|
11218
|
+
page: await readPage(file, readText, stateTypes)
|
|
11185
11219
|
};
|
|
11186
11220
|
};
|
|
11187
11221
|
const validate = async (uri) => {
|
|
11188
11222
|
try {
|
|
11189
|
-
const
|
|
11190
|
-
if (!
|
|
11191
|
-
const diagnostics = diagnose(
|
|
11223
|
+
const view = await openView(uri);
|
|
11224
|
+
if (!view) return;
|
|
11225
|
+
const diagnostics = diagnose(view.document, view.file, view.page, components);
|
|
11192
11226
|
await connection.sendDiagnostics({
|
|
11193
11227
|
uri,
|
|
11194
|
-
version:
|
|
11228
|
+
version: view.document.version,
|
|
11195
11229
|
diagnostics
|
|
11196
11230
|
});
|
|
11197
11231
|
} catch (error) {
|
|
@@ -11220,10 +11254,14 @@ function startLanguageServer(connection) {
|
|
|
11220
11254
|
};
|
|
11221
11255
|
});
|
|
11222
11256
|
connection.onInitialized(() => {
|
|
11223
|
-
if (canWatchFiles)
|
|
11257
|
+
if (canWatchFiles) {
|
|
11258
|
+
const watchers = WATCHED_FILES.map((globPattern) => ({ globPattern }));
|
|
11259
|
+
connection.client.register(import_main.DidChangeWatchedFilesNotification.type, { watchers });
|
|
11260
|
+
}
|
|
11224
11261
|
});
|
|
11225
11262
|
documents.onDidChangeContent(({ document }) => {
|
|
11226
|
-
document.uri.endsWith(".dml")
|
|
11263
|
+
if (document.uri.endsWith(".dml")) validate(document.uri);
|
|
11264
|
+
else validateAll();
|
|
11227
11265
|
});
|
|
11228
11266
|
documents.onDidClose(({ document }) => {
|
|
11229
11267
|
connection.sendDiagnostics({
|
|
@@ -11234,30 +11272,37 @@ function startLanguageServer(connection) {
|
|
|
11234
11272
|
connection.onDidChangeWatchedFiles(() => {
|
|
11235
11273
|
validateAll();
|
|
11236
11274
|
});
|
|
11237
|
-
|
|
11238
|
-
const
|
|
11239
|
-
if (!
|
|
11240
|
-
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);
|
|
11241
11287
|
});
|
|
11242
|
-
connection.onHover(async (
|
|
11243
|
-
const
|
|
11244
|
-
if (!
|
|
11245
|
-
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);
|
|
11246
11292
|
});
|
|
11247
|
-
connection.onDefinition(async (
|
|
11248
|
-
const
|
|
11249
|
-
if (!
|
|
11250
|
-
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);
|
|
11251
11297
|
});
|
|
11252
11298
|
documents.listen(connection);
|
|
11253
11299
|
connection.listen();
|
|
11254
11300
|
return connection;
|
|
11255
11301
|
}
|
|
11256
|
-
/** Serves an editor that started `dotctl lsp`, over stdin and stdout. */
|
|
11257
11302
|
function serveStdio() {
|
|
11258
11303
|
return startLanguageServer((0, import_node.createConnection)(import_node.ProposedFeatures.all, process.stdin, process.stdout));
|
|
11259
11304
|
}
|
|
11260
11305
|
//#endregion
|
|
11261
11306
|
export { serveStdio };
|
|
11262
11307
|
|
|
11263
|
-
//# sourceMappingURL=server-
|
|
11308
|
+
//# sourceMappingURL=server-DMnWDdJ6.js.map
|