Diagnostics

parse never throws. Every error it finds is in diagnostics, and the program holds everything it could read around them.

import { parse } from "@tsrx/yuku";

const { diagnostics } = parse(`export function Pair() @{ <h1>Title</h1> <p>Body</p> }`, { path: "Pair.tsrx" });

diagnostics[0];
// {
//   severity: "error",
//   message: "TSRX2011: A code block renders a single node; wrap multiple nodes or text in a fragment '<>…</>'.",
//   path: "Pair.tsrx",
//   start: 41,
//   end: 42,
//   labels: [],
//   help: null
// }
Field Is
severity "error", "warning", "hint", or "info"
message What went wrong. A TSRX error starts with its code, worded as @tsrx/core words it
path The path option, or null
start, end UTF-16 offsets into the source, like the offsets of nodes
labels Related spans, each { start, end, message }
help A hint, or null

Errors in the JavaScript and TypeScript around TSRX are Yuku's, without a code. semanticErrors: true also reports the early errors that need scopes, such as a let declared twice.

TSRX codes#

Each code is the one @tsrx/core reports for the same mistake. The examples below are parsed when this site is built, and the build fails if one stops reporting its code.

Code Means
TSRX1004 A <script> body holds </script, which would end it in HTML
TSRX1005 A namespaced element, such as <svg:rect>
TSRX1008 A control-flow directive without its {
TSRX1009 A clause written without its @, such as else after an @if block
TSRX1010 An @try with neither @pending nor @catch
TSRX2008 A break inside an @case
TSRX2009 A return inside an @case
TSRX2011 A code block that renders more than one node
TSRX2012 A statement after a code block's output
TSRX2014 A dynamic tag that is not an identifier, a member access, or a string
TSRX2015 An @for that is not a for...of
export function Toggle({ on }) @{
  @if (on) { <b>On</b> } else { <i>Off</i> }
}
export function Pair() @{
  <h1>Title</h1>
  <p>Body</p>
}

Wrap the two in a fragment, and the code block renders one node:

export function Pair() @{
  <>
    <h1>Title</h1>
    <p>Body</p>
  </>
}
export function Late() @{
  <p>Ready</p>
  const ready = true;
}
export function Keys({ object }) @{
  @for (const key in object) { <li>{key}</li> }
}

Show them#

A diagnostic's offsets are UTF-16, as JavaScript strings index, so source.slice(start, end) is the code it points at. A line and column come from the text before it:

for (const { message, start } of diagnostics) {
  const before = source.slice(0, start);
  const line = before.split("\n").length;
  const column = start - before.lastIndexOf("\n");
  console.log(`${line}:${column} ${message}`);
}