@monimesl/dot-cli 0.1.0-alpha.9 → 1.0.0-beta.1

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