@monimesl/dot-cli 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +34 -15
- package/dist/cli.js +252 -39
- package/dist/cli.js.map +1 -1
- package/dist/{server-z7M4FLjf.js → server-DFOUQnLA.js} +437 -60
- package/dist/server-DFOUQnLA.js.map +1 -0
- package/dist/{template → templates/showcase}/dot.ts +3 -0
- package/dist/templates/showcase/pages/carousel/controller.ts +10 -0
- package/dist/templates/showcase/pages/carousel/state.ts +40 -0
- package/dist/templates/showcase/pages/carousel/view.dml +37 -0
- package/dist/templates/showcase/pages/filters/controller.ts +28 -0
- package/dist/templates/showcase/pages/filters/state.ts +72 -0
- package/dist/templates/showcase/pages/filters/view.dml +43 -0
- package/dist/{template → templates/showcase}/pages/index/state.ts +19 -1
- package/dist/{template → templates/showcase}/pages/layout/controller.ts +3 -0
- package/dist/{template → templates/showcase}/pages/layout/state.ts +2 -0
- package/dist/{template → templates/showcase}/pages/layout/view.dml +17 -0
- package/dist/{template → templates/showcase}/pages/navigation/controller.ts +22 -0
- package/dist/{template → templates/showcase}/pages/navigation/state.ts +4 -0
- package/dist/{template → templates/showcase}/pages/navigation/view.dml +10 -0
- package/dist/templates/showcase/pages/payments/controller.ts +42 -0
- package/dist/templates/showcase/pages/payments/state.ts +10 -0
- package/dist/templates/showcase/pages/payments/view.dml +35 -0
- package/dist/templates/starter/.env +3 -0
- 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/dist/templates/starter/statics/logo.png +0 -0
- package/package.json +8 -7
- package/dist/server-z7M4FLjf.js.map +0 -1
- /package/dist/{template → templates/showcase}/.env +0 -0
- /package/dist/{template → templates/showcase}/pages/device/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/device/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/device/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/feedback/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/feedback/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/feedback/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/files/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/files/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/files/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/forms/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/forms/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/forms/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/images/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/images/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/images/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/index/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/index/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/lists/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/lists/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/lists/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/network/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/network/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/network/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/state/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/state/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/state/view.dml +0 -0
- /package/dist/{template → templates/showcase}/pages/text/controller.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/text/state.ts +0 -0
- /package/dist/{template → templates/showcase}/pages/text/view.dml +0 -0
- /package/dist/{template → templates/showcase}/statics/logo.png +0 -0
|
@@ -3,9 +3,11 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { CONTROL_FLOW_TAGS, DATA_PREFIX, ROOT_TAG, canHaveChildren, compileView, dataKey, listStatics } from "@monimesl/dot-compiler";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import {
|
|
6
|
+
import { parseDmlToIR } from "@monimesl/dot-dml-parser";
|
|
7
|
+
import { ScriptParseError, parseExpression, parseLogicModule, parseStateModule } from "@monimesl/dot-script-parser";
|
|
7
8
|
import { SourceError, builtinRegistry, eventName, eventProp, isEventProp } from "@monimesl/dot-core";
|
|
8
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import ts from "typescript";
|
|
9
11
|
//#region \0rolldown/runtime.js
|
|
10
12
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
11
13
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
@@ -10141,6 +10143,159 @@ function getWellformedEdit(textEdit) {
|
|
|
10141
10143
|
return textEdit;
|
|
10142
10144
|
}
|
|
10143
10145
|
//#endregion
|
|
10146
|
+
//#region src/lsp/shapes.ts
|
|
10147
|
+
const NUMBER = {
|
|
10148
|
+
kind: "value",
|
|
10149
|
+
type: "number"
|
|
10150
|
+
};
|
|
10151
|
+
function parseBinding(source) {
|
|
10152
|
+
try {
|
|
10153
|
+
return parseExpression(source);
|
|
10154
|
+
} catch (error) {
|
|
10155
|
+
if (error instanceof ScriptParseError) return;
|
|
10156
|
+
throw error;
|
|
10157
|
+
}
|
|
10158
|
+
}
|
|
10159
|
+
function shapeOf(expr, scope) {
|
|
10160
|
+
switch (expr.type) {
|
|
10161
|
+
case "Identifier":
|
|
10162
|
+
if (scope.loops.has(expr.name)) return scope.loops.get(expr.name);
|
|
10163
|
+
return scope.state[expr.name];
|
|
10164
|
+
case "Member": {
|
|
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 {};
|
|
10179
|
+
}
|
|
10180
|
+
}
|
|
10181
|
+
function describeMembers(shape) {
|
|
10182
|
+
return Object.keys(membersOf(shape)).join(", ") || "none";
|
|
10183
|
+
}
|
|
10184
|
+
function loopScope(variables, state) {
|
|
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;
|
|
10231
|
+
}
|
|
10232
|
+
function checkNodes(text, nodes, scope, problems) {
|
|
10233
|
+
for (const node of nodes) {
|
|
10234
|
+
const from = node.loc?.offset ?? 0;
|
|
10235
|
+
if (node.type === "Text") {
|
|
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
|
+
}
|
|
10252
|
+
}
|
|
10253
|
+
function checkBindings(text, value, from, scope, problems) {
|
|
10254
|
+
for (const [binding, source = ""] of value.matchAll(BINDING)) {
|
|
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
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
function unknownMembers(expr, scope) {
|
|
10265
|
+
const found = (node) => unknownMembers(node, scope);
|
|
10266
|
+
switch (expr.type) {
|
|
10267
|
+
case "Literal":
|
|
10268
|
+
case "Identifier": return [];
|
|
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
|
+
];
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
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
|
+
//#endregion
|
|
10144
10299
|
//#region src/lsp/markup.ts
|
|
10145
10300
|
const NAME = /[A-Za-z0-9_.:-]/;
|
|
10146
10301
|
const ATTRIBUTE_NAME_END = /[\s=/>"']/;
|
|
@@ -10253,7 +10408,8 @@ function readTag(text, start, offset, open) {
|
|
|
10253
10408
|
kind: "attributeValue",
|
|
10254
10409
|
start: valueStart,
|
|
10255
10410
|
element,
|
|
10256
|
-
attribute: name
|
|
10411
|
+
attribute: name,
|
|
10412
|
+
loopVariables: loopVariables(open)
|
|
10257
10413
|
},
|
|
10258
10414
|
end: valueEnd
|
|
10259
10415
|
};
|
|
@@ -10263,17 +10419,19 @@ function readTag(text, start, offset, open) {
|
|
|
10263
10419
|
}
|
|
10264
10420
|
}
|
|
10265
10421
|
function valueContext(valueBeforeCursor, valueStart, element, attribute, open) {
|
|
10422
|
+
const variables = loopVariables(open);
|
|
10266
10423
|
const bindingStart = valueBeforeCursor.lastIndexOf("{{");
|
|
10267
10424
|
if (bindingStart >= 0 && !valueBeforeCursor.includes("}}", bindingStart)) return {
|
|
10268
10425
|
kind: "binding",
|
|
10269
10426
|
start: valueStart + bindingStart + 2,
|
|
10270
|
-
loopVariables:
|
|
10427
|
+
loopVariables: variables
|
|
10271
10428
|
};
|
|
10272
10429
|
return {
|
|
10273
10430
|
kind: "attributeValue",
|
|
10274
10431
|
start: valueStart,
|
|
10275
10432
|
element,
|
|
10276
|
-
attribute
|
|
10433
|
+
attribute,
|
|
10434
|
+
loopVariables: variables
|
|
10277
10435
|
};
|
|
10278
10436
|
}
|
|
10279
10437
|
function closeElement(open, tag) {
|
|
@@ -10323,6 +10481,7 @@ const TAG_NAME = /[A-Za-z0-9_.:-]/;
|
|
|
10323
10481
|
const ATTRIBUTE_NAME = /[^\s=/>"']/;
|
|
10324
10482
|
const IMAGE_ATTRIBUTES = ["src", "image"];
|
|
10325
10483
|
const IDENTIFIER = /[\w$]/;
|
|
10484
|
+
const MEMBER_CHAIN = /([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*\.\s*$/;
|
|
10326
10485
|
const VALUE = /[^\s"']/;
|
|
10327
10486
|
const CONTROL_FLOW_SNIPPETS = {
|
|
10328
10487
|
If: "If condition=\"{{ $1 }}\">$0</If>",
|
|
@@ -10375,21 +10534,24 @@ function complete(document, offset, page, { components }) {
|
|
|
10375
10534
|
newText: item.insertText ?? item.label
|
|
10376
10535
|
}
|
|
10377
10536
|
}));
|
|
10378
|
-
case "attributeValue":
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10537
|
+
case "attributeValue": {
|
|
10538
|
+
const scope = shapeScope(context.loopVariables, page);
|
|
10539
|
+
return valueCompletions(context.element, context.attribute, page, scope, components).map((item) => ({
|
|
10540
|
+
...item,
|
|
10541
|
+
textEdit: {
|
|
10542
|
+
range: range(context.start),
|
|
10543
|
+
newText: item.label
|
|
10544
|
+
}
|
|
10545
|
+
}));
|
|
10546
|
+
}
|
|
10385
10547
|
case "binding": {
|
|
10386
10548
|
const typed = text.slice(context.start, offset);
|
|
10387
|
-
const
|
|
10388
|
-
|
|
10389
|
-
return bindingCompletions(context.loopVariables, page).map((item) => ({
|
|
10549
|
+
const wordStart = offset - (/[A-Za-z_$][\w$]*$/.exec(typed)?.[0] ?? "").length;
|
|
10550
|
+
const scope = shapeScope(context.loopVariables, page);
|
|
10551
|
+
return (isMember(text, context.start, wordStart) ? memberCompletions(ownerOf(text, context.start, wordStart, scope)) : bindingCompletions(context.loopVariables, page, scope)).map((item) => ({
|
|
10390
10552
|
...item,
|
|
10391
10553
|
textEdit: {
|
|
10392
|
-
range: range(
|
|
10554
|
+
range: range(wordStart),
|
|
10393
10555
|
newText: item.label
|
|
10394
10556
|
}
|
|
10395
10557
|
}));
|
|
@@ -10464,16 +10626,23 @@ function attributeCompletions(element, components) {
|
|
|
10464
10626
|
}));
|
|
10465
10627
|
return [...props, ...events];
|
|
10466
10628
|
}
|
|
10467
|
-
function valueCompletions(element, attribute, page, components) {
|
|
10468
|
-
if (element.tag === "For" && attribute === "key")
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10629
|
+
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
|
+
}
|
|
10477
10646
|
const component = components[element.tag];
|
|
10478
10647
|
if (!component) return [];
|
|
10479
10648
|
if (isEventProp(attribute) && component.events.includes(eventName(attribute))) return handlerItems(page);
|
|
@@ -10505,22 +10674,42 @@ function handlerItems(page) {
|
|
|
10505
10674
|
detail: "action"
|
|
10506
10675
|
}))];
|
|
10507
10676
|
}
|
|
10508
|
-
function bindingCompletions(loopVariables, page) {
|
|
10677
|
+
function bindingCompletions(loopVariables, page, scope) {
|
|
10509
10678
|
const items = /* @__PURE__ */ new Map();
|
|
10510
10679
|
for (const key of page.state?.keys ?? []) items.set(key, {
|
|
10511
10680
|
label: key,
|
|
10512
10681
|
kind: import_main.CompletionItemKind.Field,
|
|
10513
|
-
detail: "state",
|
|
10682
|
+
detail: scope?.state[key] ? `state: ${scope.state[key].type}` : "state",
|
|
10514
10683
|
sortText: `1${key}`
|
|
10515
10684
|
});
|
|
10516
10685
|
for (const variable of loopVariables) items.set(variable.name, {
|
|
10517
10686
|
label: variable.name,
|
|
10518
10687
|
kind: import_main.CompletionItemKind.Variable,
|
|
10519
|
-
detail: describeLoopVariable(variable),
|
|
10688
|
+
detail: describeLoopVariable(variable, scope?.loops.get(variable.name)),
|
|
10520
10689
|
sortText: `0${variable.name}`
|
|
10521
10690
|
});
|
|
10522
10691
|
return [...items.values()];
|
|
10523
10692
|
}
|
|
10693
|
+
function memberCompletions(owner) {
|
|
10694
|
+
if (!owner) return [];
|
|
10695
|
+
return Object.entries(membersOf(owner)).map(([name, shape]) => ({
|
|
10696
|
+
label: name,
|
|
10697
|
+
kind: import_main.CompletionItemKind.Field,
|
|
10698
|
+
detail: shape.type
|
|
10699
|
+
}));
|
|
10700
|
+
}
|
|
10701
|
+
function shapeScope(loopVariables, page) {
|
|
10702
|
+
const shapes = page.state?.shapes;
|
|
10703
|
+
return shapes && loopScope(loopVariables, shapes);
|
|
10704
|
+
}
|
|
10705
|
+
function isMember(text, bindingStart, start) {
|
|
10706
|
+
return text.slice(bindingStart, start).trimEnd().endsWith(".");
|
|
10707
|
+
}
|
|
10708
|
+
function ownerOf(text, bindingStart, start, scope) {
|
|
10709
|
+
const chain = MEMBER_CHAIN.exec(text.slice(bindingStart, start))?.[1];
|
|
10710
|
+
const expr = chain === void 0 ? void 0 : parseBinding(chain);
|
|
10711
|
+
return expr && scope && shapeOf(expr, scope);
|
|
10712
|
+
}
|
|
10524
10713
|
function hover(document, offset, page, { components }) {
|
|
10525
10714
|
const found = symbolAt(document.getText(), offset, page);
|
|
10526
10715
|
if (!found) return null;
|
|
@@ -10543,6 +10732,20 @@ function definition(document, offset, page) {
|
|
|
10543
10732
|
const { symbol } = found;
|
|
10544
10733
|
if (symbol.kind === "handler" && page.controller) return locationIn(page.controller.file, page.controller.text, page.controller.offsets[symbol.name]);
|
|
10545
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
|
+
};
|
|
10748
|
+
}
|
|
10546
10749
|
if (symbol.kind === "static" && page.root) return locationIn(join(page.root, symbol.path), "", 0);
|
|
10547
10750
|
return null;
|
|
10548
10751
|
}
|
|
@@ -10598,16 +10801,38 @@ function symbolAt(text, offset, page) {
|
|
|
10598
10801
|
case "binding": {
|
|
10599
10802
|
const { start, end } = wordAt(text, offset, IDENTIFIER);
|
|
10600
10803
|
const name = text.slice(start, end);
|
|
10601
|
-
if (!name
|
|
10804
|
+
if (!name) return null;
|
|
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
|
+
}
|
|
10602
10820
|
const variable = [...context.loopVariables].reverse().find((candidate) => candidate.name === name);
|
|
10603
|
-
if (variable)
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
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
|
+
}
|
|
10611
10836
|
return {
|
|
10612
10837
|
symbol: {
|
|
10613
10838
|
kind: "state",
|
|
@@ -10647,8 +10872,9 @@ function describeSymbol(symbol, page, components) {
|
|
|
10647
10872
|
if (controller?.actions?.includes(symbol.name)) return `action \`${symbol.name}\` in controller.ts`;
|
|
10648
10873
|
return null;
|
|
10649
10874
|
}
|
|
10650
|
-
case "state": return page.state?.keys?.includes(symbol.name) ? `state \`${symbol.name}\` in state.ts` : null;
|
|
10651
|
-
case "loopVariable": return `\`${symbol.variable.name}\`: ${describeLoopVariable(symbol.variable)}`;
|
|
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}\``;
|
|
10652
10878
|
case "static": return `image \`${symbol.path}\``;
|
|
10653
10879
|
}
|
|
10654
10880
|
}
|
|
@@ -10670,9 +10896,12 @@ function describeType(type) {
|
|
|
10670
10896
|
if (typeof type === "string") return type;
|
|
10671
10897
|
return type.map((value) => `"${value}"`).join(" | ");
|
|
10672
10898
|
}
|
|
10673
|
-
function describeLoopVariable(variable) {
|
|
10899
|
+
function describeLoopVariable(variable, shape) {
|
|
10674
10900
|
const list = variable.list ? ` \`${variable.list}\`` : "";
|
|
10675
|
-
return variable.role === "item" ? `each item of${list}` : `the position in${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}\`` : "";
|
|
10676
10905
|
}
|
|
10677
10906
|
function locationIn(file, text, offset) {
|
|
10678
10907
|
if (offset === void 0) return null;
|
|
@@ -10690,7 +10919,7 @@ function diagnose(document, file, page, { components }) {
|
|
|
10690
10919
|
const context = {
|
|
10691
10920
|
file,
|
|
10692
10921
|
components,
|
|
10693
|
-
onWarning: (warning) => diagnostics.push(
|
|
10922
|
+
onWarning: (warning) => diagnostics.push(sourceDiagnostic(document, warning, import_main.DiagnosticSeverity.Warning))
|
|
10694
10923
|
};
|
|
10695
10924
|
if (page.statics) context.statics = page.statics;
|
|
10696
10925
|
if (page.state?.keys) context.stateKeys = page.state.keys;
|
|
@@ -10700,19 +10929,22 @@ function diagnose(document, file, page, { components }) {
|
|
|
10700
10929
|
compileView(document.getText(), context);
|
|
10701
10930
|
} catch (error) {
|
|
10702
10931
|
if (!(error instanceof SourceError)) throw error;
|
|
10703
|
-
diagnostics.push(
|
|
10932
|
+
diagnostics.push(sourceDiagnostic(document, error, import_main.DiagnosticSeverity.Error));
|
|
10704
10933
|
}
|
|
10934
|
+
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));
|
|
10705
10935
|
return diagnostics;
|
|
10706
10936
|
}
|
|
10707
|
-
function
|
|
10937
|
+
function sourceDiagnostic(document, problem, severity) {
|
|
10938
|
+
return diagnosticAt(document, problem.loc?.offset ?? 0, problem.reason, severity);
|
|
10939
|
+
}
|
|
10940
|
+
function diagnosticAt(document, start, message, severity) {
|
|
10708
10941
|
const text = document.getText();
|
|
10709
|
-
const start = problem.loc?.offset ?? 0;
|
|
10710
10942
|
let end = start;
|
|
10711
10943
|
while (end < text.length && !/[\s>]/.test(text[end])) end++;
|
|
10712
10944
|
return {
|
|
10713
10945
|
severity,
|
|
10714
10946
|
source: "dot",
|
|
10715
|
-
message
|
|
10947
|
+
message,
|
|
10716
10948
|
range: {
|
|
10717
10949
|
start: document.positionAt(start),
|
|
10718
10950
|
end: document.positionAt(Math.max(end, Math.min(start + 1, text.length)))
|
|
@@ -10721,11 +10953,11 @@ function diagnosticAt(document, problem, severity) {
|
|
|
10721
10953
|
}
|
|
10722
10954
|
//#endregion
|
|
10723
10955
|
//#region src/lsp/page.ts
|
|
10724
|
-
async function readPage(viewFile, readText) {
|
|
10956
|
+
async function readPage(viewFile, readText, stateTypes) {
|
|
10725
10957
|
const dir = dirname(viewFile);
|
|
10726
10958
|
const root = findRoot(dir);
|
|
10727
10959
|
const [state, controller, statics] = await Promise.all([
|
|
10728
|
-
readState(join(dir, "state.ts"), readText),
|
|
10960
|
+
readState(join(dir, "state.ts"), root, readText, stateTypes),
|
|
10729
10961
|
readController(join(dir, "controller.ts"), readText),
|
|
10730
10962
|
root === void 0 ? void 0 : listStatics(root).catch(() => void 0)
|
|
10731
10963
|
]);
|
|
@@ -10736,17 +10968,27 @@ async function readPage(viewFile, readText) {
|
|
|
10736
10968
|
statics
|
|
10737
10969
|
};
|
|
10738
10970
|
}
|
|
10739
|
-
async function readState(file, readText) {
|
|
10971
|
+
async function readState(file, root, readText, stateTypes) {
|
|
10740
10972
|
const text = await readText(file);
|
|
10741
10973
|
if (text === void 0) return;
|
|
10974
|
+
let keys;
|
|
10975
|
+
let offsets;
|
|
10742
10976
|
try {
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10977
|
+
({keys, keyOffsets: offsets} = parseStateModule(text, file));
|
|
10978
|
+
} catch {
|
|
10979
|
+
return;
|
|
10980
|
+
}
|
|
10981
|
+
return {
|
|
10982
|
+
file,
|
|
10983
|
+
text,
|
|
10984
|
+
keys,
|
|
10985
|
+
offsets,
|
|
10986
|
+
shapes: readShapes(stateTypes, root, file, text)
|
|
10987
|
+
};
|
|
10988
|
+
}
|
|
10989
|
+
function readShapes(stateTypes, root, file, text) {
|
|
10990
|
+
try {
|
|
10991
|
+
return stateTypes.read(root, file, text);
|
|
10750
10992
|
} catch {
|
|
10751
10993
|
return;
|
|
10752
10994
|
}
|
|
@@ -10776,10 +11018,144 @@ function findRoot(dir) {
|
|
|
10776
11018
|
}
|
|
10777
11019
|
}
|
|
10778
11020
|
//#endregion
|
|
11021
|
+
//#region src/lsp/state-types.ts
|
|
11022
|
+
const MAX_DEPTH = 8;
|
|
11023
|
+
const FALLBACK_OPTIONS = {
|
|
11024
|
+
target: ts.ScriptTarget.ES2022,
|
|
11025
|
+
module: ts.ModuleKind.ESNext,
|
|
11026
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
11027
|
+
strict: true,
|
|
11028
|
+
noEmit: true,
|
|
11029
|
+
skipLibCheck: true
|
|
11030
|
+
};
|
|
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
|
+
var StateTypes = class {
|
|
11036
|
+
#projects = /* @__PURE__ */ new Map();
|
|
11037
|
+
read(root, file, text) {
|
|
11038
|
+
const key = root ?? file;
|
|
11039
|
+
let project = this.#projects.get(key);
|
|
11040
|
+
if (!project) {
|
|
11041
|
+
project = new Project(root);
|
|
11042
|
+
this.#projects.set(key, project);
|
|
11043
|
+
}
|
|
11044
|
+
return project.read(file, text);
|
|
11045
|
+
}
|
|
11046
|
+
};
|
|
11047
|
+
var Project = class {
|
|
11048
|
+
#options;
|
|
11049
|
+
#fileNames;
|
|
11050
|
+
#open = /* @__PURE__ */ new Map();
|
|
11051
|
+
#service;
|
|
11052
|
+
constructor(root) {
|
|
11053
|
+
const config = root === void 0 ? void 0 : readConfig(root);
|
|
11054
|
+
this.#options = config?.options ?? FALLBACK_OPTIONS;
|
|
11055
|
+
this.#fileNames = config?.fileNames ?? [];
|
|
11056
|
+
const host = {
|
|
11057
|
+
getCompilationSettings: () => this.#options,
|
|
11058
|
+
getScriptFileNames: () => [.../* @__PURE__ */ new Set([...this.#fileNames, ...this.#open.keys()])],
|
|
11059
|
+
getScriptVersion: (file) => {
|
|
11060
|
+
const open = this.#open.get(file);
|
|
11061
|
+
if (open) return String(open.version);
|
|
11062
|
+
return String(ts.sys.getModifiedTime?.(file)?.getTime() ?? 0);
|
|
11063
|
+
},
|
|
11064
|
+
getScriptSnapshot: (file) => {
|
|
11065
|
+
const text = this.#open.get(file)?.text ?? ts.sys.readFile(file);
|
|
11066
|
+
return text === void 0 ? void 0 : ts.ScriptSnapshot.fromString(text);
|
|
11067
|
+
},
|
|
11068
|
+
getCurrentDirectory: () => root ?? process.cwd(),
|
|
11069
|
+
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
11070
|
+
fileExists: (file) => ts.sys.fileExists(file),
|
|
11071
|
+
readFile: (file) => ts.sys.readFile(file),
|
|
11072
|
+
readDirectory: (...args) => ts.sys.readDirectory(...args),
|
|
11073
|
+
directoryExists: (dir) => ts.sys.directoryExists(dir),
|
|
11074
|
+
getDirectories: (dir) => ts.sys.getDirectories(dir),
|
|
11075
|
+
realpath: (path) => ts.sys.realpath?.(path) ?? path
|
|
11076
|
+
};
|
|
11077
|
+
this.#service = ts.createLanguageService(host);
|
|
11078
|
+
}
|
|
11079
|
+
read(file, text) {
|
|
11080
|
+
const open = this.#open.get(file);
|
|
11081
|
+
if (open?.text !== text) this.#open.set(file, {
|
|
11082
|
+
text,
|
|
11083
|
+
version: (open?.version ?? 0) + 1
|
|
11084
|
+
});
|
|
11085
|
+
const program = this.#service.getProgram();
|
|
11086
|
+
const source = program?.getSourceFile(file);
|
|
11087
|
+
if (!program || !source) return;
|
|
11088
|
+
const checker = program.getTypeChecker();
|
|
11089
|
+
const module = checker.getSymbolAtLocation(source);
|
|
11090
|
+
let exported = module && checker.tryGetMemberInModuleExports("default", module);
|
|
11091
|
+
if (!exported) return;
|
|
11092
|
+
if (exported.flags & ts.SymbolFlags.Alias) exported = checker.getAliasedSymbol(exported);
|
|
11093
|
+
const state = checker.getTypeOfSymbol(exported);
|
|
11094
|
+
const shapes = {};
|
|
11095
|
+
for (const property of checker.getPropertiesOfType(state)) shapes[property.name] = propertyShape(checker, property, 0, /* @__PURE__ */ new Set());
|
|
11096
|
+
return shapes;
|
|
11097
|
+
}
|
|
11098
|
+
};
|
|
11099
|
+
function readConfig(root) {
|
|
11100
|
+
const file = join(root, "tsconfig.json");
|
|
11101
|
+
if (!existsSync(file)) return;
|
|
11102
|
+
const read = ts.readConfigFile(file, (path) => ts.sys.readFile(path));
|
|
11103
|
+
if (read.error) return;
|
|
11104
|
+
return ts.parseJsonConfigFileContent(read.config, ts.sys, root, void 0, file);
|
|
11105
|
+
}
|
|
11106
|
+
function propertyShape(checker, property, depth, seen) {
|
|
11107
|
+
const shape = typeShape(checker, checker.getTypeOfSymbol(property), depth, seen);
|
|
11108
|
+
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
11109
|
+
if (!declaration) return shape;
|
|
11110
|
+
const source = declaration.getSourceFile();
|
|
11111
|
+
const { line, character } = source.getLineAndCharacterOfPosition(declaration.getStart());
|
|
11112
|
+
return {
|
|
11113
|
+
...shape,
|
|
11114
|
+
declaration: {
|
|
11115
|
+
file: source.fileName,
|
|
11116
|
+
line,
|
|
11117
|
+
character
|
|
11118
|
+
}
|
|
11119
|
+
};
|
|
11120
|
+
}
|
|
11121
|
+
function typeShape(checker, type, depth, seen) {
|
|
11122
|
+
const present = checker.getNonNullableType(type);
|
|
11123
|
+
const name = checker.typeToString(present);
|
|
11124
|
+
if (depth >= MAX_DEPTH || seen.has(present) || present.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) return {
|
|
11125
|
+
kind: "value",
|
|
11126
|
+
type: name
|
|
11127
|
+
};
|
|
11128
|
+
const inner = new Set(seen).add(present);
|
|
11129
|
+
if (checker.isArrayType(present) || checker.isTupleType(present)) {
|
|
11130
|
+
const item = checker.getIndexTypeOfType(present, ts.IndexKind.Number);
|
|
11131
|
+
return {
|
|
11132
|
+
kind: "list",
|
|
11133
|
+
type: name,
|
|
11134
|
+
item: item ? typeShape(checker, item, depth + 1, inner) : {
|
|
11135
|
+
kind: "value",
|
|
11136
|
+
type: "unknown"
|
|
11137
|
+
}
|
|
11138
|
+
};
|
|
11139
|
+
}
|
|
11140
|
+
if (!isRecord(checker, present)) return {
|
|
11141
|
+
kind: "value",
|
|
11142
|
+
type: name
|
|
11143
|
+
};
|
|
11144
|
+
const properties = {};
|
|
11145
|
+
for (const property of checker.getPropertiesOfType(present)) properties[property.name] = propertyShape(checker, property, depth + 1, inner);
|
|
11146
|
+
return {
|
|
11147
|
+
kind: "object",
|
|
11148
|
+
type: name,
|
|
11149
|
+
properties
|
|
11150
|
+
};
|
|
11151
|
+
}
|
|
11152
|
+
function isRecord(checker, type) {
|
|
11153
|
+
return (type.isUnion() ? type.types : [type]).every((member) => (member.flags & ts.TypeFlags.Object) !== 0 && member.getCallSignatures().length === 0 && checker.getIndexInfosOfType(member).length === 0);
|
|
11154
|
+
}
|
|
11155
|
+
//#endregion
|
|
10779
11156
|
//#region src/lsp/server.ts
|
|
10780
11157
|
const WATCHED = [
|
|
10781
|
-
"
|
|
10782
|
-
"**/controller.ts",
|
|
11158
|
+
"**/*.ts",
|
|
10783
11159
|
"**/dot.json",
|
|
10784
11160
|
"**/statics/**"
|
|
10785
11161
|
];
|
|
@@ -10791,6 +11167,7 @@ const WATCHED = [
|
|
|
10791
11167
|
function startLanguageServer(connection) {
|
|
10792
11168
|
const documents = new import_main.TextDocuments(TextDocument);
|
|
10793
11169
|
const features = { components: builtinRegistry };
|
|
11170
|
+
const stateTypes = new StateTypes();
|
|
10794
11171
|
let canWatchFiles = false;
|
|
10795
11172
|
const readText = async (file) => {
|
|
10796
11173
|
const open = documents.get(pathToFileURL(file).href);
|
|
@@ -10803,7 +11180,7 @@ function startLanguageServer(connection) {
|
|
|
10803
11180
|
const file = fileURLToPath(uri);
|
|
10804
11181
|
return {
|
|
10805
11182
|
document,
|
|
10806
|
-
page: await readPage(file, readText),
|
|
11183
|
+
page: await readPage(file, readText, stateTypes),
|
|
10807
11184
|
file
|
|
10808
11185
|
};
|
|
10809
11186
|
};
|
|
@@ -10883,4 +11260,4 @@ function serveStdio() {
|
|
|
10883
11260
|
//#endregion
|
|
10884
11261
|
export { serveStdio };
|
|
10885
11262
|
|
|
10886
|
-
//# sourceMappingURL=server-
|
|
11263
|
+
//# sourceMappingURL=server-DFOUQnLA.js.map
|