@statorjs/language-server 0.1.33 → 0.1.34

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 (2) hide show
  1. package/dist/server.cjs +105 -35
  2. package/package.json +2 -2
package/dist/server.cjs CHANGED
@@ -44814,6 +44814,10 @@ function codeFrame(source, line, column) {
44814
44814
  }
44815
44815
  return out.join("\n");
44816
44816
  }
44817
+ function parseDiagnosticsOf(sf) {
44818
+ const { parseDiagnostics } = sf;
44819
+ return parseDiagnostics ?? [];
44820
+ }
44817
44821
  function locAt(source, offset, file) {
44818
44822
  const { line, column } = offsetToLineCol(source, offset);
44819
44823
  return { file, line, column, frame: codeFrame(source, line, column) };
@@ -45112,6 +45116,39 @@ function lowerTemplate(template, opts = {}) {
45112
45116
  import_typescript3.default.forEachChild(node, (child) => checkNoBindingInRcdata(child, rcdataTag));
45113
45117
  };
45114
45118
  checkNoBindingInRcdata(fragment, null);
45119
+ const childMarkerOf = (attrs) => attrs.properties.find(
45120
+ (a) => import_typescript3.default.isJsxAttribute(a) && !import_typescript3.default.isJsxNamespacedName(a.name) && a.name.getText(sf) === "child"
45121
+ );
45122
+ const checkChildMarkers = (node, underComponent) => {
45123
+ if (import_typescript3.default.isJsxElement(node) || import_typescript3.default.isJsxSelfClosingElement(node)) {
45124
+ const opening = import_typescript3.default.isJsxElement(node) ? node.openingElement : node;
45125
+ const tag = opening.tagName.getText(sf);
45126
+ const marker = childMarkerOf(opening.attributes);
45127
+ if (marker) {
45128
+ const region = marker.initializer && import_typescript3.default.isStringLiteral(marker.initializer) ? marker.initializer.text : "x";
45129
+ if (isComponentTag(tag)) {
45130
+ throw new CompileError(
45131
+ `stator: child="${region}" on <${tag}/> has no effect \u2014 a child="x" marker is only read on a direct element child of a component tag; on a component it is an ignored prop. Wrap it: <div child="${region}"><${tag}/></div>`,
45132
+ loc2(marker)
45133
+ );
45134
+ }
45135
+ if (!underComponent) {
45136
+ throw new CompileError(
45137
+ `stator: child="${region}" has no effect here \u2014 a child="x" marker is only read on a direct element child of a component tag. Move the condition inside the slotted element: <div child="${region}">{cond ? \u2026 : null}</div>`,
45138
+ loc2(marker)
45139
+ );
45140
+ }
45141
+ }
45142
+ checkChildMarkers(opening.attributes, false);
45143
+ if (import_typescript3.default.isJsxElement(node)) {
45144
+ const inner = isComponentTag(tag);
45145
+ for (const child of node.children) checkChildMarkers(child, inner);
45146
+ }
45147
+ return;
45148
+ }
45149
+ import_typescript3.default.forEachChild(node, (child) => checkChildMarkers(child, false));
45150
+ };
45151
+ checkChildMarkers(fragment, false);
45115
45152
  const checkItemReadPlacement = (node, frames) => {
45116
45153
  if (import_typescript3.default.isCallExpression(node) && import_typescript3.default.isIdentifier(node.expression)) {
45117
45154
  const callee = node.expression.text;
@@ -45775,7 +45812,8 @@ function compile(source, opts = {}) {
45775
45812
  styles,
45776
45813
  file: opts.id,
45777
45814
  fm: fm2,
45778
- source
45815
+ source,
45816
+ templateOffset
45779
45817
  });
45780
45818
  }
45781
45819
  throw new CompileError(
@@ -45812,7 +45850,7 @@ function compile(source, opts = {}) {
45812
45850
  }
45813
45851
  function compileClient(template, script, ctx) {
45814
45852
  const classes = analyzeScriptClasses(script);
45815
- const root2 = extractClientRoot(template, ctx.file);
45853
+ const root2 = extractClientRoot(template, ctx);
45816
45854
  const tags = /* @__PURE__ */ new Set([root2.tag]);
45817
45855
  analyzeClient(script, tags, { file: ctx.file });
45818
45856
  const cls = classes.find((c) => pascalToKebab(c.name) === root2.tag);
@@ -45878,23 +45916,27 @@ function compileClient(template, script, ctx) {
45878
45916
  clientTag: root2.tag
45879
45917
  };
45880
45918
  }
45881
- function staticRootAttrs(attrs, sf) {
45919
+ function staticRootAttrs(tag, attrs, sf, at) {
45882
45920
  const out = {};
45883
45921
  for (const attr of attrs.properties) {
45884
- if (!import_typescript4.default.isJsxAttribute(attr) || import_typescript4.default.isJsxNamespacedName(attr.name)) continue;
45885
- const name = attr.name.getText(sf);
45886
- const init = attr.initializer;
45887
- if (init === void 0) {
45888
- out[name] = true;
45889
- } else if (import_typescript4.default.isStringLiteral(init)) {
45890
- out[name] = init.text;
45922
+ const isStatic = import_typescript4.default.isJsxAttribute(attr) && !import_typescript4.default.isJsxNamespacedName(attr.name) && (attr.initializer === void 0 || import_typescript4.default.isStringLiteral(attr.initializer));
45923
+ if (!isStatic) {
45924
+ throw new CompileError(
45925
+ `stator: <${tag} ${attr.getText(sf)}> \u2014 attributes on an island's root must be static (a string literal or a bare boolean). The root is rendered by the shell from the use site's props; a dynamic value belongs on an inner element, or declare it with \`static attrs\` and pass it from the use site.`,
45926
+ at(attr)
45927
+ );
45891
45928
  }
45929
+ const name = attr.name.getText(sf);
45930
+ out[name] = attr.initializer === void 0 ? true : attr.initializer.text;
45892
45931
  }
45893
45932
  return out;
45894
45933
  }
45895
- function extractClientRoot(template, file) {
45896
- const wrapped = `const __t = (<>${template}</>);`;
45934
+ function extractClientRoot(template, ctx) {
45935
+ const { file } = ctx;
45936
+ const prefix = "const __t = (<>";
45937
+ const wrapped = `${prefix}${template}</>);`;
45897
45938
  const sf = import_typescript4.default.createSourceFile("t.tsx", wrapped, import_typescript4.default.ScriptTarget.Latest, true, import_typescript4.default.ScriptKind.TSX);
45939
+ const at = (node) => ctx.source != null && ctx.templateOffset != null ? locAt(ctx.source, ctx.templateOffset + (node.getStart(sf) - prefix.length), file) : void 0;
45898
45940
  let fragment;
45899
45941
  const find = (n) => {
45900
45942
  if (import_typescript4.default.isJsxFragment(n)) {
@@ -45917,13 +45959,13 @@ function extractClientRoot(template, file) {
45917
45959
  if (import_typescript4.default.isJsxSelfClosingElement(rootEl)) {
45918
45960
  const tag2 = rootEl.tagName.getText(sf);
45919
45961
  requireCustomRoot(tag2, file);
45920
- return { tag: tag2, inner: "", rootAttrs: staticRootAttrs(rootEl.attributes, sf) };
45962
+ return { tag: tag2, inner: "", rootAttrs: staticRootAttrs(tag2, rootEl.attributes, sf, at) };
45921
45963
  }
45922
45964
  const el = rootEl;
45923
45965
  const tag = el.openingElement.tagName.getText(sf);
45924
45966
  requireCustomRoot(tag, file);
45925
45967
  const inner = wrapped.slice(el.openingElement.getEnd(), el.closingElement.getStart());
45926
- return { tag, inner, rootAttrs: staticRootAttrs(el.openingElement.attributes, sf) };
45968
+ return { tag, inner, rootAttrs: staticRootAttrs(tag, el.openingElement.attributes, sf, at) };
45927
45969
  }
45928
45970
  function requireCustomRoot(tag, file) {
45929
45971
  if (!isCustomElementTag(tag)) {
@@ -45943,6 +45985,21 @@ function processFrontmatter(fm, kind, source, file) {
45943
45985
  let propsType;
45944
45986
  const reads = [];
45945
45987
  const locInFm = (node) => locAt(source, fmOffset(source) + node.getStart(sf), file);
45988
+ const syntax = parseDiagnosticsOf(sf);
45989
+ if (syntax.length > 0) {
45990
+ const jsx = findJsxLikeAssertion(sf);
45991
+ if (jsx) {
45992
+ throw new CompileError(
45993
+ "stator: JSX isn't allowed in frontmatter \u2014 it's plain TypeScript. Markup belongs in the template (use when()/match() for conditional content); a different page for a not-found case cascades to routes/404.stator.",
45994
+ locInFm(jsx)
45995
+ );
45996
+ }
45997
+ const d = syntax[0];
45998
+ throw new CompileError(
45999
+ `stator: frontmatter syntax error \u2014 ${import_typescript4.default.flattenDiagnosticMessageText(d.messageText, "\n")}`,
46000
+ locAt(source, fmOffset(source) + d.start, file)
46001
+ );
46002
+ }
45946
46003
  for (const stmt of sf.statements) {
45947
46004
  if (import_typescript4.default.isImportDeclaration(stmt) || import_typescript4.default.isTypeAliasDeclaration(stmt) || import_typescript4.default.isInterfaceDeclaration(stmt)) {
45948
46005
  hoisted.push(stmt.getText(sf));
@@ -46129,6 +46186,19 @@ function requireRoute(what, kind, loc2) {
46129
46186
  );
46130
46187
  }
46131
46188
  }
46189
+ function findJsxLikeAssertion(sf) {
46190
+ let found;
46191
+ const visit = (n) => {
46192
+ if (found) return;
46193
+ if (import_typescript4.default.isTypeAssertionExpression(n) && import_typescript4.default.isTypeReferenceNode(n.type) && import_typescript4.default.isIdentifier(n.type.typeName) && !n.type.typeArguments) {
46194
+ found = n;
46195
+ return;
46196
+ }
46197
+ import_typescript4.default.forEachChild(n, visit);
46198
+ };
46199
+ visit(sf);
46200
+ return found;
46201
+ }
46132
46202
  function fmOffset(source) {
46133
46203
  return source.startsWith("---") ? source.indexOf("\n") + 1 : 0;
46134
46204
  }
@@ -46246,11 +46316,7 @@ function buildServerTsx(regions) {
46246
46316
  const userCode = fm + regions.template.content;
46247
46317
  let code = injectImports(TEMPLATE_GLOBALS, "@statorjs/stator/template", userCode) + AMBIENT_TYPE_IMPORTS + STATOR_AMBIENT;
46248
46318
  const { hoisted, body } = splitFrontmatter(fm, fmOffset2);
46249
- for (const seg of hoisted) {
46250
- push(mappings, seg.sourceOffset, code.length, seg.text.length);
46251
- code += `${seg.text}
46252
- `;
46253
- }
46319
+ for (const seg of hoisted) code = emitSegment(mappings, code, seg);
46254
46320
  let tplOffset = regions.template.contentOffset;
46255
46321
  let tpl = regions.template.content;
46256
46322
  const doctype = DOCTYPE_RE.exec(tpl);
@@ -46268,12 +46334,7 @@ function buildServerTsx(regions) {
46268
46334
  }
46269
46335
  code += `export default function (_props: ${propsT}) {
46270
46336
  `;
46271
- for (const seg of body) {
46272
- code += " ";
46273
- push(mappings, seg.sourceOffset, code.length, seg.text.length);
46274
- code += `${seg.text}
46275
- `;
46276
- }
46337
+ for (const seg of body) code = emitSegment(mappings, `${code} `, seg);
46277
46338
  code += " return (<>";
46278
46339
  code = appendTemplateTsx(code, mappings, tpl, tplOffset);
46279
46340
  code += "</>);\n}\n";
@@ -46362,13 +46423,29 @@ function expressionEnd(tpl, start) {
46362
46423
  }
46363
46424
  return tpl.length;
46364
46425
  }
46426
+ function emitSegment(mappings, code, seg) {
46427
+ if (seg.opaque) return `${code}${JSON.stringify(seg.text)};
46428
+ `;
46429
+ push(mappings, seg.sourceOffset, code.length, seg.text.length);
46430
+ return `${code}${seg.text}
46431
+ `;
46432
+ }
46365
46433
  function splitFrontmatter(fm, fmOffset2) {
46366
46434
  const hoisted = [];
46367
46435
  const body = [];
46368
46436
  if (!fm.trim()) return { hoisted, body };
46369
46437
  const sf = import_typescript7.default.createSourceFile("fm.ts", fm, import_typescript7.default.ScriptTarget.Latest, true, import_typescript7.default.ScriptKind.TS);
46438
+ const brokenLines = new Set(
46439
+ parseDiagnosticsOf(sf).map((d) => sf.getLineAndCharacterOfPosition(d.start).line)
46440
+ );
46370
46441
  for (const stmt of sf.statements) {
46371
- const seg = { sourceOffset: fmOffset2 + stmt.getStart(sf), text: stmt.getText(sf) };
46442
+ const start = stmt.getStart(sf);
46443
+ const seg = { sourceOffset: fmOffset2 + start, text: stmt.getText(sf) };
46444
+ const first = sf.getLineAndCharacterOfPosition(start).line;
46445
+ const last = sf.getLineAndCharacterOfPosition(stmt.getEnd()).line;
46446
+ for (let line = first; line <= last; line++) {
46447
+ if (brokenLines.has(line)) seg.opaque = true;
46448
+ }
46372
46449
  if (import_typescript7.default.isImportDeclaration(stmt) || import_typescript7.default.isTypeAliasDeclaration(stmt) || import_typescript7.default.isInterfaceDeclaration(stmt)) {
46373
46450
  hoisted.push(seg);
46374
46451
  } else {
@@ -46387,17 +46464,10 @@ function buildClientTsx(regions) {
46387
46464
  const { hoisted, body } = splitFrontmatter(fm, fmOffset2);
46388
46465
  for (const seg of hoisted) {
46389
46466
  if (userScript.includes(seg.text.trim())) continue;
46390
- push(mappings, seg.sourceOffset, code.length, seg.text.length);
46391
- code += `${seg.text}
46392
- `;
46467
+ code = emitSegment(mappings, code, seg);
46393
46468
  }
46394
46469
  code += "function __statorFence(props: Record<string, unknown>) {\n";
46395
- for (const seg of body) {
46396
- code += " ";
46397
- push(mappings, seg.sourceOffset, code.length, seg.text.length);
46398
- code += `${seg.text}
46399
- `;
46400
- }
46470
+ for (const seg of body) code = emitSegment(mappings, `${code} `, seg);
46401
46471
  code += "}\nvoid __statorFence\n";
46402
46472
  }
46403
46473
  for (const script of regions.scripts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statorjs/language-server",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Volar-based language server for .stator files.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "volar-service-typescript": "0.0.71",
34
34
  "vscode-css-languageservice": "^6.3.0",
35
35
  "vscode-uri": "^3.0.8",
36
- "@statorjs/stator": "2.11.0"
36
+ "@statorjs/stator": "2.12.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "typescript": "^5.6.0"