@monimesl/dot-cli 0.1.0-alpha.3 → 0.1.0-alpha.31
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 +91 -17
- package/dist/cli.js +776 -199
- package/dist/cli.js.map +1 -1
- package/dist/{server-z7M4FLjf.js → server-DMnWDdJ6.js} +702 -280
- package/dist/server-DMnWDdJ6.js.map +1 -0
- package/dist/templates/showcase/.env +3 -0
- package/dist/templates/showcase/dot.ts +60 -0
- 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/{template/pages → templates/showcase/pages/basics}/state/controller.ts +1 -0
- package/dist/{template/pages → templates/showcase/pages/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/{template/pages → templates/showcase/pages/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/components/money/state.ts +13 -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/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/{template → templates/showcase}/pages/index/controller.ts +1 -1
- package/dist/templates/showcase/pages/index/state.ts +237 -0
- package/dist/templates/showcase/pages/index/view.dml +23 -0
- package/dist/templates/showcase/statics/logo.png +0 -0
- package/dist/{template → templates/starter}/.env +1 -1
- package/dist/templates/starter/dot.ts +3 -0
- package/dist/templates/starter/pages/index/controller.ts +9 -0
- package/dist/templates/starter/pages/index/state.ts +4 -0
- package/dist/templates/starter/pages/index/view.dml +16 -0
- package/package.json +8 -7
- package/dist/server-z7M4FLjf.js.map +0 -1
- package/dist/template/dot.ts +0 -19
- package/dist/template/pages/device/controller.ts +0 -70
- package/dist/template/pages/device/state.ts +0 -7
- package/dist/template/pages/device/view.dml +0 -44
- package/dist/template/pages/feedback/controller.ts +0 -16
- package/dist/template/pages/feedback/state.ts +0 -7
- package/dist/template/pages/feedback/view.dml +0 -87
- package/dist/template/pages/files/controller.ts +0 -47
- package/dist/template/pages/files/state.ts +0 -11
- package/dist/template/pages/files/view.dml +0 -35
- package/dist/template/pages/forms/controller.ts +0 -44
- package/dist/template/pages/forms/state.ts +0 -15
- package/dist/template/pages/forms/view.dml +0 -39
- package/dist/template/pages/images/controller.ts +0 -15
- package/dist/template/pages/images/view.dml +0 -38
- package/dist/template/pages/index/state.ts +0 -98
- package/dist/template/pages/index/view.dml +0 -20
- package/dist/template/pages/layout/controller.ts +0 -23
- package/dist/template/pages/layout/state.ts +0 -12
- package/dist/template/pages/layout/view.dml +0 -63
- package/dist/template/pages/lists/controller.ts +0 -36
- package/dist/template/pages/lists/state.ts +0 -96
- package/dist/template/pages/lists/view.dml +0 -59
- package/dist/template/pages/navigation/controller.ts +0 -42
- package/dist/template/pages/navigation/state.ts +0 -13
- package/dist/template/pages/navigation/view.dml +0 -24
- package/dist/template/pages/network/controller.ts +0 -107
- package/dist/template/pages/network/state.ts +0 -15
- package/dist/template/pages/network/view.dml +0 -56
- package/dist/template/pages/state/view.dml +0 -33
- package/dist/template/pages/text/controller.ts +0 -10
- package/dist/template/pages/text/state.ts +0 -7
- package/dist/template/pages/text/view.dml +0 -49
- /package/dist/{template → templates/starter}/statics/logo.png +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
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";
|
|
4
5
|
import { existsSync } from "node:fs";
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { parseDmlToIR } from "@monimesl/dot-dml-parser";
|
|
8
|
+
import { ScriptParseError, parseExpression, parseLogicModule, parseStateModule } from "@monimesl/dot-script-parser";
|
|
9
|
+
import { SourceError, builtinRegistry, eventName, eventProp, isEventProp, isWordsOr, otherThanWords } from "@monimesl/dot-core";
|
|
8
10
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
|
+
import ts from "typescript";
|
|
9
12
|
//#region \0rolldown/runtime.js
|
|
10
13
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
11
14
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
@@ -10141,12 +10144,57 @@ function getWellformedEdit(textEdit) {
|
|
|
10141
10144
|
return textEdit;
|
|
10142
10145
|
}
|
|
10143
10146
|
//#endregion
|
|
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."
|
|
10153
|
+
};
|
|
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(", ")}`);
|
|
10168
|
+
}
|
|
10169
|
+
return lines.join("\n");
|
|
10170
|
+
}
|
|
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(" | ");
|
|
10175
|
+
}
|
|
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}`;
|
|
10180
|
+
}
|
|
10181
|
+
function typeSuffix(shape) {
|
|
10182
|
+
return shape ? `: \`${shape.type}\`` : "";
|
|
10183
|
+
}
|
|
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";
|
|
10189
|
+
}
|
|
10190
|
+
}
|
|
10191
|
+
//#endregion
|
|
10144
10192
|
//#region src/lsp/markup.ts
|
|
10145
|
-
const
|
|
10146
|
-
const
|
|
10193
|
+
const TAG_NAME = /[A-Za-z0-9_.:-]/;
|
|
10194
|
+
const ATTRIBUTE_NAME = /[^\s=/>"']/;
|
|
10147
10195
|
const WHITESPACE = /\s/;
|
|
10148
10196
|
function markupContextAt(text, offset) {
|
|
10149
|
-
const
|
|
10197
|
+
const openElements = [];
|
|
10150
10198
|
let index = 0;
|
|
10151
10199
|
while (index < offset) if (text.startsWith("<!--", index)) {
|
|
10152
10200
|
const end = text.indexOf("-->", index + 4);
|
|
@@ -10154,39 +10202,39 @@ function markupContextAt(text, offset) {
|
|
|
10154
10202
|
index = end + 3;
|
|
10155
10203
|
} else if (text.startsWith("</", index)) {
|
|
10156
10204
|
const nameStart = index + 2;
|
|
10157
|
-
const nameEnd = readWhile(text, nameStart,
|
|
10205
|
+
const nameEnd = readWhile(text, nameStart, TAG_NAME);
|
|
10158
10206
|
const close = text.indexOf(">", nameEnd);
|
|
10159
10207
|
if (offset <= nameEnd || close < 0 || close >= offset) return {
|
|
10160
10208
|
kind: "closeTag",
|
|
10161
10209
|
start: nameStart,
|
|
10162
|
-
open:
|
|
10210
|
+
open: openElements.at(-1)
|
|
10163
10211
|
};
|
|
10164
|
-
closeElement(
|
|
10212
|
+
closeElement(openElements, text.slice(nameStart, nameEnd));
|
|
10165
10213
|
index = close + 1;
|
|
10166
10214
|
} else if (text[index] === "<") {
|
|
10167
|
-
const tag = readTag(text, index, offset,
|
|
10215
|
+
const tag = readTag(text, index, offset, openElements);
|
|
10168
10216
|
if (tag.context) return tag.context;
|
|
10169
|
-
if (tag.element && !tag.selfClosing)
|
|
10217
|
+
if (tag.element && !tag.selfClosing) openElements.push(tag.element);
|
|
10170
10218
|
index = tag.end;
|
|
10171
10219
|
} else if (text.startsWith("{{", index)) {
|
|
10172
10220
|
const end = text.indexOf("}}", index + 2);
|
|
10173
10221
|
if (end < 0 || offset <= end) return {
|
|
10174
10222
|
kind: "binding",
|
|
10175
10223
|
start: index + 2,
|
|
10176
|
-
loopVariables: loopVariables(
|
|
10224
|
+
loopVariables: loopVariables(openElements)
|
|
10177
10225
|
};
|
|
10178
10226
|
index = end + 2;
|
|
10179
10227
|
} else index++;
|
|
10180
10228
|
return { kind: "text" };
|
|
10181
10229
|
}
|
|
10182
|
-
function readTag(text, start, offset,
|
|
10230
|
+
function readTag(text, start, offset, openElements) {
|
|
10183
10231
|
const nameStart = start + 1;
|
|
10184
|
-
const nameEnd = readWhile(text, nameStart,
|
|
10232
|
+
const nameEnd = readWhile(text, nameStart, TAG_NAME);
|
|
10185
10233
|
if (offset <= nameEnd) return {
|
|
10186
10234
|
context: {
|
|
10187
10235
|
kind: "tagName",
|
|
10188
10236
|
start: nameStart,
|
|
10189
|
-
parents: [...
|
|
10237
|
+
parents: [...openElements]
|
|
10190
10238
|
},
|
|
10191
10239
|
end: nameEnd
|
|
10192
10240
|
};
|
|
@@ -10221,7 +10269,7 @@ function readTag(text, start, offset, open) {
|
|
|
10221
10269
|
end: index + 1
|
|
10222
10270
|
};
|
|
10223
10271
|
const attributeStart = index;
|
|
10224
|
-
index =
|
|
10272
|
+
index = readWhile(text, index + 1, ATTRIBUTE_NAME);
|
|
10225
10273
|
const name = text.slice(attributeStart, index);
|
|
10226
10274
|
if (offset <= index) return {
|
|
10227
10275
|
context: {
|
|
@@ -10241,7 +10289,7 @@ function readTag(text, start, offset, open) {
|
|
|
10241
10289
|
if (quote === "\"" || quote === "'") {
|
|
10242
10290
|
const close = text.indexOf(quote, valueStart + 1);
|
|
10243
10291
|
if (close < 0 || offset <= close) return {
|
|
10244
|
-
context: valueContext(text.slice(valueStart + 1, offset), valueStart + 1, element, name,
|
|
10292
|
+
context: valueContext(text.slice(valueStart + 1, offset), valueStart + 1, element, name, openElements),
|
|
10245
10293
|
end: offset
|
|
10246
10294
|
};
|
|
10247
10295
|
attributes[name] = text.slice(valueStart + 1, close);
|
|
@@ -10253,7 +10301,8 @@ function readTag(text, start, offset, open) {
|
|
|
10253
10301
|
kind: "attributeValue",
|
|
10254
10302
|
start: valueStart,
|
|
10255
10303
|
element,
|
|
10256
|
-
attribute: name
|
|
10304
|
+
attribute: name,
|
|
10305
|
+
loopVariables: loopVariables(openElements)
|
|
10257
10306
|
},
|
|
10258
10307
|
end: valueEnd
|
|
10259
10308
|
};
|
|
@@ -10262,32 +10311,34 @@ function readTag(text, start, offset, open) {
|
|
|
10262
10311
|
}
|
|
10263
10312
|
}
|
|
10264
10313
|
}
|
|
10265
|
-
function valueContext(valueBeforeCursor, valueStart, element, attribute,
|
|
10314
|
+
function valueContext(valueBeforeCursor, valueStart, element, attribute, openElements) {
|
|
10315
|
+
const variables = loopVariables(openElements);
|
|
10266
10316
|
const bindingStart = valueBeforeCursor.lastIndexOf("{{");
|
|
10267
10317
|
if (bindingStart >= 0 && !valueBeforeCursor.includes("}}", bindingStart)) return {
|
|
10268
10318
|
kind: "binding",
|
|
10269
10319
|
start: valueStart + bindingStart + 2,
|
|
10270
|
-
loopVariables:
|
|
10320
|
+
loopVariables: variables
|
|
10271
10321
|
};
|
|
10272
10322
|
return {
|
|
10273
10323
|
kind: "attributeValue",
|
|
10274
10324
|
start: valueStart,
|
|
10275
10325
|
element,
|
|
10276
|
-
attribute
|
|
10326
|
+
attribute,
|
|
10327
|
+
loopVariables: variables
|
|
10277
10328
|
};
|
|
10278
10329
|
}
|
|
10279
|
-
function closeElement(
|
|
10280
|
-
for (let index =
|
|
10281
|
-
|
|
10330
|
+
function closeElement(openElements, tag) {
|
|
10331
|
+
for (let index = openElements.length - 1; index >= 0; index--) if (openElements[index].tag === tag) {
|
|
10332
|
+
openElements.length = index;
|
|
10282
10333
|
return;
|
|
10283
10334
|
}
|
|
10284
10335
|
}
|
|
10285
|
-
function loopVariables(
|
|
10336
|
+
function loopVariables(openElements) {
|
|
10286
10337
|
const variables = [];
|
|
10287
|
-
for (const element of
|
|
10338
|
+
for (const element of openElements) {
|
|
10288
10339
|
if (element.tag !== "For") continue;
|
|
10289
10340
|
const { item = "item", index = "index", each = "" } = element.attributes;
|
|
10290
|
-
const list = each
|
|
10341
|
+
const list = bindingSource(each);
|
|
10291
10342
|
variables.push({
|
|
10292
10343
|
name: item,
|
|
10293
10344
|
role: "item",
|
|
@@ -10300,6 +10351,9 @@ function loopVariables(open) {
|
|
|
10300
10351
|
}
|
|
10301
10352
|
return variables;
|
|
10302
10353
|
}
|
|
10354
|
+
function bindingSource(value) {
|
|
10355
|
+
return value.replace(/^\s*{{\s*|\s*}}\s*$/g, "");
|
|
10356
|
+
}
|
|
10303
10357
|
function readWhile(text, index, pattern) {
|
|
10304
10358
|
while (index < text.length && pattern.test(text[index])) index++;
|
|
10305
10359
|
return index;
|
|
@@ -10308,7 +10362,6 @@ function readUntil(text, index, pattern) {
|
|
|
10308
10362
|
while (index < text.length && !pattern.test(text[index])) index++;
|
|
10309
10363
|
return index;
|
|
10310
10364
|
}
|
|
10311
|
-
/** The start and end of the run of `pattern` characters around `offset`. */
|
|
10312
10365
|
function wordAt(text, offset, pattern) {
|
|
10313
10366
|
let start = offset;
|
|
10314
10367
|
while (start > 0 && pattern.test(text[start - 1])) start--;
|
|
@@ -10318,92 +10371,163 @@ function wordAt(text, offset, pattern) {
|
|
|
10318
10371
|
};
|
|
10319
10372
|
}
|
|
10320
10373
|
//#endregion
|
|
10321
|
-
//#region src/lsp/
|
|
10322
|
-
const
|
|
10323
|
-
|
|
10374
|
+
//#region src/lsp/shapes.ts
|
|
10375
|
+
const NUMBER_SHAPE = {
|
|
10376
|
+
kind: "value",
|
|
10377
|
+
type: "number"
|
|
10378
|
+
};
|
|
10379
|
+
const MEMBER_CHAIN = /([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*\.\s*$/;
|
|
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
|
|
10324
10460
|
const IMAGE_ATTRIBUTES = ["src", "image"];
|
|
10325
|
-
const
|
|
10326
|
-
const VALUE = /[^\s"']/;
|
|
10461
|
+
const IDENTIFIER_BEFORE_CURSOR = /[A-Za-z_$][\w$]*$/;
|
|
10327
10462
|
const CONTROL_FLOW_SNIPPETS = {
|
|
10328
10463
|
If: "If condition=\"{{ $1 }}\">$0</If>",
|
|
10329
10464
|
ElseIf: "ElseIf condition=\"{{ $1 }}\">$0</ElseIf>",
|
|
10330
10465
|
Else: "Else>$0</Else>",
|
|
10331
10466
|
For: "For each=\"{{ $1 }}\" key=\"$2\">$0</For>"
|
|
10332
10467
|
};
|
|
10333
|
-
|
|
10334
|
-
If: "Shows its children when `condition` is true.",
|
|
10335
|
-
ElseIf: "Follows an `<If>` or `<ElseIf>`: shows its children when the ones before are false and `condition` is true.",
|
|
10336
|
-
Else: "Follows an `<If>` or `<ElseIf>`: shows its children when the ones before are false.",
|
|
10337
|
-
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."
|
|
10338
|
-
};
|
|
10339
|
-
const CONTROL_FLOW_ATTRIBUTE_DOCS = {
|
|
10340
|
-
condition: "A single `{{ }}` binding",
|
|
10341
|
-
each: "The list to repeat, as a single `{{ }}` binding",
|
|
10342
|
-
item: "The name of each item (default `item`)",
|
|
10343
|
-
index: "The name of each position (default `index`)",
|
|
10344
|
-
key: "A property of each item, `*this` or `*index`"
|
|
10345
|
-
};
|
|
10346
|
-
function complete(document, offset, page, { components }) {
|
|
10468
|
+
function complete(document, offset, page, components) {
|
|
10347
10469
|
const text = document.getText();
|
|
10348
10470
|
const context = markupContextAt(text, offset);
|
|
10349
|
-
const range = (start) => ({
|
|
10350
|
-
start: document.positionAt(start),
|
|
10351
|
-
end: document.positionAt(offset)
|
|
10352
|
-
});
|
|
10353
10471
|
switch (context.kind) {
|
|
10354
|
-
case "tagName":
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
newText: item.insertText ?? item.label
|
|
10359
|
-
}
|
|
10360
|
-
}));
|
|
10472
|
+
case "tagName": {
|
|
10473
|
+
const items = tagCompletions(context.parents, components);
|
|
10474
|
+
return replacingFrom(document, context.start, offset, items);
|
|
10475
|
+
}
|
|
10361
10476
|
case "closeTag":
|
|
10362
10477
|
if (!context.open) return [];
|
|
10363
10478
|
return [{
|
|
10364
10479
|
label: `/${context.open.tag}`,
|
|
10365
10480
|
kind: import_main.CompletionItemKind.Property,
|
|
10366
10481
|
textEdit: {
|
|
10367
|
-
range:
|
|
10482
|
+
range: rangeFrom(document, context.start, offset),
|
|
10368
10483
|
newText: `${context.open.tag}>`
|
|
10369
10484
|
}
|
|
10370
10485
|
}];
|
|
10371
|
-
case "attributeName":
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
textEdit: {
|
|
10381
|
-
range: range(context.start),
|
|
10382
|
-
newText: item.label
|
|
10383
|
-
}
|
|
10384
|
-
}));
|
|
10486
|
+
case "attributeName": {
|
|
10487
|
+
const items = attributeCompletions(context.element, components);
|
|
10488
|
+
return replacingFrom(document, context.start, offset, items);
|
|
10489
|
+
}
|
|
10490
|
+
case "attributeValue": {
|
|
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);
|
|
10494
|
+
}
|
|
10385
10495
|
case "binding": {
|
|
10386
10496
|
const typed = text.slice(context.start, offset);
|
|
10387
|
-
const
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
newText: item.label
|
|
10394
|
-
}
|
|
10395
|
-
}));
|
|
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);
|
|
10396
10503
|
}
|
|
10397
10504
|
default: return [];
|
|
10398
10505
|
}
|
|
10399
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
|
+
}
|
|
10400
10522
|
function tagCompletions(parents, components) {
|
|
10401
10523
|
if (parents.length === 0) {
|
|
10402
10524
|
const root = components[ROOT_TAG];
|
|
10403
|
-
|
|
10525
|
+
if (!root) return [];
|
|
10526
|
+
return [componentItem(root, "0")];
|
|
10404
10527
|
}
|
|
10405
10528
|
const parent = [...parents].reverse().find((element) => !(element.tag in CONTROL_FLOW_TAGS));
|
|
10406
|
-
|
|
10529
|
+
let parentManifest;
|
|
10530
|
+
if (parent) parentManifest = components[parent.tag];
|
|
10407
10531
|
const items = Object.values(components).filter((component) => !parentManifest || canHaveChildren(parentManifest, component)).map((component) => componentItem(component, "1"));
|
|
10408
10532
|
for (const [tag, snippet] of Object.entries(CONTROL_FLOW_SNIPPETS)) items.push({
|
|
10409
10533
|
label: tag,
|
|
@@ -10420,7 +10544,8 @@ function tagCompletions(parents, components) {
|
|
|
10420
10544
|
return items;
|
|
10421
10545
|
}
|
|
10422
10546
|
function componentItem(component, sortGroup) {
|
|
10423
|
-
|
|
10547
|
+
let snippet = `${component.name}$1>$0</${component.name}>`;
|
|
10548
|
+
if (component.children === "none") snippet = `${component.name}$1 />`;
|
|
10424
10549
|
return {
|
|
10425
10550
|
label: component.name,
|
|
10426
10551
|
kind: import_main.CompletionItemKind.Class,
|
|
@@ -10435,18 +10560,18 @@ function componentItem(component, sortGroup) {
|
|
|
10435
10560
|
};
|
|
10436
10561
|
}
|
|
10437
10562
|
function attributeCompletions(element, components) {
|
|
10438
|
-
const
|
|
10563
|
+
const isWritten = (name) => name in element.attributes;
|
|
10439
10564
|
const controlFlow = CONTROL_FLOW_TAGS[element.tag];
|
|
10440
|
-
if (controlFlow) return controlFlow.attributes.filter((name) => !
|
|
10565
|
+
if (controlFlow) return controlFlow.attributes.filter((name) => !isWritten(name)).map((name) => ({
|
|
10441
10566
|
label: name,
|
|
10442
10567
|
kind: import_main.CompletionItemKind.Property,
|
|
10443
10568
|
detail: CONTROL_FLOW_ATTRIBUTE_DOCS[name] ?? name,
|
|
10444
|
-
insertText: name
|
|
10569
|
+
insertText: controlFlowAttributeSnippet(name),
|
|
10445
10570
|
insertTextFormat: import_main.InsertTextFormat.Snippet
|
|
10446
10571
|
}));
|
|
10447
10572
|
const component = components[element.tag];
|
|
10448
10573
|
if (!component) return [];
|
|
10449
|
-
const props = Object.entries(component.props).filter(([name]) => !
|
|
10574
|
+
const props = Object.entries(component.props).filter(([name]) => !isWritten(name)).map(([name, type]) => ({
|
|
10450
10575
|
label: name,
|
|
10451
10576
|
kind: import_main.CompletionItemKind.Property,
|
|
10452
10577
|
detail: describeType(type),
|
|
@@ -10454,7 +10579,7 @@ function attributeCompletions(element, components) {
|
|
|
10454
10579
|
insertTextFormat: import_main.InsertTextFormat.Snippet,
|
|
10455
10580
|
sortText: `0${name}`
|
|
10456
10581
|
}));
|
|
10457
|
-
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) => ({
|
|
10458
10583
|
label: name,
|
|
10459
10584
|
kind: import_main.CompletionItemKind.Event,
|
|
10460
10585
|
detail: "a mutation or action in controller.ts",
|
|
@@ -10464,20 +10589,20 @@ function attributeCompletions(element, components) {
|
|
|
10464
10589
|
}));
|
|
10465
10590
|
return [...props, ...events];
|
|
10466
10591
|
}
|
|
10467
|
-
function
|
|
10468
|
-
if (
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
label: "*index",
|
|
10474
|
-
kind: import_main.CompletionItemKind.Value,
|
|
10475
|
-
detail: "match items by position"
|
|
10476
|
-
}];
|
|
10592
|
+
function controlFlowAttributeSnippet(name) {
|
|
10593
|
+
if (name === "condition" || name === "each") return `${name}="{{ $1 }}"`;
|
|
10594
|
+
return `${name}="$1"`;
|
|
10595
|
+
}
|
|
10596
|
+
function valueCompletions(element, attribute, page, scope, components) {
|
|
10597
|
+
if (element.tag === "For" && attribute === "key") return forKeyCompletions(element, scope);
|
|
10477
10598
|
const component = components[element.tag];
|
|
10478
10599
|
if (!component) return [];
|
|
10479
|
-
if (isEventProp(attribute) && component.events.includes(eventName(attribute))) return
|
|
10600
|
+
if (isEventProp(attribute) && component.events.includes(eventName(attribute))) return handlerCompletions(page);
|
|
10480
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
|
+
}));
|
|
10481
10606
|
if (Array.isArray(type)) return type.map((value) => ({
|
|
10482
10607
|
label: value,
|
|
10483
10608
|
kind: import_main.CompletionItemKind.EnumMember
|
|
@@ -10492,36 +10617,394 @@ function valueCompletions(element, attribute, page, components) {
|
|
|
10492
10617
|
}));
|
|
10493
10618
|
return [];
|
|
10494
10619
|
}
|
|
10495
|
-
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) {
|
|
10496
10641
|
const { controller } = page;
|
|
10497
10642
|
if (!controller) return [];
|
|
10498
|
-
|
|
10643
|
+
const mutations = (controller.mutations ?? []).map((name) => ({
|
|
10499
10644
|
label: name,
|
|
10500
10645
|
kind: import_main.CompletionItemKind.Method,
|
|
10501
10646
|
detail: "mutation"
|
|
10502
|
-
}))
|
|
10647
|
+
}));
|
|
10648
|
+
const actions = (controller.actions ?? []).map((name) => ({
|
|
10503
10649
|
label: name,
|
|
10504
10650
|
kind: import_main.CompletionItemKind.Function,
|
|
10505
10651
|
detail: "action"
|
|
10506
|
-
}))
|
|
10652
|
+
}));
|
|
10653
|
+
return [...mutations, ...actions];
|
|
10507
10654
|
}
|
|
10508
|
-
function bindingCompletions(loopVariables, page) {
|
|
10655
|
+
function bindingCompletions(loopVariables, page, scope) {
|
|
10509
10656
|
const items = /* @__PURE__ */ new Map();
|
|
10510
|
-
for (const key of page.state?.keys ?? [])
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
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
|
+
}
|
|
10516
10666
|
for (const variable of loopVariables) items.set(variable.name, {
|
|
10517
10667
|
label: variable.name,
|
|
10518
10668
|
kind: import_main.CompletionItemKind.Variable,
|
|
10519
|
-
detail: describeLoopVariable(variable),
|
|
10669
|
+
detail: describeLoopVariable(variable, scope?.loops.get(variable.name)),
|
|
10520
10670
|
sortText: `0${variable.name}`
|
|
10521
10671
|
});
|
|
10522
10672
|
return [...items.values()];
|
|
10523
10673
|
}
|
|
10524
|
-
function
|
|
10674
|
+
function memberCompletions(owner) {
|
|
10675
|
+
if (!owner) return [];
|
|
10676
|
+
return Object.entries(membersOf(owner)).map(([name, shape]) => ({
|
|
10677
|
+
label: name,
|
|
10678
|
+
kind: import_main.CompletionItemKind.Field,
|
|
10679
|
+
detail: shape.type
|
|
10680
|
+
}));
|
|
10681
|
+
}
|
|
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;
|
|
10701
|
+
}
|
|
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;
|
|
10708
|
+
}
|
|
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;
|
|
10713
|
+
}
|
|
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);
|
|
10721
|
+
}
|
|
10722
|
+
}
|
|
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
|
+
});
|
|
10732
|
+
}
|
|
10733
|
+
}
|
|
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
|
+
];
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
function offsetOf(text, written, from) {
|
|
10760
|
+
const foundAt = text.indexOf(written, from);
|
|
10761
|
+
if (foundAt < 0) return from;
|
|
10762
|
+
return foundAt;
|
|
10763
|
+
}
|
|
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;
|
|
10769
|
+
}
|
|
10770
|
+
//#endregion
|
|
10771
|
+
//#region src/lsp/diagnostics.ts
|
|
10772
|
+
function diagnose(document, file, page, components) {
|
|
10773
|
+
const diagnostics = [];
|
|
10774
|
+
const context = {
|
|
10775
|
+
file,
|
|
10776
|
+
components,
|
|
10777
|
+
onWarning: (warning) => {
|
|
10778
|
+
diagnostics.push(sourceDiagnostic(document, warning, import_main.DiagnosticSeverity.Warning));
|
|
10779
|
+
}
|
|
10780
|
+
};
|
|
10781
|
+
if (page.statics) context.statics = page.statics;
|
|
10782
|
+
if (page.state?.keys) context.stateKeys = page.state.keys;
|
|
10783
|
+
const mutations = page.controller?.mutations;
|
|
10784
|
+
const actions = page.controller?.actions;
|
|
10785
|
+
if (mutations && actions) context.handlers = [...mutations, ...actions];
|
|
10786
|
+
try {
|
|
10787
|
+
compileView(document.getText(), context);
|
|
10788
|
+
} catch (error) {
|
|
10789
|
+
if (!(error instanceof SourceError)) throw error;
|
|
10790
|
+
diagnostics.push(sourceDiagnostic(document, error, import_main.DiagnosticSeverity.Error));
|
|
10791
|
+
}
|
|
10792
|
+
if (page.state?.shapes) for (const problem of checkMembers(document.getText(), page.state.shapes)) diagnostics.push(diagnosticAt(document, problem.offset, problem.message, import_main.DiagnosticSeverity.Error));
|
|
10793
|
+
return diagnostics;
|
|
10794
|
+
}
|
|
10795
|
+
function sourceDiagnostic(document, problem, severity) {
|
|
10796
|
+
return diagnosticAt(document, problem.loc?.offset ?? 0, problem.reason, severity);
|
|
10797
|
+
}
|
|
10798
|
+
function diagnosticAt(document, start, message, severity) {
|
|
10799
|
+
const text = document.getText();
|
|
10800
|
+
const end = readUntil(text, start, /[\s>]/);
|
|
10801
|
+
const atLeastOneCharacter = Math.min(start + 1, text.length);
|
|
10802
|
+
return {
|
|
10803
|
+
severity,
|
|
10804
|
+
source: "dot",
|
|
10805
|
+
message,
|
|
10806
|
+
range: {
|
|
10807
|
+
start: document.positionAt(start),
|
|
10808
|
+
end: document.positionAt(Math.max(end, atLeastOneCharacter))
|
|
10809
|
+
}
|
|
10810
|
+
};
|
|
10811
|
+
}
|
|
10812
|
+
//#endregion
|
|
10813
|
+
//#region src/lsp/page.ts
|
|
10814
|
+
async function readPage(viewFile, readText, stateTypes) {
|
|
10815
|
+
const dir = dirname(viewFile);
|
|
10816
|
+
const root = findDotRoot(dir);
|
|
10817
|
+
const [state, controller, statics] = await Promise.all([
|
|
10818
|
+
readState(join(dir, "state.ts"), root, readText, stateTypes),
|
|
10819
|
+
readController(join(dir, "controller.ts"), readText),
|
|
10820
|
+
root === void 0 ? void 0 : listStatics(root).catch(() => void 0)
|
|
10821
|
+
]);
|
|
10822
|
+
return {
|
|
10823
|
+
root,
|
|
10824
|
+
state,
|
|
10825
|
+
controller,
|
|
10826
|
+
statics
|
|
10827
|
+
};
|
|
10828
|
+
}
|
|
10829
|
+
async function readState(file, root, readText, stateTypes) {
|
|
10830
|
+
const text = await readText(file);
|
|
10831
|
+
if (text === void 0) return;
|
|
10832
|
+
try {
|
|
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
|
+
};
|
|
10841
|
+
} catch {
|
|
10842
|
+
return;
|
|
10843
|
+
}
|
|
10844
|
+
}
|
|
10845
|
+
function readShapes(stateTypes, root, file, text) {
|
|
10846
|
+
try {
|
|
10847
|
+
return stateTypes.read(root, file, text);
|
|
10848
|
+
} catch {
|
|
10849
|
+
return;
|
|
10850
|
+
}
|
|
10851
|
+
}
|
|
10852
|
+
async function readController(file, readText) {
|
|
10853
|
+
const text = await readText(file);
|
|
10854
|
+
if (text === void 0) return;
|
|
10855
|
+
try {
|
|
10856
|
+
const { mutations, actions, handlerOffsets } = parseLogicModule(text, file);
|
|
10857
|
+
return {
|
|
10858
|
+
file,
|
|
10859
|
+
text,
|
|
10860
|
+
mutations,
|
|
10861
|
+
actions,
|
|
10862
|
+
offsets: handlerOffsets
|
|
10863
|
+
};
|
|
10864
|
+
} catch {
|
|
10865
|
+
return;
|
|
10866
|
+
}
|
|
10867
|
+
}
|
|
10868
|
+
//#endregion
|
|
10869
|
+
//#region src/lsp/state-types.ts
|
|
10870
|
+
const MAX_DEPTH = 8;
|
|
10871
|
+
const FALLBACK_OPTIONS = {
|
|
10872
|
+
target: ts.ScriptTarget.ES2022,
|
|
10873
|
+
module: ts.ModuleKind.ESNext,
|
|
10874
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
10875
|
+
strict: true,
|
|
10876
|
+
noEmit: true,
|
|
10877
|
+
skipLibCheck: true
|
|
10878
|
+
};
|
|
10879
|
+
var StateTypes = class {
|
|
10880
|
+
#projects = /* @__PURE__ */ new Map();
|
|
10881
|
+
read(root, file, text) {
|
|
10882
|
+
const key = root ?? file;
|
|
10883
|
+
let project = this.#projects.get(key);
|
|
10884
|
+
if (!project) {
|
|
10885
|
+
project = new TypeScriptProject(root);
|
|
10886
|
+
this.#projects.set(key, project);
|
|
10887
|
+
}
|
|
10888
|
+
return project.read(file, text);
|
|
10889
|
+
}
|
|
10890
|
+
};
|
|
10891
|
+
var TypeScriptProject = class {
|
|
10892
|
+
#options;
|
|
10893
|
+
#fileNames;
|
|
10894
|
+
#open = /* @__PURE__ */ new Map();
|
|
10895
|
+
#service;
|
|
10896
|
+
constructor(root) {
|
|
10897
|
+
const config = root === void 0 ? void 0 : readConfig(root);
|
|
10898
|
+
this.#options = config?.options ?? FALLBACK_OPTIONS;
|
|
10899
|
+
this.#fileNames = config?.fileNames ?? [];
|
|
10900
|
+
const host = {
|
|
10901
|
+
getCompilationSettings: () => this.#options,
|
|
10902
|
+
getScriptFileNames: () => [.../* @__PURE__ */ new Set([...this.#fileNames, ...this.#open.keys()])],
|
|
10903
|
+
getScriptVersion: (file) => {
|
|
10904
|
+
const open = this.#open.get(file);
|
|
10905
|
+
if (open) return String(open.version);
|
|
10906
|
+
return String(ts.sys.getModifiedTime?.(file)?.getTime() ?? 0);
|
|
10907
|
+
},
|
|
10908
|
+
getScriptSnapshot: (file) => {
|
|
10909
|
+
const text = this.#open.get(file)?.text ?? ts.sys.readFile(file);
|
|
10910
|
+
if (text === void 0) return;
|
|
10911
|
+
return ts.ScriptSnapshot.fromString(text);
|
|
10912
|
+
},
|
|
10913
|
+
getCurrentDirectory: () => root ?? process.cwd(),
|
|
10914
|
+
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
10915
|
+
fileExists: (file) => ts.sys.fileExists(file),
|
|
10916
|
+
readFile: (file) => ts.sys.readFile(file),
|
|
10917
|
+
readDirectory: (...args) => ts.sys.readDirectory(...args),
|
|
10918
|
+
directoryExists: (dir) => ts.sys.directoryExists(dir),
|
|
10919
|
+
getDirectories: (dir) => ts.sys.getDirectories(dir),
|
|
10920
|
+
realpath: (path) => ts.sys.realpath?.(path) ?? path
|
|
10921
|
+
};
|
|
10922
|
+
this.#service = ts.createLanguageService(host);
|
|
10923
|
+
}
|
|
10924
|
+
read(file, text) {
|
|
10925
|
+
const open = this.#open.get(file);
|
|
10926
|
+
if (open?.text !== text) this.#open.set(file, {
|
|
10927
|
+
text,
|
|
10928
|
+
version: (open?.version ?? 0) + 1
|
|
10929
|
+
});
|
|
10930
|
+
const program = this.#service.getProgram();
|
|
10931
|
+
const source = program?.getSourceFile(file);
|
|
10932
|
+
if (!program || !source) return;
|
|
10933
|
+
const checker = program.getTypeChecker();
|
|
10934
|
+
const module = checker.getSymbolAtLocation(source);
|
|
10935
|
+
if (!module) return;
|
|
10936
|
+
let exported = checker.tryGetMemberInModuleExports("default", module);
|
|
10937
|
+
if (!exported) return;
|
|
10938
|
+
if (exported.flags & ts.SymbolFlags.Alias) exported = checker.getAliasedSymbol(exported);
|
|
10939
|
+
const state = checker.getTypeOfSymbol(exported);
|
|
10940
|
+
const shapes = {};
|
|
10941
|
+
for (const property of checker.getPropertiesOfType(state)) shapes[property.name] = propertyShape(checker, property, 0, /* @__PURE__ */ new Set());
|
|
10942
|
+
return shapes;
|
|
10943
|
+
}
|
|
10944
|
+
};
|
|
10945
|
+
function readConfig(root) {
|
|
10946
|
+
const file = join(root, "tsconfig.json");
|
|
10947
|
+
if (!existsSync(file)) return;
|
|
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);
|
|
10951
|
+
}
|
|
10952
|
+
function propertyShape(checker, property, depth, seen) {
|
|
10953
|
+
const shape = typeShape(checker, checker.getTypeOfSymbol(property), depth, seen);
|
|
10954
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
10955
|
+
if (!declaration) return shape;
|
|
10956
|
+
const source = declaration.getSourceFile();
|
|
10957
|
+
const { line, character } = source.getLineAndCharacterOfPosition(declaration.getStart());
|
|
10958
|
+
return {
|
|
10959
|
+
...shape,
|
|
10960
|
+
declaration: {
|
|
10961
|
+
file: source.fileName,
|
|
10962
|
+
line,
|
|
10963
|
+
character
|
|
10964
|
+
}
|
|
10965
|
+
};
|
|
10966
|
+
}
|
|
10967
|
+
function typeShape(checker, type, depth, seen) {
|
|
10968
|
+
const present = checker.getNonNullableType(type);
|
|
10969
|
+
const name = checker.typeToString(present);
|
|
10970
|
+
if (depth >= MAX_DEPTH || seen.has(present) || present.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) return {
|
|
10971
|
+
kind: "value",
|
|
10972
|
+
type: name
|
|
10973
|
+
};
|
|
10974
|
+
const inner = new Set(seen).add(present);
|
|
10975
|
+
if (checker.isArrayType(present) || checker.isTupleType(present)) {
|
|
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);
|
|
10982
|
+
return {
|
|
10983
|
+
kind: "list",
|
|
10984
|
+
type: name,
|
|
10985
|
+
item: itemShape
|
|
10986
|
+
};
|
|
10987
|
+
}
|
|
10988
|
+
if (!isRecord(checker, present)) return {
|
|
10989
|
+
kind: "value",
|
|
10990
|
+
type: name
|
|
10991
|
+
};
|
|
10992
|
+
const properties = {};
|
|
10993
|
+
for (const property of checker.getPropertiesOfType(present)) properties[property.name] = propertyShape(checker, property, depth + 1, inner);
|
|
10994
|
+
return {
|
|
10995
|
+
kind: "object",
|
|
10996
|
+
type: name,
|
|
10997
|
+
properties
|
|
10998
|
+
};
|
|
10999
|
+
}
|
|
11000
|
+
function isRecord(checker, type) {
|
|
11001
|
+
return (type.isUnion() ? type.types : [type]).every((member) => (member.flags & ts.TypeFlags.Object) !== 0 && member.getCallSignatures().length === 0 && checker.getIndexInfosOfType(member).length === 0);
|
|
11002
|
+
}
|
|
11003
|
+
//#endregion
|
|
11004
|
+
//#region src/lsp/symbols.ts
|
|
11005
|
+
const IDENTIFIER = /[\w$]/;
|
|
11006
|
+
const VALUE = /[^\s"']/;
|
|
11007
|
+
function hover(document, offset, page, components) {
|
|
10525
11008
|
const found = symbolAt(document.getText(), offset, page);
|
|
10526
11009
|
if (!found) return null;
|
|
10527
11010
|
const markdown = describeSymbol(found.symbol, page, components);
|
|
@@ -10541,8 +11024,22 @@ function definition(document, offset, page) {
|
|
|
10541
11024
|
const found = symbolAt(document.getText(), offset, page);
|
|
10542
11025
|
if (!found) return null;
|
|
10543
11026
|
const { symbol } = found;
|
|
10544
|
-
if (symbol.kind === "handler" && page.controller) return
|
|
10545
|
-
if (symbol.kind === "state" && page.state) return
|
|
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
|
+
}
|
|
10546
11043
|
if (symbol.kind === "static" && page.root) return locationIn(join(page.root, symbol.path), "", 0);
|
|
10547
11044
|
return null;
|
|
10548
11045
|
}
|
|
@@ -10564,7 +11061,8 @@ function symbolAt(text, offset, page) {
|
|
|
10564
11061
|
case "attributeName": {
|
|
10565
11062
|
const { start, end } = wordAt(text, offset, ATTRIBUTE_NAME);
|
|
10566
11063
|
const name = text.slice(start, end);
|
|
10567
|
-
|
|
11064
|
+
if (!name) return null;
|
|
11065
|
+
return {
|
|
10568
11066
|
symbol: {
|
|
10569
11067
|
kind: "attribute",
|
|
10570
11068
|
tag: context.element.tag,
|
|
@@ -10572,7 +11070,7 @@ function symbolAt(text, offset, page) {
|
|
|
10572
11070
|
},
|
|
10573
11071
|
start,
|
|
10574
11072
|
end
|
|
10575
|
-
}
|
|
11073
|
+
};
|
|
10576
11074
|
}
|
|
10577
11075
|
case "attributeValue": {
|
|
10578
11076
|
const { start, end } = wordAt(text, offset, VALUE);
|
|
@@ -10598,12 +11096,30 @@ function symbolAt(text, offset, page) {
|
|
|
10598
11096
|
case "binding": {
|
|
10599
11097
|
const { start, end } = wordAt(text, offset, IDENTIFIER);
|
|
10600
11098
|
const name = text.slice(start, end);
|
|
10601
|
-
if (!name
|
|
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
|
+
}
|
|
10602
11117
|
const variable = [...context.loopVariables].reverse().find((candidate) => candidate.name === name);
|
|
10603
11118
|
if (variable) return {
|
|
10604
11119
|
symbol: {
|
|
10605
11120
|
kind: "loopVariable",
|
|
10606
|
-
variable
|
|
11121
|
+
variable,
|
|
11122
|
+
shape: scope?.loops.get(name)
|
|
10607
11123
|
},
|
|
10608
11124
|
start,
|
|
10609
11125
|
end
|
|
@@ -10623,56 +11139,45 @@ function symbolAt(text, offset, page) {
|
|
|
10623
11139
|
function describeSymbol(symbol, page, components) {
|
|
10624
11140
|
switch (symbol.kind) {
|
|
10625
11141
|
case "component": {
|
|
10626
|
-
const
|
|
10627
|
-
if (
|
|
10628
|
-
const component = components[symbol.tag];
|
|
10629
|
-
return component ? describeComponent(component) : null;
|
|
10630
|
-
}
|
|
10631
|
-
case "attribute": {
|
|
10632
|
-
if (CONTROL_FLOW_TAGS[symbol.tag]) {
|
|
10633
|
-
const doc = CONTROL_FLOW_ATTRIBUTE_DOCS[symbol.name];
|
|
10634
|
-
return doc ? `\`${symbol.name}\`: ${doc}` : null;
|
|
10635
|
-
}
|
|
11142
|
+
const controlFlowDoc = CONTROL_FLOW_DOCS[symbol.tag];
|
|
11143
|
+
if (controlFlowDoc) return `**<${symbol.tag}>** · control flow\n\n${controlFlowDoc}`;
|
|
10636
11144
|
const component = components[symbol.tag];
|
|
10637
11145
|
if (!component) return null;
|
|
10638
|
-
|
|
10639
|
-
if (type) return `\`${symbol.name}\`: ${describeType(type)}`;
|
|
10640
|
-
if (symbol.name.startsWith(DATA_PREFIX)) return `\`${symbol.name}\`: reaches this element's handlers as \`event.data.${dataKey(symbol.name)}\``;
|
|
10641
|
-
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)}\``;
|
|
10642
|
-
return null;
|
|
11146
|
+
return describeComponent(component);
|
|
10643
11147
|
}
|
|
11148
|
+
case "attribute": return describeAttribute(symbol.tag, symbol.name, components);
|
|
10644
11149
|
case "handler": {
|
|
10645
11150
|
const { controller } = page;
|
|
10646
11151
|
if (controller?.mutations?.includes(symbol.name)) return `mutation \`${symbol.name}\` in controller.ts`;
|
|
10647
11152
|
if (controller?.actions?.includes(symbol.name)) return `action \`${symbol.name}\` in controller.ts`;
|
|
10648
11153
|
return null;
|
|
10649
11154
|
}
|
|
10650
|
-
case "state":
|
|
10651
|
-
|
|
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}\``;
|
|
10652
11162
|
case "static": return `image \`${symbol.path}\``;
|
|
10653
11163
|
}
|
|
10654
11164
|
}
|
|
10655
|
-
function
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
return lines.join("\n");
|
|
10661
|
-
}
|
|
10662
|
-
function describeChildren(component) {
|
|
10663
|
-
switch (component.children) {
|
|
10664
|
-
case "none": return "no children";
|
|
10665
|
-
case "text": return "text and inline components";
|
|
10666
|
-
case "elements": return "any components";
|
|
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}`;
|
|
10667
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;
|
|
10668
11178
|
}
|
|
10669
|
-
function
|
|
10670
|
-
|
|
10671
|
-
return type.map((value) => `"${value}"`).join(" | ");
|
|
10672
|
-
}
|
|
10673
|
-
function describeLoopVariable(variable) {
|
|
10674
|
-
const list = variable.list ? ` \`${variable.list}\`` : "";
|
|
10675
|
-
return variable.role === "item" ? `each item of${list}` : `the position in${list}`;
|
|
11179
|
+
function definitionIn(source, name) {
|
|
11180
|
+
return locationIn(source.file, source.text, source.offsets[name]);
|
|
10676
11181
|
}
|
|
10677
11182
|
function locationIn(file, text, offset) {
|
|
10678
11183
|
if (offset === void 0) return null;
|
|
@@ -10685,136 +11190,42 @@ function locationIn(file, text, offset) {
|
|
|
10685
11190
|
}
|
|
10686
11191
|
};
|
|
10687
11192
|
}
|
|
10688
|
-
function diagnose(document, file, page, { components }) {
|
|
10689
|
-
const diagnostics = [];
|
|
10690
|
-
const context = {
|
|
10691
|
-
file,
|
|
10692
|
-
components,
|
|
10693
|
-
onWarning: (warning) => diagnostics.push(diagnosticAt(document, warning, import_main.DiagnosticSeverity.Warning))
|
|
10694
|
-
};
|
|
10695
|
-
if (page.statics) context.statics = page.statics;
|
|
10696
|
-
if (page.state?.keys) context.stateKeys = page.state.keys;
|
|
10697
|
-
const { mutations, actions } = page.controller ?? {};
|
|
10698
|
-
if (mutations && actions) context.handlers = [...mutations, ...actions];
|
|
10699
|
-
try {
|
|
10700
|
-
compileView(document.getText(), context);
|
|
10701
|
-
} catch (error) {
|
|
10702
|
-
if (!(error instanceof SourceError)) throw error;
|
|
10703
|
-
diagnostics.push(diagnosticAt(document, error, import_main.DiagnosticSeverity.Error));
|
|
10704
|
-
}
|
|
10705
|
-
return diagnostics;
|
|
10706
|
-
}
|
|
10707
|
-
function diagnosticAt(document, problem, severity) {
|
|
10708
|
-
const text = document.getText();
|
|
10709
|
-
const start = problem.loc?.offset ?? 0;
|
|
10710
|
-
let end = start;
|
|
10711
|
-
while (end < text.length && !/[\s>]/.test(text[end])) end++;
|
|
10712
|
-
return {
|
|
10713
|
-
severity,
|
|
10714
|
-
source: "dot",
|
|
10715
|
-
message: problem.reason,
|
|
10716
|
-
range: {
|
|
10717
|
-
start: document.positionAt(start),
|
|
10718
|
-
end: document.positionAt(Math.max(end, Math.min(start + 1, text.length)))
|
|
10719
|
-
}
|
|
10720
|
-
};
|
|
10721
|
-
}
|
|
10722
|
-
//#endregion
|
|
10723
|
-
//#region src/lsp/page.ts
|
|
10724
|
-
async function readPage(viewFile, readText) {
|
|
10725
|
-
const dir = dirname(viewFile);
|
|
10726
|
-
const root = findRoot(dir);
|
|
10727
|
-
const [state, controller, statics] = await Promise.all([
|
|
10728
|
-
readState(join(dir, "state.ts"), readText),
|
|
10729
|
-
readController(join(dir, "controller.ts"), readText),
|
|
10730
|
-
root === void 0 ? void 0 : listStatics(root).catch(() => void 0)
|
|
10731
|
-
]);
|
|
10732
|
-
return {
|
|
10733
|
-
root,
|
|
10734
|
-
state,
|
|
10735
|
-
controller,
|
|
10736
|
-
statics
|
|
10737
|
-
};
|
|
10738
|
-
}
|
|
10739
|
-
async function readState(file, readText) {
|
|
10740
|
-
const text = await readText(file);
|
|
10741
|
-
if (text === void 0) return;
|
|
10742
|
-
try {
|
|
10743
|
-
const { keys, keyOffsets } = parseStateModule(text, file);
|
|
10744
|
-
return {
|
|
10745
|
-
file,
|
|
10746
|
-
text,
|
|
10747
|
-
keys,
|
|
10748
|
-
offsets: keyOffsets
|
|
10749
|
-
};
|
|
10750
|
-
} catch {
|
|
10751
|
-
return;
|
|
10752
|
-
}
|
|
10753
|
-
}
|
|
10754
|
-
async function readController(file, readText) {
|
|
10755
|
-
const text = await readText(file);
|
|
10756
|
-
if (text === void 0) return;
|
|
10757
|
-
try {
|
|
10758
|
-
const { mutations, actions, handlerOffsets } = parseLogicModule(text, file);
|
|
10759
|
-
return {
|
|
10760
|
-
file,
|
|
10761
|
-
text,
|
|
10762
|
-
mutations,
|
|
10763
|
-
actions,
|
|
10764
|
-
offsets: handlerOffsets
|
|
10765
|
-
};
|
|
10766
|
-
} catch {
|
|
10767
|
-
return;
|
|
10768
|
-
}
|
|
10769
|
-
}
|
|
10770
|
-
function findRoot(dir) {
|
|
10771
|
-
while (true) {
|
|
10772
|
-
if (existsSync(join(dir, "dot.json"))) return dir;
|
|
10773
|
-
const parent = dirname(dir);
|
|
10774
|
-
if (parent === dir) return;
|
|
10775
|
-
dir = parent;
|
|
10776
|
-
}
|
|
10777
|
-
}
|
|
10778
11193
|
//#endregion
|
|
10779
11194
|
//#region src/lsp/server.ts
|
|
10780
|
-
const
|
|
10781
|
-
"
|
|
10782
|
-
"**/controller.ts",
|
|
11195
|
+
const WATCHED_FILES = [
|
|
11196
|
+
"**/*.ts",
|
|
10783
11197
|
"**/dot.json",
|
|
10784
11198
|
"**/statics/**"
|
|
10785
11199
|
];
|
|
10786
|
-
/**
|
|
10787
|
-
* Serves .dml files over the Language Server Protocol: completion, hover, go to definition and
|
|
10788
|
-
* the compiler's own errors and warnings. It runs from the dot's own installed `dotctl`, so what
|
|
10789
|
-
* it knows always matches the Dot version the dot builds with.
|
|
10790
|
-
*/
|
|
10791
11200
|
function startLanguageServer(connection) {
|
|
10792
11201
|
const documents = new import_main.TextDocuments(TextDocument);
|
|
10793
|
-
const
|
|
11202
|
+
const components = builtinRegistry;
|
|
11203
|
+
const stateTypes = new StateTypes();
|
|
10794
11204
|
let canWatchFiles = false;
|
|
10795
11205
|
const readText = async (file) => {
|
|
10796
|
-
const
|
|
10797
|
-
if (
|
|
10798
|
-
|
|
11206
|
+
const openDocument = documents.get(pathToFileURL(file).href);
|
|
11207
|
+
if (openDocument) return openDocument.getText();
|
|
11208
|
+
if (!existsSync(file)) return;
|
|
11209
|
+
return readFile(file, "utf8");
|
|
10799
11210
|
};
|
|
10800
|
-
const
|
|
11211
|
+
const openView = async (uri) => {
|
|
10801
11212
|
const document = documents.get(uri);
|
|
10802
11213
|
if (!document || !uri.endsWith(".dml")) return;
|
|
10803
11214
|
const file = fileURLToPath(uri);
|
|
10804
11215
|
return {
|
|
10805
11216
|
document,
|
|
10806
|
-
|
|
10807
|
-
file
|
|
11217
|
+
file,
|
|
11218
|
+
page: await readPage(file, readText, stateTypes)
|
|
10808
11219
|
};
|
|
10809
11220
|
};
|
|
10810
11221
|
const validate = async (uri) => {
|
|
10811
11222
|
try {
|
|
10812
|
-
const
|
|
10813
|
-
if (!
|
|
10814
|
-
const diagnostics = diagnose(
|
|
11223
|
+
const view = await openView(uri);
|
|
11224
|
+
if (!view) return;
|
|
11225
|
+
const diagnostics = diagnose(view.document, view.file, view.page, components);
|
|
10815
11226
|
await connection.sendDiagnostics({
|
|
10816
11227
|
uri,
|
|
10817
|
-
version:
|
|
11228
|
+
version: view.document.version,
|
|
10818
11229
|
diagnostics
|
|
10819
11230
|
});
|
|
10820
11231
|
} catch (error) {
|
|
@@ -10843,10 +11254,14 @@ function startLanguageServer(connection) {
|
|
|
10843
11254
|
};
|
|
10844
11255
|
});
|
|
10845
11256
|
connection.onInitialized(() => {
|
|
10846
|
-
if (canWatchFiles)
|
|
11257
|
+
if (canWatchFiles) {
|
|
11258
|
+
const watchers = WATCHED_FILES.map((globPattern) => ({ globPattern }));
|
|
11259
|
+
connection.client.register(import_main.DidChangeWatchedFilesNotification.type, { watchers });
|
|
11260
|
+
}
|
|
10847
11261
|
});
|
|
10848
11262
|
documents.onDidChangeContent(({ document }) => {
|
|
10849
|
-
document.uri.endsWith(".dml")
|
|
11263
|
+
if (document.uri.endsWith(".dml")) validate(document.uri);
|
|
11264
|
+
else validateAll();
|
|
10850
11265
|
});
|
|
10851
11266
|
documents.onDidClose(({ document }) => {
|
|
10852
11267
|
connection.sendDiagnostics({
|
|
@@ -10857,30 +11272,37 @@ function startLanguageServer(connection) {
|
|
|
10857
11272
|
connection.onDidChangeWatchedFiles(() => {
|
|
10858
11273
|
validateAll();
|
|
10859
11274
|
});
|
|
10860
|
-
|
|
10861
|
-
const
|
|
10862
|
-
if (!
|
|
10863
|
-
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);
|
|
10864
11287
|
});
|
|
10865
|
-
connection.onHover(async (
|
|
10866
|
-
const
|
|
10867
|
-
if (!
|
|
10868
|
-
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);
|
|
10869
11292
|
});
|
|
10870
|
-
connection.onDefinition(async (
|
|
10871
|
-
const
|
|
10872
|
-
if (!
|
|
10873
|
-
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);
|
|
10874
11297
|
});
|
|
10875
11298
|
documents.listen(connection);
|
|
10876
11299
|
connection.listen();
|
|
10877
11300
|
return connection;
|
|
10878
11301
|
}
|
|
10879
|
-
/** Serves an editor that started `dotctl lsp`, over stdin and stdout. */
|
|
10880
11302
|
function serveStdio() {
|
|
10881
11303
|
return startLanguageServer((0, import_node.createConnection)(import_node.ProposedFeatures.all, process.stdin, process.stdout));
|
|
10882
11304
|
}
|
|
10883
11305
|
//#endregion
|
|
10884
11306
|
export { serveStdio };
|
|
10885
11307
|
|
|
10886
|
-
//# sourceMappingURL=server-
|
|
11308
|
+
//# sourceMappingURL=server-DMnWDdJ6.js.map
|