@vesk/compiler 0.2.91 → 0.2.93

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.
@@ -774,6 +774,7 @@ function generateJsxParser(acorn, acornTypeScript, Parser, jsxOptions) {
774
774
  return node;
775
775
  case tok.jsxTagStart:
776
776
  case tt.string:
777
+ case tt.backQuote:
777
778
  return this.parseExprAtom();
778
779
  default:
779
780
  this.raise(this.start, "JSX value should be either an expression or a quoted JSX text");
@@ -1 +1 @@
1
- {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAUjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAkGxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAgJlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAI5E;AAyCD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAuB9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAoFD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,SAAS,CAQtE;AAupED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAuKD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAuED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAOpQ;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAyC5J;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAG/E;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAUjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAkGxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAgJlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAI5E;AAyCD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAuB9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAoFD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,SAAS,CAQtE;AAiqED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAuKD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAwED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAOpQ;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAyC5J;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAG/E;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -539,6 +539,15 @@ class Ctx {
539
539
  // item element already claimed by key) instead of walking for a new element.
540
540
  rootClaim = false;
541
541
  walker = '__hydrate';
542
+ // Registry a hydrate-mode component call must resolve through. The hydrate
543
+ // bundle registers its functions under `__hydrators`, while non-hydrate
544
+ // nested renders (children of self-claiming components, client-only blocks,
545
+ // fresh map items) must keep calling the plain `__components` map. Emitting
546
+ // the hydrate registry name directly avoids the old global
547
+ // `__components` → `__hydrators` text rewrite, which also rewrote those
548
+ // nested fresh-render calls and handed them the 3-argument hydrator with no
549
+ // walker (`Cannot read properties of undefined (reading 'root')`).
550
+ hydrateRegistry = '__hydrators';
542
551
  asyncComps = new Set();
543
552
  isAsyncScope = false;
544
553
  // While emitting a loop body, names the boolean flag a `break` sets (see
@@ -1322,7 +1331,7 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1322
1331
  const access = calleeExpr
1323
1332
  ?? (ctx.importedNames.has(node.componentName)
1324
1333
  ? node.componentName
1325
- : `__components[${JSON.stringify(node.componentName)}]`);
1334
+ : `${ctx.hydrateRegistry}[${JSON.stringify(node.componentName)}]`);
1326
1335
  // Registry lookups can resolve to `undefined` when a JSX tag names a
1327
1336
  // component that was never declared, imported, or registered (e.g.
1328
1337
  // `<Slot/>` in a framework where `{props.children}` is the channel).
@@ -2746,7 +2755,8 @@ function buildParamInit(paramNames, propsAlias = null) {
2746
2755
  }
2747
2756
  function buildComponentMap(irRoot, hydrate = false, alloc, markerless = true) {
2748
2757
  const mapLines = [];
2749
- mapLines.push(`const __components = {};`);
2758
+ const registry = hydrate ? '__hydrators' : '__components';
2759
+ mapLines.push(`const ${registry} = {};`);
2750
2760
  const asyncComps = computeAsyncComponents(irRoot.components);
2751
2761
  // Wipe-style router components (Link/NavLink) replace their SSR children on
2752
2762
  // hydration, so their children fragments must be rebuilt in full. Track the
@@ -2788,11 +2798,11 @@ function buildComponentMap(irRoot, hydrate = false, alloc, markerless = true) {
2788
2798
  ? `__hydrate.claimOnly(undefined, (typeof __hydrate.takeSkipK === 'function' ? __hydrate.takeSkipK() : 0))`
2789
2799
  : `__hydrate.claimOnly()`;
2790
2800
  const stub = `(props, __registry, __hydrate) => { return ${claimExpr}; }`;
2791
- mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
2801
+ mapLines.push(`${registry}[${JSON.stringify(comp.name)}] = ${stub};`);
2792
2802
  continue;
2793
2803
  }
2794
2804
  const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames, alloc, markerless);
2795
- mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
2805
+ mapLines.push(`${registry}[${JSON.stringify(comp.name)}] = ${code};`);
2796
2806
  }
2797
2807
  mapLines.push(`function __cleanup(start, end) {`);
2798
2808
  mapLines.push(`\tlet n = start.nextSibling;`);
@@ -3117,13 +3127,14 @@ function emitClientFromIR(ir, options) {
3117
3127
  const importLines = ir.imports.length > 0 ? ir.imports.join('\n') + '\n' : '';
3118
3128
  const topCode = (options.includeTopLevel === false ? [] : transformTopLevelForActions(ir.topLevelCode, 'client')).join('\n') + '\n';
3119
3129
  const exportLines = [];
3130
+ const registry = options.hydrate ? '__hydrators' : '__components';
3120
3131
  for (const comp of ir.components) {
3121
3132
  if (comp.exported) {
3122
3133
  if (comp.defaultExport) {
3123
- exportLines.push(`export default __components[${JSON.stringify(comp.name)}];`);
3134
+ exportLines.push(`export default ${registry}[${JSON.stringify(comp.name)}];`);
3124
3135
  }
3125
3136
  else {
3126
- exportLines.push(`export const ${comp.name} = __components[${JSON.stringify(comp.name)}];`);
3137
+ exportLines.push(`export const ${comp.name} = ${registry}[${JSON.stringify(comp.name)}];`);
3127
3138
  }
3128
3139
  }
3129
3140
  }
package/dist/errors.d.ts CHANGED
@@ -104,6 +104,34 @@ export declare class VeskError extends Error {
104
104
  static attrJsxElement(context?: VeskErrorOptions & {
105
105
  attr?: string;
106
106
  }): VeskError;
107
+ /**
108
+ * A `{ ... }` block statement wrapping regular JS in statement mode.
109
+ * Vesk statement mode doesn't need braces around statements —
110
+ * `if (cond) { <jsx/> }` is written without the outer `{ }`.
111
+ */
112
+ static blockStatementInStatementMode(context?: VeskErrorOptions): VeskError;
113
+ /**
114
+ * An opening JSX tag was never closed, e.g. `<div><span>hello</div>`.
115
+ * Acorn reports this as "Expected corresponding JSX closing tag for <span>"
116
+ * or, when wrapped in a component body, as a generic "Unexpected token".
117
+ */
118
+ static jsxUnclosedTag(tagName: string, context?: VeskErrorOptions & {
119
+ closingTag?: string;
120
+ suggestedFix?: string;
121
+ }): VeskError;
122
+ /**
123
+ * A JSX closing tag doesn't match the most recent opening tag,
124
+ * e.g. `<div></span>`.
125
+ */
126
+ static jsxMismatchedTag(openingTag: string, closingTag: string, context?: VeskErrorOptions): VeskError;
127
+ /**
128
+ * JSX content was never terminated before end of file, e.g. `<div>` with no `>`.
129
+ */
130
+ static jsxUnterminated(context?: VeskErrorOptions): VeskError;
131
+ /**
132
+ * An empty JSX expression container `{}`, which is not allowed.
133
+ */
134
+ static jsxEmptyExpression(context?: VeskErrorOptions): VeskError;
107
135
  toString(): string;
108
136
  toJSON(): Record<string, unknown>;
109
137
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/errors.js CHANGED
@@ -389,38 +389,146 @@ export class VeskError extends Error {
389
389
  tip: 'JSX elements are content. They render inside an element or travel as a component prop — never as an attribute value on an HTML tag.',
390
390
  });
391
391
  }
392
+ /**
393
+ * A `{ ... }` block statement wrapping regular JS in statement mode.
394
+ * Vesk statement mode doesn't need braces around statements —
395
+ * `if (cond) { <jsx/> }` is written without the outer `{ }`.
396
+ */
397
+ static blockStatementInStatementMode(context = {}) {
398
+ return new VeskError('Unnecessary `{ ... }` block around statements in statement mode. Vesk statement mode does not use braces around `if`/`for`/`while`/etc. — write the statement directly.', {
399
+ ...context,
400
+ code: context.code || 'V0424',
401
+ suggestions: [
402
+ 'Remove the outer `{ }` and write the statement directly:',
403
+ ' `{ if (cond) { <div/> } }` → `if (cond) { <div/> }`',
404
+ 'Braces are only needed around JSX expression containers `{ expr }` inside JSX, not around statements.',
405
+ ],
406
+ nextSteps: [
407
+ 'Delete the opening `{` and closing `}` that wrap the statement.',
408
+ 'If you meant to group multiple statements, they can appear directly one after another in statement mode.',
409
+ ],
410
+ tip: 'Vesk statement mode accepts bare JSX, if/for/while/switch/try without wrapping braces — no `{ }` needed around statements.',
411
+ });
412
+ }
413
+ /**
414
+ * An opening JSX tag was never closed, e.g. `<div><span>hello</div>`.
415
+ * Acorn reports this as "Expected corresponding JSX closing tag for <span>"
416
+ * or, when wrapped in a component body, as a generic "Unexpected token".
417
+ */
418
+ static jsxUnclosedTag(tagName, context = {}) {
419
+ const closing = context.closingTag ?? `</${tagName}>`;
420
+ return new VeskError(`Unclosed JSX tag \`<${tagName}>\` — expected \`${closing}\` before the closing tag.`, {
421
+ ...context,
422
+ code: context.code || 'V0420',
423
+ suggestions: [
424
+ `Add \`${closing}\` to close the \`<${tagName}>\` tag.`,
425
+ `Every opening JSX tag must have a matching closing tag: \`<${tagName}></${tagName}>\`.`,
426
+ ],
427
+ nextSteps: [
428
+ context.suggestedFix ?? `Insert \`${closing}\` right before the tag that closes the parent element.`,
429
+ `Check that all JSX elements in this component are properly closed and nested.`,
430
+ ],
431
+ tip: `The \`<${tagName}>\` tag on the marked line was opened but never closed.`,
432
+ });
433
+ }
434
+ /**
435
+ * A JSX closing tag doesn't match the most recent opening tag,
436
+ * e.g. `<div></span>`.
437
+ */
438
+ static jsxMismatchedTag(openingTag, closingTag, context = {}) {
439
+ return new VeskError(`Expected closing tag \`</${openingTag}>\` to match \`<${openingTag}>\`, but found \`</${closingTag}>\`.`, {
440
+ ...context,
441
+ code: context.code || 'V0421',
442
+ suggestions: [
443
+ `Change \`</${closingTag}>\` to \`</${openingTag}>\` to close the \`<${openingTag}>\` tag.`,
444
+ `Or change the opening tag to \`<${closingTag}>\` if that was the intended element.`,
445
+ ],
446
+ nextSteps: [
447
+ `Fix the mismatched tag on the marked line so opening and closing tags agree.`,
448
+ ],
449
+ tip: `JSX closing tags must match their opening tag: \`<${openingTag}></${openingTag}>\`, not \`<${openingTag}></${closingTag}>\`.`,
450
+ });
451
+ }
452
+ /**
453
+ * JSX content was never terminated before end of file, e.g. `<div>` with no `>`.
454
+ */
455
+ static jsxUnterminated(context = {}) {
456
+ return new VeskError('Unterminated JSX contents — the JSX block was never closed before the end of the file.', {
457
+ ...context,
458
+ code: context.code || 'V0422',
459
+ suggestions: [
460
+ 'Close the JSX element with `>` or `/>` before the end of the file.',
461
+ 'Make sure every `<` that opens a JSX tag has a matching `>` (or `/>`).',
462
+ ],
463
+ nextSteps: [
464
+ 'Add the missing `>` or `/>` to close the JSX tag.',
465
+ 'Check that no string or comment is swallowing the closing bracket.',
466
+ ],
467
+ tip: 'The JSX block on the marked line was opened but never terminated.',
468
+ });
469
+ }
470
+ /**
471
+ * An empty JSX expression container `{}`, which is not allowed.
472
+ */
473
+ static jsxEmptyExpression(context = {}) {
474
+ return new VeskError('Empty JSX expression container `{}` — JSX expressions must contain a value.', {
475
+ ...context,
476
+ code: context.code || 'V0423',
477
+ suggestions: [
478
+ 'Fill the braces with an expression: `{ count }`, `{ item.name }`, or `{ true }`.',
479
+ 'If you meant literal braces, escape them: `{ \'{\' }` and `{ \'}\' }`.',
480
+ ],
481
+ nextSteps: [
482
+ 'Replace `{}` with a real expression, or remove the empty braces.',
483
+ ],
484
+ tip: '`{}` is not valid JSX — put a value inside the braces.',
485
+ });
486
+ }
392
487
  toString() {
393
- let out = this.code ? `[vesk ${this.code}] ${this.message}` : `[vesk] ${this.message}`;
488
+ const lines = [];
489
+ // Header: `error[V####]: message` — rustc-style error code first.
490
+ lines.push(this.code ? `error[${this.code}]: ${this.message}` : `error: ${this.message}`);
491
+ // Location: `--> file:line:col`
492
+ let loc = ' --> ';
493
+ let hasLoc = false;
394
494
  if (this.file) {
395
- out += `\n File: ${this.file}`;
396
- if (this.line) {
397
- out += `:${this.line}`;
398
- if (this.column)
399
- out += `:${this.column}`;
400
- }
401
- if (this.line && this.column)
402
- out += ` (line ${this.line}, column ${this.column})`;
403
- else if (this.line)
404
- out += ` (line ${this.line})`;
495
+ loc += this.file;
496
+ hasLoc = true;
405
497
  }
406
- else if (this.line) {
407
- out += `\n at line ${this.line}${this.column ? `, column ${this.column}` : ''}`;
498
+ if (this.line) {
499
+ loc += `:${this.line}`;
500
+ hasLoc = true;
501
+ }
502
+ if (this.column) {
503
+ loc += `:${this.column}`;
504
+ hasLoc = true;
408
505
  }
409
- if (this.code)
410
- out += `\n\n${this.code}`;
411
- if (this.suggestions.length) {
412
- out += '\n\n Suggestions:';
413
- for (const s of this.suggestions.slice(0, 4))
414
- out += `\n • ${s}`;
506
+ if (hasLoc)
507
+ lines.push(loc);
508
+ // Code frame: source context with a `^` caret under the offending column.
509
+ if (this.frame) {
510
+ lines.push(' |');
511
+ for (const frameLine of this.frame.split('\n')) {
512
+ lines.push(` ${frameLine}`);
513
+ }
415
514
  }
416
- if (this.nextSteps.length) {
417
- out += '\n\n Next steps:';
418
- for (const s of this.nextSteps.slice(0, 4))
419
- out += `\n • ${s}`;
515
+ else if (this.line) {
516
+ lines.push(' |');
420
517
  }
518
+ // Help lines, rustc-style `= help:` — tip first, then next steps / suggestions.
519
+ const help = [];
421
520
  if (this.tip)
422
- out += `\n\n Tip: ${this.tip}`;
423
- return out;
521
+ help.push(this.tip);
522
+ for (const s of this.nextSteps)
523
+ help.push(s);
524
+ for (const s of this.suggestions)
525
+ help.push(s);
526
+ if (help.length) {
527
+ lines.push(' |');
528
+ for (const h of help)
529
+ lines.push(`= help: ${h}`);
530
+ }
531
+ return lines.join('\n');
424
532
  }
425
533
  toJSON() {
426
534
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAuBP,MAAM,uBAAuB,CAAC;AAkW/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAsvDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsV9E"}
1
+ {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAuBP,MAAM,uBAAuB,CAAC;AAkW/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AA0yDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsV9E"}
@@ -974,6 +974,12 @@ function processAttribute(source, attr) {
974
974
  return { name, value: '' };
975
975
  if (attr.value.type === 'Literal')
976
976
  return { name, value: String(attr.value.value) };
977
+ if (attr.value.type === 'TemplateLiteral') {
978
+ // A bare backtick template literal in a JSX attribute, e.g.
979
+ // `<Link href=`/admin/learners/${id}` />`. It is a dynamic value, so it
980
+ // becomes an attribute binding rather than a static string.
981
+ return { name, value: toExpression(source, attr.value) };
982
+ }
977
983
  if (attr.value.type === 'JSXExpressionContainer') {
978
984
  const expr = attr.value.expression;
979
985
  if (expr.type === 'Literal')
@@ -1596,6 +1602,11 @@ function extractProps(source, element) {
1596
1602
  }
1597
1603
  value = toExpression(source, expr);
1598
1604
  }
1605
+ else if (attr.value.type === 'TemplateLiteral') {
1606
+ // A bare backtick template literal prop value, e.g.
1607
+ // `<Comp href=`/admin/learners/${id}` />`.
1608
+ value = toExpression(source, attr.value);
1609
+ }
1599
1610
  else {
1600
1611
  value = new Expression(JSON.stringify(attr.value.value));
1601
1612
  }
@@ -1765,6 +1776,8 @@ function isStatementMode(bodyStmts) {
1765
1776
  if (bodyStmts.some((s) => s.type === 'JSXElement' || s.type === 'JSXExpressionContainer' || s.type === 'JSXFragment'))
1766
1777
  return true;
1767
1778
  for (const stmt of bodyStmts) {
1779
+ if (stmt.type === 'BlockStatement' && hasJSXInSubtree(stmt))
1780
+ return true;
1768
1781
  if (stmt.type === 'IfStatement' && !isGuardClause(stmt) && hasJSXInSubtree(stmt))
1769
1782
  return true;
1770
1783
  if (stmt.type === 'ForOfStatement' && hasJSXInSubtree(stmt))
@@ -1928,6 +1941,57 @@ function processStatementModeBody(source, bodyStmts, filename) {
1928
1941
  if (refName)
1929
1942
  nodes.push(new ComponentRef(refName));
1930
1943
  }
1944
+ else if (stmt.type === 'VariableDeclaration') {
1945
+ // Plain variable declarations (`const items = [1, 2, 3]`, `let x = 1`)
1946
+ // that are not track() declarations are regular JavaScript. Emit them
1947
+ // verbatim as runtime statements so they execute in statement mode.
1948
+ const raw = getSource(source, stmt);
1949
+ if (raw)
1950
+ nodes.push(new RuntimeStatement(raw, stmt, source));
1951
+ }
1952
+ else if (stmt.type === 'BlockStatement') {
1953
+ // Braces around statements are unnecessary in Vesk statement mode —
1954
+ // `if (cond) { <jsx/> }` is written without the outer `{ }`. If the
1955
+ // block contains only JSX children, unwrap them; otherwise give a
1956
+ // clear error (braces around `if`/`for`/`const`/etc. are not needed).
1957
+ const innerStmts = stmt.body || [];
1958
+ const hasNonJSX = innerStmts.some((s) => s.type !== 'JSXElement' && s.type !== 'JSXExpressionContainer' && s.type !== 'JSXFragment');
1959
+ if (hasNonJSX) {
1960
+ const start = stmt.start ?? 0;
1961
+ let line = 1;
1962
+ let column = 1;
1963
+ for (let i = 0; i < start && i < source.length; i++) {
1964
+ if (source.charCodeAt(i) === 10) {
1965
+ line++;
1966
+ column = 1;
1967
+ }
1968
+ else {
1969
+ column++;
1970
+ }
1971
+ }
1972
+ throw VeskError.blockStatementInStatementMode({
1973
+ file: filename || '',
1974
+ line,
1975
+ column,
1976
+ code: codeFrame(source, line, column),
1977
+ });
1978
+ }
1979
+ // Pure JSX block: unwrap the children directly.
1980
+ for (const child of innerStmts) {
1981
+ if (child.type === 'JSXElement') {
1982
+ nodes.push(...processJSXElement(source, child));
1983
+ }
1984
+ else if (child.type === 'JSXExpressionContainer') {
1985
+ if (child.expression.type === 'JSXEmptyExpression')
1986
+ continue;
1987
+ nodes.push(...exprToIR(source, child.expression));
1988
+ }
1989
+ else if (child.type === 'JSXFragment') {
1990
+ for (const c of child.children)
1991
+ nodes.push(...processJSXChildren(source, [c]));
1992
+ }
1993
+ }
1994
+ }
1931
1995
  else if (stmt.type === 'IfStatement') {
1932
1996
  // Guard-clause early return (`if (c) return X` with no else): everything
1933
1997
  // after this statement becomes the alternate branch, mirroring
@@ -2388,7 +2452,7 @@ export function generateIR(ast, source, filename) {
2388
2452
  for (const spec of node.specifiers) {
2389
2453
  if (spec.importKind === 'type')
2390
2454
  continue;
2391
- if (spec.type === 'ImportSpecifier') {
2455
+ if (spec.type === 'ImportSpecifier' || spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
2392
2456
  importedNames.add(spec.local.name);
2393
2457
  }
2394
2458
  }
@@ -1 +1 @@
1
- {"version":3,"file":"module-imports.d.ts","sourceRoot":"","sources":["../src/module-imports.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAGlD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAyEtD,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAM7D;AAED,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC1F;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQpE;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,IAAI,CAuBN;AAiBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAsE7E;AAmFD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC;IACrC,mEAAmE;IACnE,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA+B/D;AAgbD,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU/D;AAgDD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB/E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAevE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+ClF;AA6DD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwCpF;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyB9F;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiC1F;AAsGD,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAiGxF;AAaD,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,IAAI,CAAkC;IAE9C,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAI7B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM;IASrD,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI/C;2DACuD;IACvD,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IAItC,yDAAyD;IACzD,cAAc,IAAI,MAAM;CAOzB;AAoDD,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,CA6DtF;AAID;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAAE,EACpB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,eAAe,GACzB,iBAAiB,EAAE,CAuBrB"}
1
+ {"version":3,"file":"module-imports.d.ts","sourceRoot":"","sources":["../src/module-imports.ts"],"names":[],"mappings":"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"}
@@ -259,12 +259,21 @@ export function loadSsrModule(absPath) {
259
259
  }
260
260
  else {
261
261
  exportsObj = {};
262
+ // Wrap exportsObj in a module object so CJS modules that do
263
+ // `module.exports = factory()` (e.g. qrcode-generator) have their
264
+ // export captured correctly. Without this, module.exports is replaced
265
+ // but exportsObj still points to the original empty object.
266
+ const moduleObj = { exports: exportsObj };
262
267
  EVALUATING.set(absPath, exportsObj);
263
- const ran = evaluateModuleFile(absPath, exportsObj, src);
268
+ const ran = evaluateModuleFile(absPath, moduleObj, src);
264
269
  // Runtime evaluation failure (already warned) — give Node's own loader
265
270
  // a chance before giving up.
266
271
  if (!ran)
267
272
  exportsObj = null;
273
+ // Prefer module.exports when a CJS module replaced it (e.g.
274
+ // qrcode-generator), otherwise fall back to exportsObj.
275
+ else if (moduleObj.exports !== exportsObj)
276
+ exportsObj = moduleObj.exports;
268
277
  }
269
278
  }
270
279
  if (exportsObj === null) {
@@ -601,7 +610,7 @@ function createLazyBarrelExports(spec, dir) {
601
610
  * `src` carries a read+parse already done by the caller (either a barrel probe
602
611
  * or a plain load) so big files aren't read/parsed twice.
603
612
  */
604
- function evaluateModuleFile(absPath, exportsObj, src) {
613
+ function evaluateModuleFile(absPath, moduleObj, src) {
605
614
  let raw;
606
615
  if (src) {
607
616
  raw = src.raw;
@@ -636,7 +645,7 @@ function evaluateModuleFile(absPath, exportsObj, src) {
636
645
  // Not parseable as ESM — run verbatim as CJS.
637
646
  try {
638
647
  const fn = new Function('require', 'module', 'exports', '__dirname', '__filename', raw);
639
- fn(createModuleRequire(dirname(absPath)), { exports: exportsObj }, exportsObj, dirname(absPath), absPath);
648
+ fn(createModuleRequire(dirname(absPath)), moduleObj, moduleObj.exports, dirname(absPath), absPath);
640
649
  }
641
650
  catch (err) {
642
651
  console.warn(`[vesk] SSR: failed to load ${absPath}: ${err?.message ?? String(err)}`);
@@ -661,7 +670,7 @@ function evaluateModuleFile(absPath, exportsObj, src) {
661
670
  // ESM modules are always strict; mirror per-spec `this === undefined`.
662
671
  try {
663
672
  const fn = new Function('require', 'module', 'exports', '__dirname', '__filename', "'use strict';\n" + body);
664
- fn(createModuleRequire(dirname(absPath)), { exports: exportsObj }, exportsObj, dirname(absPath), absPath);
673
+ fn(createModuleRequire(dirname(absPath)), moduleObj, moduleObj.exports, dirname(absPath), absPath);
665
674
  }
666
675
  catch (err) {
667
676
  console.warn(`[vesk] SSR: failed to load ${absPath}: ${err?.message ?? String(err)}`);
@@ -949,6 +958,12 @@ export function resolveImportPath(spec, fromDir) {
949
958
  const aliased = resolveAliasModule(spec, fromDir);
950
959
  if (aliased)
951
960
  return aliased;
961
+ // Bare npm specifiers (e.g. "vesk-qr") must resolve to absolute paths so
962
+ // the client-chunk bundler (esbuild from a temp dir) can find them in
963
+ // the app's node_modules via the package's exports map.
964
+ const resolved = resolveSsrModule(spec, fromDir);
965
+ if (resolved)
966
+ return isBuiltinPath(resolved) ? resolved.slice(BUILTIN_PREFIX.length) : resolved;
952
967
  }
953
968
  return spec;
954
969
  }
@@ -1161,8 +1176,13 @@ export function resolveViaPackageModuleField(specifier, fromDir) {
1161
1176
  if (statOrNull(pkgPath) !== null) {
1162
1177
  try {
1163
1178
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
1164
- if (typeof pkg.module === 'string' && pkg.module.length > 0) {
1165
- const found = probeFile(resolve(join(dir, 'node_modules', pkgName), pkg.module));
1179
+ const entry = typeof pkg.module === 'string' && pkg.module.length > 0
1180
+ ? pkg.module
1181
+ : typeof pkg.main === 'string' && pkg.main.length > 0
1182
+ ? pkg.main
1183
+ : null;
1184
+ if (entry) {
1185
+ const found = probeFile(resolve(join(dir, 'node_modules', pkgName), entry));
1166
1186
  if (found)
1167
1187
  return toRealPath(found);
1168
1188
  }
@@ -1486,9 +1506,6 @@ export class ModuleRegistry {
1486
1506
  return `export const __veskModules = {${parts.join(',')}};\n`;
1487
1507
  }
1488
1508
  }
1489
- function isRelativeSpec(spec) {
1490
- return spec.startsWith('./') || spec.startsWith('../');
1491
- }
1492
1509
  function stringLiteralValue(node) {
1493
1510
  const n = node;
1494
1511
  if (n && (n.type === 'Literal' || n.type === 'StringLiteral') && typeof n.value === 'string') {
@@ -1603,10 +1620,15 @@ export function collectModuleBundled(absPath, registry) {
1603
1620
  }
1604
1621
  const deps = {};
1605
1622
  for (const spec of specifiers) {
1606
- if (!isRelativeSpec(spec))
1623
+ // Compiler-owned (`@vesk/*`) targets are injected by the SSR require shim
1624
+ // at runtime (`precompile-runtime.ts`), never bundled: their built JS may
1625
+ // contain identifiers the `.vsk` grammar reserves (`component` in
1626
+ // ripple-runtime.js) and would fail to parse here. `.vsk`/`.css`/`.md`
1627
+ // carry no SSR value. Node builtins load natively through the shim.
1628
+ if (isCompilerOwnedTarget(spec) || isValueLessTarget(spec))
1607
1629
  continue;
1608
1630
  const depPath = resolveSsrModule(spec, dir);
1609
- if (!depPath)
1631
+ if (!depPath || isBuiltinPath(depPath))
1610
1632
  continue;
1611
1633
  deps[spec] = collectModuleBundled(depPath, registry);
1612
1634
  }
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAOtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,cAAc,EAAE,CAAA;CAAE,CAmMjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwFlE;AA2DD,wBAAgB,gBAAgB,IAAI,OAAO,KAAK,CAAC,MAAM,CAEtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAM/D;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CA6EzE"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAOtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,cAAc,EAAE,CAAA;CAAE,CAmMjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwFlE;AA2DD,wBAAgB,gBAAgB,IAAI,OAAO,KAAK,CAAC,MAAM,CAEtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAM/D;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CA+NzE"}
package/dist/parser.js CHANGED
@@ -573,26 +573,317 @@ export function parse(source, options = {}) {
573
573
  column = (err.loc.column ?? 0) + 1;
574
574
  }
575
575
  else {
576
- const m = err.message.match(/\((\d+):(\d+)\)/);
577
- if (m) {
578
- line = parseInt(m[1], 10);
579
- column = parseInt(m[2], 10) + 1;
576
+ // Parse "(line:column)" from the message without regex.
577
+ const parenOpen = err.message.indexOf('(');
578
+ if (parenOpen >= 0) {
579
+ const parenClose = err.message.indexOf(')', parenOpen);
580
+ if (parenClose > parenOpen) {
581
+ const inside = err.message.slice(parenOpen + 1, parenClose);
582
+ const colon = inside.indexOf(':');
583
+ if (colon > 0) {
584
+ line = parseInt(inside.slice(0, colon), 10);
585
+ column = parseInt(inside.slice(colon + 1), 10) + 1;
586
+ }
587
+ }
580
588
  }
581
589
  }
582
590
  const filename = options.filename || '';
583
- const cleanMessage = err.message.replace(/\s*\(\d+:\d+\)\s*$/, '');
584
- const frame = line > 0 ? codeFrame(source, line, column, 5, 5) : '';
591
+ // Strip the trailing "(line:column)" acorn appends, without regex.
592
+ let cleanMessage = err.message;
593
+ const lastParen = cleanMessage.lastIndexOf('(');
594
+ if (lastParen >= 0) {
595
+ const closeParen = cleanMessage.indexOf(')', lastParen);
596
+ if (closeParen > lastParen) {
597
+ const inside = cleanMessage.slice(lastParen + 1, closeParen);
598
+ const colon = inside.indexOf(':');
599
+ if (colon > 0 &&
600
+ inside.slice(0, colon).split('').every((ch) => ch >= '0' && ch <= '9') &&
601
+ inside.slice(colon + 1).split('').every((ch) => ch >= '0' && ch <= '9')) {
602
+ cleanMessage = cleanMessage.slice(0, lastParen).trimEnd();
603
+ }
604
+ }
605
+ }
606
+ // --- Convert acorn's raw JSX errors into descriptive Vesk messages ---
607
+ let message = cleanMessage;
608
+ let errLine = line;
609
+ let errColumn = column;
610
+ let code;
611
+ const extraHelp = [];
612
+ const frame = errLine > 0 ? codeFrame(source, errLine, errColumn, 5, 5) : '';
585
613
  const hint = filename ? ` in ${filename}` : '';
586
- throw new VeskError(`${cleanMessage}${hint}`, {
614
+ // "Unterminated JSX contents" — acorn hit EOF inside a JSX block.
615
+ if (cleanMessage.includes('Unterminated JSX contents')) {
616
+ throw VeskError.jsxUnterminated({
617
+ file: filename,
618
+ line: errLine,
619
+ column: errColumn,
620
+ frame,
621
+ });
622
+ }
623
+ // "JSX attributes must only be assigned a non-empty expression" — `{}`.
624
+ if (cleanMessage.includes('JSX attributes must only be assigned a non-empty expression')) {
625
+ throw VeskError.jsxEmptyExpression({
626
+ file: filename,
627
+ line: errLine,
628
+ column: errColumn,
629
+ frame,
630
+ });
631
+ }
632
+ // "Expected corresponding JSX closing tag for <X>" — acorn points at the
633
+ // opening tag, but it could mean either a mismatched closing tag
634
+ // (<div></span>) or a genuinely unclosed tag (<div><span>...</div>).
635
+ const jsxClosePrefix = 'Expected corresponding JSX closing tag for <';
636
+ if (cleanMessage.startsWith(jsxClosePrefix)) {
637
+ const mismatched = findMismatchedJsxTag(source, errLine);
638
+ if (mismatched) {
639
+ throw VeskError.jsxMismatchedTag(mismatched.openingTag, mismatched.closingTag, {
640
+ file: filename,
641
+ line: mismatched.line,
642
+ column: mismatched.column,
643
+ frame: codeFrame(source, mismatched.line, mismatched.column, 5, 5),
644
+ });
645
+ }
646
+ const rest = cleanMessage.slice(jsxClosePrefix.length);
647
+ const gt = rest.indexOf('>');
648
+ const tagName = gt >= 0 ? rest.slice(0, gt) : rest;
649
+ throw VeskError.jsxUnclosedTag(tagName, {
650
+ file: filename,
651
+ line: errLine,
652
+ column: errColumn,
653
+ frame,
654
+ suggestedFix: `Insert </${tagName}> before the tag that closes the parent element.`,
655
+ });
656
+ }
657
+ // "Unexpected token" inside a component body often means a JSX tag was
658
+ // opened but never closed — acorn reports it late, at the closing `}`.
659
+ if (cleanMessage.includes('Unexpected token') && errLine > 0) {
660
+ const srcLine = (source.split('\n')[errLine - 1] ?? '').trim();
661
+ const isBlockCloser = srcLine.startsWith('}') || srcLine.startsWith(')');
662
+ if (isBlockCloser) {
663
+ const unclosed = findUnclosedJsxTag(source, errLine);
664
+ if (unclosed) {
665
+ throw VeskError.jsxUnclosedTag(unclosed.tagName, {
666
+ file: filename,
667
+ line: unclosed.line,
668
+ column: unclosed.column,
669
+ frame: errLine > 0 ? codeFrame(source, unclosed.line, unclosed.column, 5, 5) : '',
670
+ suggestedFix: `Insert </${unclosed.tagName}> before the tag that closes the parent element.`,
671
+ });
672
+ }
673
+ }
674
+ // `{if (cond) { <jsx/> }}` — LLMs often wrap statements in braces in
675
+ // statement mode, but Vesk doesn't need them. Look for `{` immediately
676
+ // before the offending token (skipping whitespace) followed by a
677
+ // statement keyword (`if`, `for`, `while`, `switch`, `try`, `const`,
678
+ // `let`, `var`, `function`, `class`, `return`).
679
+ if (errColumn > 1) {
680
+ const lineText = source.split('\n')[errLine - 1] ?? '';
681
+ // Walk back from the error column to find the `{` that opened the
682
+ // block (or JSX expression container) that shouldn't be there.
683
+ let j = errColumn - 2; // 0-based index of char before error token
684
+ while (j >= 0 && (lineText[j] === ' ' || lineText[j] === '\t'))
685
+ j--;
686
+ if (j >= 0 && lineText[j] === '{') {
687
+ // Read the keyword after `{` (skipping whitespace)
688
+ let k = errColumn - 1; // 0-based index of error token start
689
+ while (k < lineText.length && (lineText[k] === ' ' || lineText[k] === '\t'))
690
+ k++;
691
+ let keyword = '';
692
+ while (k < lineText.length && ((lineText[k] >= 'a' && lineText[k] <= 'z') || (lineText[k] >= 'A' && lineText[k] <= 'Z') || lineText[k] === '_' || lineText[k] === '$')) {
693
+ keyword += lineText[k];
694
+ k++;
695
+ }
696
+ const statementKeywords = new Set(['if', 'for', 'while', 'switch', 'try', 'const', 'let', 'var', 'function', 'class', 'return', 'do', 'with']);
697
+ if (statementKeywords.has(keyword)) {
698
+ const startLine = errLine;
699
+ const startColumn = j + 1; // 1-based column of `{`
700
+ throw VeskError.blockStatementInStatementMode({
701
+ file: filename,
702
+ line: startLine,
703
+ column: startColumn,
704
+ frame: codeFrame(source, startLine, startColumn, 5, 5),
705
+ });
706
+ }
707
+ }
708
+ }
709
+ }
710
+ // Targeted help for a bare template literal used as a JSX attribute value,
711
+ // e.g. <Link href=`/admin/learners/${id}`>. JSX attributes must be a quoted
712
+ // string or a `{...}` expression; a backtick on its own is not valid JSX.
713
+ if (errLine > 0 &&
714
+ cleanMessage.includes('JSX value should be either an expression or a quoted JSX text')) {
715
+ const srcLine = (source.split('\n')[errLine - 1] ?? '');
716
+ if (srcLine.includes('`')) {
717
+ extraHelp.push('JSX attribute values must be a quoted string (`href="..."`) or a `{...}` expression (`href={expr}`).', 'A bare backtick template literal is not valid JSX — wrap it in braces:', ' href={`/admin/learners/${id}`}');
718
+ }
719
+ }
720
+ throw new VeskError(`${message}${hint}`, {
587
721
  file: filename,
588
- line,
589
- column,
590
- code: frame,
591
- nextSteps: [
592
- 'Check the line indicated by the ^ marker for missing brackets, quotes, or JSX syntax.',
593
- 'If you wrote literal { or } inside JSX text, escape them as {\'{\'} and {\'}\'} or use &lbrace; &rbrace;.',
594
- 'Ensure all JSX tags are properly closed and component names start with an uppercase letter.',
595
- ],
722
+ line: errLine,
723
+ column: errColumn,
724
+ frame,
725
+ code,
726
+ nextSteps: extraHelp.length ? extraHelp : [],
596
727
  });
597
728
  }
598
729
  }
730
+ /**
731
+ * Scan the source (character by character, no regex) tracking JSX opening and
732
+ * closing tags up to `upToLine`. Returns the most recently opened tag that
733
+ * was never closed, or `null` if all tags are balanced. Used only to produce
734
+ * a better error message when acorn reports a generic "Unexpected token"
735
+ * for an unclosed JSX tag.
736
+ */
737
+ function findUnclosedJsxTag(source, upToLine) {
738
+ const stack = [];
739
+ let i = 0;
740
+ let currentLine = 1;
741
+ const isIdentStart = (code) => (code >= 97 && code <= 122) || (code >= 65 && code <= 90) || code === 95; // a-z A-Z _
742
+ while (i < source.length) {
743
+ if (source[i] === '\n') {
744
+ currentLine++;
745
+ i++;
746
+ if (currentLine > upToLine)
747
+ break;
748
+ continue;
749
+ }
750
+ if (currentLine > upToLine)
751
+ break;
752
+ if (source[i] === '<') {
753
+ // Closing tag: </tagName>
754
+ if (i + 1 < source.length && source[i + 1] === '/') {
755
+ let j = i + 2;
756
+ while (j < source.length && source[j] !== '>' && source[j] !== '\n')
757
+ j++;
758
+ const tagName = source.slice(i + 2, j);
759
+ // Only pop if it matches the most recently opened unclosed tag.
760
+ if (stack.length > 0 && stack[stack.length - 1].tagName === tagName) {
761
+ stack.pop();
762
+ }
763
+ // Skip past the closing >.
764
+ while (j < source.length && source[j] !== '>')
765
+ j++;
766
+ if (j < source.length)
767
+ j++;
768
+ i = j;
769
+ continue;
770
+ }
771
+ // Opening tag: <tagName ...> (skip namespace prefixes like ns.)
772
+ let j = i + 1;
773
+ while (j < source.length &&
774
+ (source[j] === '.' || source[j] === ':' ||
775
+ (source.charCodeAt(j) >= 97 && source.charCodeAt(j) <= 122) ||
776
+ (source.charCodeAt(j) >= 65 && source.charCodeAt(j) <= 90) ||
777
+ source.charCodeAt(j) === 95)) {
778
+ j++;
779
+ }
780
+ const tagName = source.slice(i + 1, j);
781
+ if (tagName.length > 0 && isIdentStart(tagName.charCodeAt(0))) {
782
+ // Check for self-closing: <tagName />
783
+ let k = j;
784
+ let selfClosing = false;
785
+ while (k < source.length && source[k] !== '>') {
786
+ if (source[k] === '/' && k + 1 < source.length && source[k + 1] === '>') {
787
+ selfClosing = true;
788
+ break;
789
+ }
790
+ k++;
791
+ }
792
+ if (!selfClosing) {
793
+ const loc = offsetToLineCol(source, i);
794
+ stack.push({ tagName, line: loc.line, column: loc.column });
795
+ }
796
+ }
797
+ // Skip past the closing >.
798
+ while (j < source.length && source[j] !== '>')
799
+ j++;
800
+ if (j < source.length)
801
+ j++;
802
+ i = j;
803
+ continue;
804
+ }
805
+ i++;
806
+ }
807
+ // The most recently opened tag still on the stack is the unclosed one.
808
+ if (stack.length > 0) {
809
+ return stack[stack.length - 1];
810
+ }
811
+ return null;
812
+ }
813
+ /**
814
+ * Scan the source tracking JSX tags up to `upToLine`. Returns the first
815
+ * closing tag that does not match the most recently opened tag,
816
+ * e.g. `</span>` after `<div>`. Character scan only — no regex.
817
+ */
818
+ function findMismatchedJsxTag(source, upToLine) {
819
+ const stack = [];
820
+ let i = 0;
821
+ let currentLine = 1;
822
+ const isIdentStart = (code) => (code >= 97 && code <= 122) || (code >= 65 && code <= 90) || code === 95;
823
+ while (i < source.length) {
824
+ if (source[i] === '\n') {
825
+ currentLine++;
826
+ i++;
827
+ if (currentLine > upToLine)
828
+ break;
829
+ continue;
830
+ }
831
+ if (currentLine > upToLine)
832
+ break;
833
+ if (source[i] === '<') {
834
+ // Closing tag: </tagName>
835
+ if (i + 1 < source.length && source[i + 1] === '/') {
836
+ let j = i + 2;
837
+ while (j < source.length && source[j] !== '>' && source[j] !== '\n')
838
+ j++;
839
+ const closingTag = source.slice(i + 2, j);
840
+ while (j < source.length && source[j] !== '>')
841
+ j++;
842
+ if (j < source.length)
843
+ j++;
844
+ const loc = offsetToLineCol(source, i);
845
+ // Mismatch: a closing tag that doesn't match the most recent opening tag.
846
+ if (stack.length > 0 && stack[stack.length - 1] !== closingTag) {
847
+ return { openingTag: stack[stack.length - 1], closingTag, line: loc.line, column: loc.column };
848
+ }
849
+ // Match: pop the stack.
850
+ if (stack.length > 0)
851
+ stack.pop();
852
+ i = j;
853
+ continue;
854
+ }
855
+ // Opening tag: <tagName ...>
856
+ let j = i + 1;
857
+ while (j < source.length &&
858
+ (source[j] === '.' || source[j] === ':' ||
859
+ (source.charCodeAt(j) >= 97 && source.charCodeAt(j) <= 122) ||
860
+ (source.charCodeAt(j) >= 65 && source.charCodeAt(j) <= 90) ||
861
+ source.charCodeAt(j) === 95)) {
862
+ j++;
863
+ }
864
+ const tagName = source.slice(i + 1, j);
865
+ if (tagName.length > 0 && isIdentStart(tagName.charCodeAt(0))) {
866
+ // Self-closing: <tagName />
867
+ let k = j;
868
+ let selfClosing = false;
869
+ while (k < source.length && source[k] !== '>') {
870
+ if (source[k] === '/' && k + 1 < source.length && source[k + 1] === '>') {
871
+ selfClosing = true;
872
+ break;
873
+ }
874
+ k++;
875
+ }
876
+ if (!selfClosing)
877
+ stack.push(tagName);
878
+ }
879
+ while (j < source.length && source[j] !== '>')
880
+ j++;
881
+ if (j < source.length)
882
+ j++;
883
+ i = j;
884
+ continue;
885
+ }
886
+ i++;
887
+ }
888
+ return null;
889
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.ts"],"names":[],"mappings":"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"}
@@ -463,23 +463,24 @@ export function buildDataScripts(ssrProps, ssrData, external) {
463
463
  return scripts;
464
464
  }
465
465
  export async function renderFullPage(source, componentName, props = {}, registry = new Map(), options = {}) {
466
- return withSsrStore(async () => {
467
- globalThis.__vsk_ssr = true;
468
- // Reuse the request token when a page/layout render already started one (the
469
- // generated adapter handler renders page -> layouts -> document within one
470
- // process), otherwise start fresh. One request = one token = one data slot.
471
- if (!globalThis.__vsk_ssr_token)
472
- globalThis.__vsk_ssr_token = Math.random().toString(36).slice(2);
473
- const renderToken = globalThis.__vsk_ssr_token;
474
- keepSsrSlot(renderToken);
475
- pruneSsrDataSlots();
476
- // Start this render's handoff store fresh: setSsrData mirrors into
477
- // globalThis.__vsk_ssr_data (see resource.ts) because the AsyncLocalStorage
478
- // sink snapshot can be empty when the write runs in a forked async context.
479
- // Resetting here also gives the /about-after-/posts no-leak behavior the
480
- // ALS isolation was introduced to fix.
481
- globalThis.__vsk_ssr_data = {};
482
- try {
466
+ // Adopt the ambient request store when one is open: renderPage runs in the
467
+ // enclosing withSsrStore scope and attributes setSsrData writes to that
468
+ // store's token, so renderFullPage must read the same slot to hand off SSR
469
+ // data. Only mint a fresh store when called stand-alone (no ambient scope),
470
+ // which keeps concurrent requests isolated through their distinct tokens.
471
+ const store = adoptSsrStore();
472
+ const renderToken = store.token;
473
+ keepSsrSlot(renderToken);
474
+ pruneSsrDataSlots();
475
+ globalThis.__vsk_ssr = true;
476
+ // Start this render's handoff store fresh: setSsrData mirrors into
477
+ // globalThis.__vsk_ssr_data (see resource.ts) because the AsyncLocalStorage
478
+ // sink snapshot can be empty when the write runs in a forked async context.
479
+ // Resetting here also gives the /about-after-/posts no-leak behavior.
480
+ globalThis.__vsk_ssr_data = {};
481
+ let result;
482
+ try {
483
+ result = await withSsrStoreOf(store, async () => {
483
484
  let ssrProps = { ...props };
484
485
  let serializedProps = null;
485
486
  const cached = options.cached || undefined;
@@ -500,18 +501,20 @@ export async function renderFullPage(source, componentName, props = {}, registry
500
501
  // Defensive settle: page/layout renders already awaited their own SSR
501
502
  // promises before cleanup; this covers any resource started here (loadFn).
502
503
  await settleSsrPromises(renderToken);
503
- // Merge the ALS sink snapshot with the request's per-token data slot. The
504
- // slot is the deterministic channel for writes that happened in a forked
505
- // async context (native fetch) — the sink alone can miss them. The slot is
506
- // request-scoped (deleted below), so a late write can never leak into a
507
- // following request the way the flat global would.
504
+ // Merge the request's per-token data slot. The slot is the deterministic
505
+ // channel for writes that happened in a forked async context (native fetch
506
+ // .then() callbacks run outside the ALS scope) and is keyed per render
507
+ // token, so concurrent renders never mix data. The ALS sink (ssrSink) is
508
+ // NOT read here because withSsrStore reuses the ambient store when one is
509
+ // already open, so concurrent renderFullPage calls share a single sink and
510
+ // their writes would cross-contaminate. Every setSsrData write also lands
511
+ // in the slot, so the slot alone is a complete handoff source.
508
512
  const slot = globalThis[`__vsk_ssr_data_${renderToken}`] || {};
509
513
  const ssrData = {
510
- ...ssrSink.snapshot(),
511
514
  ...slot,
512
515
  };
513
516
  if (process.env.VESK_SSR_TRACE) {
514
- console.error(`[render-trace] renderFullPage comp=${componentName} keys=${Object.keys(ssrData).join(',') || '∅'} slot=${Object.keys(slot).join(',') || '∅'} sink=${Object.keys(ssrSink.snapshot()).join(',') || '∅'}`);
517
+ console.error(`[render-trace] renderFullPage comp=${componentName} keys=${Object.keys(ssrData).join(',') || '∅'} slot=${Object.keys(slot).join(',') || '∅'}`);
515
518
  }
516
519
  let headHtml = rendered.head;
517
520
  if (options.pageHead) {
@@ -565,17 +568,15 @@ ${bodyHtml}
565
568
  ${dataScriptBlock}${clientScript}</body>
566
569
  </html>`;
567
570
  return applyHtmlPlugins(docHtml, options.plugins, { sourcePath: options.sourcePath, url: options.__vesk?.url || undefined });
568
- }
569
- finally {
570
- delete globalThis.__vsk_ssr;
571
- delete globalThis[`__vsk_ssr_data_${renderToken}`];
572
- // The token lives in the AsyncLocalStorage store now (see ssr-store.ts);
573
- // it goes away with the scope. Deleting the global would destroy the
574
- // request-scoped accessor that generated render code reads.
575
- dropSsrSlot(renderToken);
576
- pruneSsrDataSlots();
577
- }
578
- });
571
+ });
572
+ }
573
+ finally {
574
+ delete globalThis.__vsk_ssr;
575
+ delete globalThis[`__vsk_ssr_data_${renderToken}`];
576
+ dropSsrSlot(renderToken);
577
+ pruneSsrDataSlots();
578
+ }
579
+ return result;
579
580
  }
580
581
  export function renderPageStream(source, componentName, props = {}, registry = new Map(), options = {}) {
581
582
  // One store for the whole stream. `next()` is driven by the consumer, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/compiler",
3
- "version": "0.2.91",
3
+ "version": "0.2.93",
4
4
  "type": "module",
5
5
  "description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@sveltejs/acorn-typescript": "^1.0.0",
23
- "@vesk/types": "^0.2.91",
23
+ "@vesk/types": "^0.2.93",
24
24
  "acorn": "^8.15.0",
25
25
  "esrap": "^2.3.0",
26
26
  "is-reference": "^3.0.0",