@lucidkiln/core 1.0.0 → 1.1.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"format-CJoqhehX.js","names":[],"sources":["../src/formatter/delimiters.ts","../src/formatter/format.ts"],"sourcesContent":["import type { DelimitedPart, Token } from \"../lexer/lexer.js\";\n\n/** A token as the formatter prints it: its source text, unchanged. An interpolated string is one piece. */\ntype Piece = {\n\ttype: Token[\"type\"];\n\ttext: string;\n\tstart: number;\n\tend: number;\n};\n\n/**\n * What a piece does, where that changes its spacing.\n *\n * - `word`: a name, number or string.\n * - `keyword`: a tag's name, or a name such as `and` or `in`. A bracket after it groups rather than calls.\n * - `unary`: a sign that makes a number negative or positive, written against its operand.\n * - `tight`: `.`, `?.`, `..` and a filter's `|`, with no space either side.\n * - `key`: the colon after a key or named argument, or `?` and `?:` marking an optional key in `props`.\n * - `call`: a bracket that calls or indexes the piece before it.\n * - `open`, `close`: other brackets.\n * - `spaced`: operators with a space either side.\n */\ntype Role =\n\t| \"word\"\n\t| \"keyword\"\n\t| \"unary\"\n\t| \"tight\"\n\t| \"key\"\n\t| \"call\"\n\t| \"open\"\n\t| \"close\"\n\t| \"comma\"\n\t| \"spread\"\n\t| \"spaced\";\n\nconst KEYWORDS = new Set([\n\t\"and\",\n\t\"or\",\n\t\"not\",\n\t\"in\",\n\t\"is\",\n\t\"starts\",\n\t\"ends\",\n\t\"with\",\n\t\"as\",\n\t\"import\",\n\t\"ignore\",\n\t\"missing\",\n]);\n\n/** Joins each interpolated string's tokens, from `stringStart` to its matching `stringEnd`, into one piece. */\nconst toPieces = (source: string, tokens: readonly Token[]): Piece[] => {\n\tconst pieces: Piece[] = [];\n\tfor (let index = 0; index < tokens.length; index++) {\n\t\tconst token = tokens[index] as Token;\n\t\tlet end = token.end;\n\t\tif (token.type === \"stringStart\") {\n\t\t\tlet depth = 0;\n\t\t\tfor (; index < tokens.length; index++) {\n\t\t\t\tconst inner = tokens[index] as Token;\n\t\t\t\tif (inner.type === \"stringStart\") depth++;\n\t\t\t\telse if (inner.type === \"stringEnd\" && --depth === 0) break;\n\t\t\t}\n\t\t\tend = (tokens[Math.min(index, tokens.length - 1)] as Token).end;\n\t\t}\n\t\tpieces.push({\n\t\t\ttype: token.type === \"stringStart\" ? \"string\" : token.type,\n\t\t\ttext: source.slice(token.start, end),\n\t\t\tstart: token.start,\n\t\t\tend,\n\t\t});\n\t}\n\treturn pieces;\n};\n\n/** Works out each piece's role. `isTag` means the first piece is a tag's name. */\nconst rolesOf = (pieces: readonly Piece[], isTag: boolean): Role[] => {\n\tconst roles: Role[] = [];\n\t// In `props`, `|` joins types and `?:` marks an optional key, until `=` starts a default value.\n\tconst props = isTag && pieces[0]?.text === \"props\";\n\tlet inDefault = false;\n\t// Open `?`s at each bracket depth, so a conditional's `:` can be told from a key's.\n\tconst conditionals: number[] = [0];\n\n\tfor (const [index, piece] of pieces.entries()) {\n\t\tconst previous = roles[index - 1];\n\t\tconst depth = conditionals.length - 1;\n\t\tconst types = props && !inDefault;\n\t\tlet role: Role;\n\n\t\tif (piece.type !== \"punct\") {\n\t\t\trole =\n\t\t\t\t(isTag && index === 0) ||\n\t\t\t\t(piece.type === \"name\" && KEYWORDS.has(piece.text))\n\t\t\t\t\t? \"keyword\"\n\t\t\t\t\t: \"word\";\n\t\t} else {\n\t\t\tswitch (piece.text) {\n\t\t\t\tcase \"(\":\n\t\t\t\tcase \"[\":\n\t\t\t\t\trole = previous === \"word\" || previous === \"close\" ? \"call\" : \"open\";\n\t\t\t\t\tconditionals.push(0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"{\":\n\t\t\t\t\trole = \"open\";\n\t\t\t\t\tconditionals.push(0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \")\":\n\t\t\t\tcase \"]\":\n\t\t\t\tcase \"}\":\n\t\t\t\t\trole = \"close\";\n\t\t\t\t\tif (conditionals.length > 1) conditionals.pop();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \",\":\n\t\t\t\t\trole = \"comma\";\n\t\t\t\t\tif (props && depth === 0) inDefault = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"...\":\n\t\t\t\t\trole = \"spread\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \".\":\n\t\t\t\tcase \"?.\":\n\t\t\t\tcase \"..\":\n\t\t\t\t\trole = \"tight\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"|\":\n\t\t\t\t\trole = types ? \"spaced\" : \"tight\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"-\":\n\t\t\t\tcase \"+\":\n\t\t\t\t\trole =\n\t\t\t\t\t\tprevious === undefined ||\n\t\t\t\t\t\t(previous !== \"word\" && previous !== \"close\")\n\t\t\t\t\t\t\t? \"unary\"\n\t\t\t\t\t\t\t: \"spaced\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"?\":\n\t\t\t\tcase \"?:\":\n\t\t\t\t\trole = types ? \"key\" : \"spaced\";\n\t\t\t\t\tif (piece.text === \"?\" && !types) {\n\t\t\t\t\t\tconditionals[depth] = (conditionals[depth] ?? 0) + 1;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \":\":\n\t\t\t\t\tif ((conditionals[depth] ?? 0) > 0) {\n\t\t\t\t\t\tconditionals[depth] = (conditionals[depth] ?? 0) - 1;\n\t\t\t\t\t\trole = \"spaced\";\n\t\t\t\t\t} else role = \"key\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\trole = \"spaced\";\n\t\t\t\t\tif (props && depth === 0 && piece.text === \"=\") inDefault = true;\n\t\t\t}\n\t\t}\n\t\troles.push(role);\n\t}\n\treturn roles;\n};\n\n/** The gap between two pieces: a space or nothing. */\nconst gapBetween = (\n\tprevious: Piece,\n\tpreviousRole: Role,\n\tpiece: Piece,\n\trole: Role,\n): string => {\n\tif (role === \"comma\" || role === \"tight\" || role === \"key\" || role === \"call\")\n\t\treturn \"\";\n\tif (role === \"close\") {\n\t\treturn piece.text === \"}\" && previous.text !== \"{\" ? \" \" : \"\";\n\t}\n\tif (previousRole === \"tight\" || previousRole === \"unary\") return \"\";\n\tif (previousRole === \"spread\") return \"\";\n\tif (previousRole === \"open\" || previousRole === \"call\") {\n\t\treturn previous.text === \"{\" ? \" \" : \"\";\n\t}\n\treturn \" \";\n};\n\n/**\n * Prints one output or tag with normalised spacing. Line breaks between tokens stay; each line after one is indented from `indent`, one step further or to match the brackets it sits inside, using `unit` per step.\n *\n * Returns null for a delimiter that never closed, which the formatter leaves as it is.\n */\nexport const printDelimiter = (\n\tsource: string,\n\tpart: DelimitedPart,\n\tindent: string,\n\tunit: string,\n): string | null => {\n\tconst tokens = part.tokens;\n\tconst open = tokens[0];\n\tconst close = tokens[tokens.length - 1];\n\tif (!open || !close || close.value === \"\" || tokens.length < 3) return null;\n\n\tconst pieces = toPieces(source, tokens.slice(1, -1));\n\tconst roles = rolesOf(pieces, part.kind === \"tag\");\n\tlet out = open.value;\n\tlet previousEnd = part.start + open.value.length;\n\t// The line each open bracket started on. Brackets opened on the same line add one step between them.\n\tconst openedOn: number[] = [];\n\tlet line = 0;\n\t/** Steps of indentation for a new line, once `closers` brackets at its start have closed. */\n\tconst steps = (closers: number) =>\n\t\tnew Set(openedOn.slice(0, openedOn.length - closers)).size;\n\n\tfor (const [index, piece] of pieces.entries()) {\n\t\tconst role = roles[index] as Role;\n\t\tconst gap = source.slice(previousEnd, piece.start);\n\t\tif (gap.includes(\"\\n\")) {\n\t\t\tline++;\n\t\t\t// Closing brackets at the start of a line line up with the line that opened them. Only those on this line count.\n\t\t\tlet closers = 0;\n\t\t\twhile (\n\t\t\t\troles[index + closers] === \"close\" &&\n\t\t\t\t(closers === 0 ||\n\t\t\t\t\t!source\n\t\t\t\t\t\t.slice(\n\t\t\t\t\t\t\t(pieces[index + closers - 1] as Piece).end,\n\t\t\t\t\t\t\t(pieces[index + closers] as Piece).start,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.includes(\"\\n\"))\n\t\t\t) {\n\t\t\t\tclosers++;\n\t\t\t}\n\t\t\tout += `\\n${indent}${unit.repeat(closers > 0 ? steps(closers) : Math.max(steps(0), 1))}`;\n\t\t} else if (index === 0) out += \" \";\n\t\telse {\n\t\t\tout += gapBetween(\n\t\t\t\tpieces[index - 1] as Piece,\n\t\t\t\troles[index - 1] as Role,\n\t\t\t\tpiece,\n\t\t\t\trole,\n\t\t\t);\n\t\t}\n\t\tout += piece.text;\n\t\tif (role === \"open\" || role === \"call\") openedOn.push(line);\n\t\telse if (role === \"close\") openedOn.pop();\n\t\tpreviousEnd = piece.end;\n\t}\n\n\tconst closeStart = part.end - close.value.length;\n\tout += source.slice(previousEnd, closeStart).includes(\"\\n\")\n\t\t? `\\n${indent}`\n\t\t: \" \";\n\treturn out + close.value;\n};\n","import { builtinTests } from \"../builtins/functions.js\";\nimport { KilnSyntaxError } from \"../errors.js\";\nimport { createDiagnostic } from \"../errors.js\";\nimport { type DelimitedPart, type Part, scanParts } from \"../lexer/lexer.js\";\nimport { parse } from \"../parser/parser.js\";\nimport { Source } from \"../source.js\";\nimport { printDelimiter } from \"./delimiters.js\";\n\nexport type FormatOptions = {\n\t/** Indent with tabs. Default `true`. */\n\tuseTabs?: boolean;\n\t/** Spaces per step without tabs, and the width of a tab when moving lines. A whole number of at least 1. Default `4`. */\n\ttabWidth?: number;\n\t/** Only format lines that overlap this range of offsets. */\n\trange?: { start: number; end: number };\n};\n\n/** Replace `start` to `end` of the original source with `text`. */\nexport type FormatEdit = { start: number; end: number; text: string };\n\n/** Thrown when formatting would have changed what a template renders. That is a bug in the formatter. */\nexport class KilnFormatError extends Error {\n\toverride name = \"KilnFormatError\";\n}\n\n/**\n * How the formatter treats one line.\n *\n * - `indent`: set its indentation to `level` steps.\n * - `shift`: move it by as much as line `anchor` moved, keeping its own indentation relative to it. For scripts, styles and comments.\n * - `keep`: leave it alone, for `<pre>`, `<textarea>`, `verbatim`, text inside attribute values, and the body of a `set` or `apply` whose tag shares its line.\n * - `inside`: it starts inside an output or tag, which `printDelimiter` indents.\n */\ntype Line =\n\t| { kind: \"indent\"; start: number; level: number }\n\t| { kind: \"shift\"; start: number; anchor: number }\n\t| { kind: \"keep\" | \"inside\"; start: number };\n\ntype Frame = { kind: \"kiln\" | \"html\"; name: string };\n\n/**\n * Where the scanner is in the HTML.\n *\n * - `tag`: inside `<name ...>` or `</name>`, which started on line `line`. `inner` counts block tags open inside it.\n * - `raw`: inside `<script>` or `<style>`, or kept as written inside `<pre>`, `<textarea>` and the like.\n * - `kept`: inside the body of block tag `name`, output as written: `verbatim`, or a `set` or `apply` whose tag shares\n * its line, where Kiln doesn't remove the body's indentation. `depth` counts the tags of that name open.\n */\ntype Mode =\n\t| { kind: \"data\" }\n\t| {\n\t\t\tkind: \"tag\";\n\t\t\tname: string;\n\t\t\tclosing: boolean;\n\t\t\tquote: string | null;\n\t\t\tline: number;\n\t\t\tatLineStart: boolean;\n\t\t\tinner: number;\n\t }\n\t| { kind: \"comment\"; anchor: number; level: number }\n\t| { kind: \"raw\"; name: string; keep: boolean; anchor: number; level: number }\n\t| { kind: \"kept\"; name: string; depth: number; level: number };\n\nconst VOID_ELEMENTS = new Set([\n\t\"area\",\n\t\"base\",\n\t\"br\",\n\t\"col\",\n\t\"embed\",\n\t\"hr\",\n\t\"img\",\n\t\"input\",\n\t\"link\",\n\t\"meta\",\n\t\"source\",\n\t\"track\",\n\t\"wbr\",\n]);\n\n/** Elements written in `<script>` or `<style>`'s own language, whose lines move together. */\nconst RAW_ELEMENTS = new Set([\"script\", \"style\"]);\n/** Elements whose whitespace shows on the page. Nothing closes `<plaintext>`. */\nconst KEPT_ELEMENTS = new Set([\n\t\"pre\",\n\t\"textarea\",\n\t\"xmp\",\n\t\"listing\",\n\t\"plaintext\",\n]);\n\n/** Whether `</name` starts at `index`, as a whole tag name. */\nconst closesElement = (text: string, index: number, name: string): boolean => {\n\tif (name === \"plaintext\") return false;\n\tconst end = index + 2 + name.length;\n\treturn (\n\t\ttext.slice(index, end).toLowerCase() === `</${name}` &&\n\t\t(end === text.length || /[\\s/>]/.test(text[end] as string))\n\t);\n};\n\n/**\n * Whether a line starting with `trimmed` begins with the tag that ends the outermost `kept` body, and the formatter\n * may indent it. It may when Kiln removes the whitespace before the tag: a `-` trim marker, or a tag alone on its line.\n */\nconst endsKept = (\n\ttrimmed: string,\n\tmode: Extract<Mode, { kind: \"kept\" }>,\n): boolean => {\n\tconst match = /^\\{%(-?)\\s*end([A-Za-z]+)\\s*-?%\\}([^\\n]*)/.exec(trimmed);\n\treturn (\n\t\tmatch !== null &&\n\t\tmatch[2] === mode.name &&\n\t\tmode.depth === 1 &&\n\t\t(match[1] === \"-\" || /^[ \\t\\r]*$/.test(match[3] as string))\n\t);\n};\n\n/** Elements that the next of these siblings closes, such as a `<li>` without `</li>`. */\nconst CLOSED_BY: Record<string, readonly string[]> = {\n\tli: [\"li\"],\n\tdt: [\"dt\", \"dd\"],\n\tdd: [\"dt\", \"dd\"],\n\tp: [\"p\"],\n\toption: [\"option\"],\n\ttr: [\"tr\"],\n\ttd: [\"td\", \"th\"],\n\tth: [\"td\", \"th\"],\n\tthead: [\"tbody\", \"tfoot\"],\n\ttbody: [\"tbody\", \"tfoot\"],\n};\n\n/** Block tags that hold a body, and so indent what follows. */\nconst BLOCK_TAGS = new Set([\n\t\"if\",\n\t\"for\",\n\t\"set\",\n\t\"block\",\n\t\"component\",\n\t\"fill\",\n\t\"slot\",\n\t\"macro\",\n\t\"with\",\n\t\"cache\",\n\t\"island\",\n\t\"add\",\n\t\"apply\",\n\t\"autoescape\",\n\t\"verbatim\",\n]);\n\n/** What a tag does to indentation: opens a body, starts another branch of one, closes one, or nothing. */\nconst roleOf = (\n\tpart: DelimitedPart,\n): { kind: \"open\" | \"close\"; name: string } | { kind: \"branch\" } | null => {\n\tconst name = part.tokens[1];\n\tif (name?.type !== \"name\") return null;\n\tif (BLOCK_TAGS.has(name.value)) {\n\t\tif (\n\t\t\tname.value === \"set\" &&\n\t\t\tpart.tokens.some((token) => token.type === \"punct\" && token.value === \"=\")\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\t// `{% block title page.title %}` is the short form, with no body.\n\t\tif (name.value === \"block\" && part.tokens.length > 4) return null;\n\t\treturn { kind: \"open\", name: name.value };\n\t}\n\tif (name.value === \"else\" || name.value === \"elseif\")\n\t\treturn { kind: \"branch\" };\n\tif (name.value.startsWith(\"end\")) {\n\t\treturn { kind: \"close\", name: name.value.slice(3) };\n\t}\n\treturn null;\n};\n\nconst isLetter = (char: string | undefined) =>\n\tchar !== undefined && /[A-Za-z]/.test(char);\n\nconst isTest = (name: string) => Object.hasOwn(builtinTests, name);\n\n/** The start offset of every line. */\nconst lineStarts = (text: string): number[] => {\n\tconst starts = [0];\n\tfor (let index = text.indexOf(\"\\n\"); index !== -1;) {\n\t\tstarts.push(index + 1);\n\t\tindex = text.indexOf(\"\\n\", index + 1);\n\t}\n\treturn starts;\n};\n\n/**\n * Walks the template once, tracking open block tags and HTML elements, and decides how to treat each line.\n *\n * Returns the lines, which lines may lose trailing whitespace, and the outputs and tags to print.\n */\nconst planLines = (text: string, parts: readonly Part[]) => {\n\tconst starts = lineStarts(text);\n\tconst lines: Line[] = [];\n\tconst trimEnd: boolean[] = [];\n\tconst stack: Frame[] = [];\n\tlet mode: Mode = { kind: \"data\" };\n\tlet atLineStart = true;\n\t// A line that starts where the next part does, recorded when that part begins.\n\tlet pendingLine = -1;\n\n\tconst lineText = (index: number) =>\n\t\ttext.slice(starts[index], starts[index + 1] ?? text.length);\n\t/** Whether only spaces and tabs come between `offset` and the end of its line. */\n\tconst endsLine = (offset: number) => {\n\t\tlet index = offset;\n\t\twhile (index < text.length && \" \\t\\r\".includes(text[index] as string)) {\n\t\t\tindex++;\n\t\t}\n\t\treturn index === text.length || text[index] === \"\\n\";\n\t};\n\tconst current = () => lines.length - 1;\n\n\t/** Lowers the current line's level when a closing tag comes before anything else on it. */\n\tconst outdent = (line: number, level: number, first: boolean) => {\n\t\tconst info = lines[line];\n\t\tif (first && info?.kind === \"indent\") {\n\t\t\tinfo.level = Math.min(info.level, level);\n\t\t}\n\t};\n\n\tconst levelOf = (line: number): number => {\n\t\tconst info = lines[line];\n\t\treturn info?.kind === \"indent\" ? info.level : stack.length;\n\t};\n\n\t/** Records how to treat the line that starts at `start`, from the current state. */\n\tconst beginLine = (start: number) => {\n\t\tconst index = lines.length;\n\t\tconst trimmed = lineText(index).trimStart();\n\t\tatLineStart = true;\n\t\tswitch (mode.kind) {\n\t\t\tcase \"data\":\n\t\t\t\tlines.push({ kind: \"indent\", start, level: stack.length });\n\t\t\t\treturn;\n\t\t\tcase \"tag\": {\n\t\t\t\tif (mode.quote !== null) {\n\t\t\t\t\tlines.push({ kind: \"keep\", start });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst base = levelOf(mode.line);\n\t\t\t\tconst level =\n\t\t\t\t\ttrimmed.startsWith(\">\") || trimmed.startsWith(\"/>\")\n\t\t\t\t\t\t? base\n\t\t\t\t\t\t: base + 1 + mode.inner;\n\t\t\t\tlines.push({ kind: \"indent\", start, level });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"comment\":\n\t\t\t\tlines.push(\n\t\t\t\t\ttrimmed.startsWith(\"-->\")\n\t\t\t\t\t\t? { kind: \"indent\", start, level: mode.level }\n\t\t\t\t\t\t: { kind: \"shift\", start, anchor: mode.anchor },\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\tcase \"raw\":\n\t\t\t\tif (mode.keep) lines.push({ kind: \"keep\", start });\n\t\t\t\telse if (closesElement(trimmed, 0, mode.name)) {\n\t\t\t\t\tlines.push({ kind: \"indent\", start, level: mode.level });\n\t\t\t\t} else lines.push({ kind: \"shift\", start, anchor: mode.anchor });\n\t\t\t\treturn;\n\t\t\tcase \"kept\":\n\t\t\t\tlines.push(\n\t\t\t\t\tendsKept(trimmed, mode)\n\t\t\t\t\t\t? { kind: \"indent\", start, level: mode.level }\n\t\t\t\t\t\t: { kind: \"keep\", start },\n\t\t\t\t);\n\t\t}\n\t};\n\n\t/** Whether trailing whitespace on a line ending here can go. Not inside attribute values or kept elements. */\n\tconst canTrimHere = () =>\n\t\t!(mode.kind === \"tag\" && mode.quote !== null) &&\n\t\t!(mode.kind === \"raw\" && mode.keep) &&\n\t\tmode.kind !== \"kept\";\n\n\tconst finishTag = (\n\t\ttag: Extract<Mode, { kind: \"tag\" }>,\n\t\tselfClosing: boolean,\n\t) => {\n\t\tmode = { kind: \"data\" };\n\t\tconst name = tag.name;\n\t\tif (tag.closing) {\n\t\t\t// Close the nearest open element of this name, without crossing a block tag.\n\t\t\tfor (let index = stack.length - 1; index >= 0; index--) {\n\t\t\t\tconst frame = stack[index] as Frame;\n\t\t\t\tif (frame.kind === \"kiln\") break;\n\t\t\t\tif (frame.name === name) {\n\t\t\t\t\tstack.length = index;\n\t\t\t\t\toutdent(tag.line, index, tag.atLineStart);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (name.startsWith(\"!\") || selfClosing || VOID_ELEMENTS.has(name)) return;\n\t\tif (RAW_ELEMENTS.has(name) || KEPT_ELEMENTS.has(name)) {\n\t\t\tmode = {\n\t\t\t\tkind: \"raw\",\n\t\t\t\tname,\n\t\t\t\tkeep: KEPT_ELEMENTS.has(name),\n\t\t\t\tanchor: tag.line,\n\t\t\t\tlevel: stack.length,\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\t\tconst top = stack[stack.length - 1];\n\t\tif (top?.kind === \"html\" && CLOSED_BY[top.name]?.includes(name)) {\n\t\t\tstack.pop();\n\t\t\toutdent(tag.line, stack.length, tag.atLineStart);\n\t\t}\n\t\tstack.push({ kind: \"html\", name });\n\t};\n\n\t/** Starts a tag at `index` if one starts there, and returns where scanning continues. */\n\tconst startTag = (index: number, closing: boolean): number => {\n\t\tconst nameStart = index + (closing ? 2 : 1);\n\t\tlet nameEnd = nameStart;\n\t\twhile (\n\t\t\tnameEnd < text.length &&\n\t\t\t/[A-Za-z0-9:!_-]/.test(text[nameEnd] as string)\n\t\t) {\n\t\t\tnameEnd++;\n\t\t}\n\t\tmode = {\n\t\t\tkind: \"tag\",\n\t\t\tname: text.slice(nameStart, nameEnd).toLowerCase(),\n\t\t\tclosing,\n\t\t\tquote: null,\n\t\t\tline: current(),\n\t\t\tatLineStart,\n\t\t\tinner: 0,\n\t\t};\n\t\tatLineStart = false;\n\t\treturn nameEnd;\n\t};\n\n\t/** Moves through a text part one character at a time. */\n\tconst scanText = (start: number, end: number) => {\n\t\tfor (let index = start; index < end; index++) {\n\t\t\tconst char = text[index] as string;\n\t\t\tif (char === \"\\n\") {\n\t\t\t\ttrimEnd[current()] = canTrimHere();\n\t\t\t\tif (index + 1 < end) beginLine(index + 1);\n\t\t\t\telse if (index + 1 === text.length) {\n\t\t\t\t\t// The file ends with a line break: there is no further line.\n\t\t\t\t} else pendingLine = index + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tswitch (mode.kind) {\n\t\t\t\tcase \"data\":\n\t\t\t\t\tif (char === \" \" || char === \"\\t\" || char === \"\\r\") break;\n\t\t\t\t\tif (char === \"<\") {\n\t\t\t\t\t\tif (text.startsWith(\"<!--\", index)) {\n\t\t\t\t\t\t\tmode = {\n\t\t\t\t\t\t\t\tkind: \"comment\",\n\t\t\t\t\t\t\t\tanchor: current(),\n\t\t\t\t\t\t\t\tlevel: levelOf(current()),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tatLineStart = false;\n\t\t\t\t\t\t\tindex += 3;\n\t\t\t\t\t\t} else if (text[index + 1] === \"/\" && isLetter(text[index + 2])) {\n\t\t\t\t\t\t\tindex = startTag(index, true) - 1;\n\t\t\t\t\t\t} else if (isLetter(text[index + 1]) || text[index + 1] === \"!\") {\n\t\t\t\t\t\t\tindex = startTag(index, false) - 1;\n\t\t\t\t\t\t} else atLineStart = false;\n\t\t\t\t\t} else atLineStart = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tag\":\n\t\t\t\t\tif (mode.quote !== null) {\n\t\t\t\t\t\tif (char === mode.quote) mode.quote = null;\n\t\t\t\t\t} else if (char === '\"' || char === \"'\") mode.quote = char;\n\t\t\t\t\telse if (char === \">\") finishTag(mode, text[index - 1] === \"/\");\n\t\t\t\t\telse if (char !== \" \" && char !== \"\\t\" && char !== \"\\r\") {\n\t\t\t\t\t\tatLineStart = false;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"comment\":\n\t\t\t\t\tif (text.startsWith(\"-->\", index)) {\n\t\t\t\t\t\tmode = { kind: \"data\" };\n\t\t\t\t\t\tindex += 2;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"raw\":\n\t\t\t\t\tif (char === \"<\" && closesElement(text, index, mode.name)) {\n\t\t\t\t\t\tindex = startTag(index, true) - 1;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"kept\":\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t};\n\n\t/** Applies a block tag to the stack, or to the count of block tags open inside an HTML tag. */\n\tconst applyTag = (part: DelimitedPart) => {\n\t\tconst role = roleOf(part);\n\t\tconst first = atLineStart;\n\t\tatLineStart = false;\n\t\tif (!role) return;\n\t\tif (mode.kind === \"tag\") {\n\t\t\tconst base = levelOf(mode.line) + 1;\n\t\t\tif (role.kind === \"open\") mode.inner++;\n\t\t\telse if (role.kind === \"close\") {\n\t\t\t\tmode.inner = Math.max(0, mode.inner - 1);\n\t\t\t\toutdent(current(), base + mode.inner, first);\n\t\t\t} else outdent(current(), base + mode.inner - 1, first);\n\t\t\treturn;\n\t\t}\n\t\tif (mode.kind === \"kept\") {\n\t\t\tif (role.kind === \"open\" && role.name === mode.name) mode.depth++;\n\t\t\telse if (\n\t\t\t\trole.kind === \"close\" &&\n\t\t\t\trole.name === mode.name &&\n\t\t\t\t--mode.depth === 0\n\t\t\t) {\n\t\t\t\tconst index = stack.findLastIndex((frame) => frame.kind === \"kiln\");\n\t\t\t\tif (index !== -1) stack.length = index;\n\t\t\t\tmode = { kind: \"data\" };\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (mode.kind !== \"data\") return;\n\t\tif (role.kind === \"open\") {\n\t\t\tstack.push({ kind: \"kiln\", name: role.name });\n\t\t\tif (\n\t\t\t\trole.name === \"verbatim\" ||\n\t\t\t\t((role.name === \"set\" || role.name === \"apply\") &&\n\t\t\t\t\t!(first && endsLine(part.end)))\n\t\t\t) {\n\t\t\t\tmode = {\n\t\t\t\t\tkind: \"kept\",\n\t\t\t\t\tname: role.name,\n\t\t\t\t\tdepth: 1,\n\t\t\t\t\tlevel: stack.length - 1,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst index =\n\t\t\trole.kind === \"branch\"\n\t\t\t\t? stack.findLastIndex((frame) => frame.kind === \"kiln\")\n\t\t\t\t: stack.findLastIndex(\n\t\t\t\t\t\t(frame) => frame.kind === \"kiln\" && frame.name === role.name,\n\t\t\t\t\t);\n\t\tif (index === -1) return;\n\t\t// Elements opened in this body or branch end with it.\n\t\tstack.length = role.kind === \"branch\" ? index + 1 : index;\n\t\toutdent(current(), index, first);\n\t};\n\n\tbeginLine(0);\n\tconst delimiters: { part: DelimitedPart; line: number }[] = [];\n\n\tfor (const part of parts) {\n\t\tif (pendingLine !== -1) {\n\t\t\tbeginLine(pendingLine);\n\t\t\tpendingLine = -1;\n\t\t}\n\t\tif (part.kind === \"text\") {\n\t\t\tscanText(part.start, part.end);\n\t\t\tcontinue;\n\t\t}\n\t\tconst line = current();\n\t\tif (part.kind === \"comment\") atLineStart = false;\n\t\telse {\n\t\t\tdelimiters.push({ part, line });\n\t\t\tif (part.kind === \"tag\") applyTag(part);\n\t\t\telse atLineStart = false;\n\t\t}\n\t\t// Lines that start inside this delimiter.\n\t\tfor (\n\t\t\tlet index = text.indexOf(\"\\n\", part.start);\n\t\t\tindex !== -1 && index < part.end;\n\t\t\tindex = text.indexOf(\"\\n\", index + 1)\n\t\t) {\n\t\t\ttrimEnd[current()] = part.kind === \"comment\";\n\t\t\tlines.push(\n\t\t\t\tpart.kind === \"comment\"\n\t\t\t\t\t? { kind: \"shift\", start: index + 1, anchor: line }\n\t\t\t\t\t: { kind: \"inside\", start: index + 1 },\n\t\t\t);\n\t\t}\n\t}\n\ttrimEnd[current()] ??= canTrimHere();\n\treturn { lines, trimEnd, delimiters };\n};\n\nconst widthOf = (whitespace: string, tabWidth: number): number => {\n\tlet width = 0;\n\tfor (const char of whitespace) width += char === \"\\t\" ? tabWidth : 1;\n\treturn width;\n};\n\n/**\n * Works out the edits that format a template, without checking them. See `formatEdits`.\n */\nconst planEdits = (\n\ttext: string,\n\tparts: readonly Part[],\n\toptions: Required<Omit<FormatOptions, \"range\">>,\n): FormatEdit[] => {\n\tconst { useTabs, tabWidth } = options;\n\tconst unit = useTabs ? \"\\t\" : \" \".repeat(tabWidth);\n\tconst indentOfWidth = (width: number) =>\n\t\tuseTabs\n\t\t\t? \"\\t\".repeat(Math.floor(width / tabWidth)) + \" \".repeat(width % tabWidth)\n\t\t\t: \" \".repeat(width);\n\tconst { lines, trimEnd, delimiters } = planLines(text, parts);\n\n\tconst oldIndent = (line: number) => {\n\t\tconst start = lines[line]?.start ?? 0;\n\t\tlet end = start;\n\t\twhile (text[end] === \" \" || text[end] === \"\\t\") end++;\n\t\treturn text.slice(start, end);\n\t};\n\t/** Where a line's content ends: at its line break, or the end of the file. */\n\tconst lineEnd = (line: number) => {\n\t\tconst next = lines[line + 1]?.start;\n\t\tif (next !== undefined) return next - 1;\n\t\treturn text.endsWith(\"\\n\") ? text.length - 1 : text.length;\n\t};\n\tconst isBlank = (line: number) => {\n\t\tconst info = lines[line] as Line;\n\t\treturn (\n\t\t\tinfo.kind !== \"keep\" &&\n\t\t\tinfo.kind !== \"inside\" &&\n\t\t\ttext.slice(info.start, lineEnd(line)).trim() === \"\"\n\t\t);\n\t};\n\n\t// Whitespace before the first content and after the last is output as written wherever the template is placed, so\n\t// it stays as it is, as does a file of only whitespace.\n\tlet firstContent = 0;\n\twhile (firstContent < lines.length && isBlank(firstContent)) firstContent++;\n\tlet lastContent = lines.length - 1;\n\twhile (lastContent >= 0 && isBlank(lastContent)) lastContent--;\n\n\tconst newIndents = new Map<number, string | null>();\n\tconst newIndent = (line: number): string | null => {\n\t\tif (newIndents.has(line)) return newIndents.get(line) ?? null;\n\t\tconst info = lines[line];\n\t\tlet indent: string | null = null;\n\t\tif (info?.kind === \"indent\") {\n\t\t\t// The first content line keeps its indentation. See `firstContent`.\n\t\t\tindent =\n\t\t\t\tline === firstContent ? oldIndent(line) : unit.repeat(info.level);\n\t\t} else if (info?.kind === \"shift\") {\n\t\t\tconst anchor = newIndent(info.anchor);\n\t\t\tconst moved =\n\t\t\t\tanchor === null\n\t\t\t\t\t? 0\n\t\t\t\t\t: widthOf(anchor, tabWidth) -\n\t\t\t\t\t\twidthOf(oldIndent(info.anchor), tabWidth);\n\t\t\tindent = indentOfWidth(\n\t\t\t\tMath.max(0, widthOf(oldIndent(line), tabWidth) + moved),\n\t\t\t);\n\t\t}\n\t\tnewIndents.set(line, indent);\n\t\treturn indent;\n\t};\n\n\tconst edits: FormatEdit[] = [];\n\t// Blank lines go where the line before is blank too, so runs become one.\n\tlet previousBlank = false;\n\n\tfor (const [index, info] of lines.entries()) {\n\t\tif (index > lastContent) break;\n\t\tif (index < firstContent) continue;\n\t\tconst start = info.start;\n\t\tconst end = lineEnd(index);\n\t\tif (isBlank(index)) {\n\t\t\tif (info.kind === \"indent\" && previousBlank) {\n\t\t\t\tedits.push({ start, end: end + 1, text: \"\" });\n\t\t\t} else if (end > start) edits.push({ start, end, text: \"\" });\n\t\t\tpreviousBlank = true;\n\t\t\tcontinue;\n\t\t}\n\t\tpreviousBlank = false;\n\n\t\tconst indent = newIndent(index);\n\t\tif (indent !== null) {\n\t\t\tconst current = oldIndent(index);\n\t\t\tif (current !== indent) {\n\t\t\t\tedits.push({ start, end: start + current.length, text: indent });\n\t\t\t}\n\t\t}\n\t\tif (trimEnd[index] && info.kind !== \"keep\" && index !== lastContent) {\n\t\t\tlet from = end;\n\t\t\twhile (from > start && /[ \\t\\r]/.test(text[from - 1] as string)) from--;\n\t\t\tif (from < end && !insideDelimiter(delimiters, from)) {\n\t\t\t\tedits.push({ start: from, end, text: \"\" });\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const { part, line } of delimiters) {\n\t\tconst indent = newIndent(line) ?? oldIndent(line);\n\t\tconst printed = printDelimiter(text, part, indent, unit);\n\t\tif (printed !== null && printed !== text.slice(part.start, part.end)) {\n\t\t\tedits.push({ start: part.start, end: part.end, text: printed });\n\t\t}\n\t}\n\n\treturn edits.sort((a, b) => a.start - b.start || a.end - b.end);\n};\n\nconst insideDelimiter = (\n\tdelimiters: readonly { part: DelimitedPart }[],\n\toffset: number,\n) => delimiters.some(({ part }) => offset > part.start && offset < part.end);\n\nconst applyEdits = (text: string, edits: readonly FormatEdit[]): string => {\n\tlet out = \"\";\n\tlet position = 0;\n\tfor (const edit of edits) {\n\t\tout += text.slice(position, edit.start) + edit.text;\n\t\tposition = edit.end;\n\t}\n\treturn out + text.slice(position);\n};\n\n/** Removes whitespace at the start and end of lines, and blank lines, which the formatter may change. */\nconst normaliseText = (value: string) =>\n\tvalue\n\t\t.replace(/[ \\t\\r\\f\\v]+$/gm, \"\")\n\t\t.replace(/^[ \\t\\f\\v]+/gm, \"\")\n\t\t.replace(/\\n{2,}/g, \"\\n\");\n\n/** A copy of a syntax tree without positions or layout, with text normalised and text left empty by that removed. */\nconst normaliseTree = (value: unknown): unknown => {\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.map(normaliseTree)\n\t\t\t.filter(\n\t\t\t\t(item) =>\n\t\t\t\t\t!(\n\t\t\t\t\t\ttypeof item === \"object\" &&\n\t\t\t\t\t\titem !== null &&\n\t\t\t\t\t\t(item as { type?: unknown }).type === \"Text\" &&\n\t\t\t\t\t\t(item as { value?: unknown }).value === \"\"\n\t\t\t\t\t),\n\t\t\t);\n\t}\n\tif (typeof value !== \"object\" || value === null) return value;\n\tconst copy: Record<string, unknown> = {};\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tif (\n\t\t\tkey === \"span\" ||\n\t\t\tkey === \"nameSpan\" ||\n\t\t\tkey === \"lines\" ||\n\t\t\tkey === \"indent\"\n\t\t)\n\t\t\tcontinue;\n\t\tcopy[key] =\n\t\t\tkey === \"value\" && (value as { type?: unknown }).type === \"Text\"\n\t\t\t\t? normaliseText(item as string)\n\t\t\t\t: normaliseTree(item);\n\t}\n\treturn copy;\n};\n\n/** The syntax tree as JSON, without the whitespace the formatter may change, so two trees can be compared. */\nconst shape = (text: string): string =>\n\tJSON.stringify(normaliseTree(parse(text, { isTest }).ast));\n\n/**\n * Works out the edits that format a template, in source order and never overlapping. Editors apply these so the cursor and undo history stay put. See `format` for what it throws.\n */\nexport const formatEdits = (\n\ttext: string,\n\toptions: FormatOptions = {},\n): FormatEdit[] => {\n\tconst parsed = parse(text, { isTest });\n\tif (parsed.problems.length > 0) {\n\t\tconst source = new Source(\"template.kiln\", text);\n\t\tthrow new KilnSyntaxError(\n\t\t\tparsed.problems.map((problem) =>\n\t\t\t\tcreateDiagnostic(source, problem.code, problem.message, problem.span),\n\t\t\t),\n\t\t\tsource,\n\t\t);\n\t}\n\n\tconst tabWidth = options.tabWidth ?? 4;\n\tif (!Number.isInteger(tabWidth) || tabWidth < 1) {\n\t\tthrow new RangeError(\n\t\t\t`tabWidth must be a whole number of at least 1, but got ${tabWidth}`,\n\t\t);\n\t}\n\tlet edits = planEdits(text, scanParts(text).parts, {\n\t\tuseTabs: options.useTabs ?? true,\n\t\ttabWidth,\n\t});\n\tconst range = options.range;\n\tif (range) {\n\t\tedits = edits.filter(\n\t\t\t(edit) => edit.end >= range.start && edit.start <= range.end,\n\t\t);\n\t}\n\n\tconst formatted = applyEdits(text, edits);\n\tconst changed = parse(formatted, { isTest });\n\tif (changed.problems.length > 0 || shape(formatted) !== shape(text)) {\n\t\tthrow new KilnFormatError(\n\t\t\t\"Formatting would have changed what this template renders, so nothing was changed. This is a bug in Kiln's formatter.\",\n\t\t);\n\t}\n\treturn edits;\n};\n\n/**\n * Formats a template: indents it to match its structure, and tidies spacing inside outputs and tags. Never changes what it renders.\n *\n * Throws `KilnSyntaxError` when the template has syntax errors, `KilnFormatError` if the safety check fails, and\n * `RangeError` for a `tabWidth` that isn't a whole number of at least 1.\n *\n * ```ts\n * const formatted = format(source, { useTabs: false, tabWidth: 2 });\n * ```\n */\nexport const format = (text: string, options: FormatOptions = {}): string =>\n\tapplyEdits(text, formatEdits(text, options));\n"],"mappings":"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"}
1
+ {"version":3,"file":"format-CLCViAPn.js","names":[],"sources":["../src/formatter/delimiters.ts","../src/formatter/format.ts"],"sourcesContent":["import type { DelimitedPart, Token } from \"../lexer/lexer.js\";\n\n/** A token as the formatter prints it: its source text, unchanged. An interpolated string is one piece. */\ntype Piece = {\n\ttype: Token[\"type\"];\n\ttext: string;\n\tstart: number;\n\tend: number;\n};\n\n/**\n * What a piece does, where that changes its spacing.\n *\n * - `word`: a name, number or string.\n * - `keyword`: a tag's name, or a name such as `and` or `in`. A bracket after it groups rather than calls.\n * - `unary`: a sign that makes a number negative or positive, written against its operand.\n * - `tight`: `.`, `?.`, `..` and a filter's `|`, with no space either side.\n * - `key`: the colon after a key or named argument, or `?` and `?:` marking an optional key in `props`.\n * - `call`: a bracket that calls or indexes the piece before it.\n * - `open`, `close`: other brackets.\n * - `spaced`: operators with a space either side.\n */\ntype Role =\n\t| \"word\"\n\t| \"keyword\"\n\t| \"unary\"\n\t| \"tight\"\n\t| \"key\"\n\t| \"call\"\n\t| \"open\"\n\t| \"close\"\n\t| \"comma\"\n\t| \"spread\"\n\t| \"spaced\";\n\nconst KEYWORDS = new Set([\n\t\"and\",\n\t\"or\",\n\t\"not\",\n\t\"in\",\n\t\"is\",\n\t\"starts\",\n\t\"ends\",\n\t\"with\",\n\t\"as\",\n\t\"import\",\n\t\"ignore\",\n\t\"missing\",\n]);\n\n/** Joins each interpolated string's tokens, from `stringStart` to its matching `stringEnd`, into one piece. */\nconst toPieces = (source: string, tokens: readonly Token[]): Piece[] => {\n\tconst pieces: Piece[] = [];\n\tfor (let index = 0; index < tokens.length; index++) {\n\t\tconst token = tokens[index] as Token;\n\t\tlet end = token.end;\n\t\tif (token.type === \"stringStart\") {\n\t\t\tlet depth = 0;\n\t\t\tfor (; index < tokens.length; index++) {\n\t\t\t\tconst inner = tokens[index] as Token;\n\t\t\t\tif (inner.type === \"stringStart\") depth++;\n\t\t\t\telse if (inner.type === \"stringEnd\" && --depth === 0) break;\n\t\t\t}\n\t\t\tend = (tokens[Math.min(index, tokens.length - 1)] as Token).end;\n\t\t}\n\t\tpieces.push({\n\t\t\ttype: token.type === \"stringStart\" ? \"string\" : token.type,\n\t\t\ttext: source.slice(token.start, end),\n\t\t\tstart: token.start,\n\t\t\tend,\n\t\t});\n\t}\n\treturn pieces;\n};\n\n/** Works out each piece's role. `isTag` means the first piece is a tag's name. */\nconst rolesOf = (pieces: readonly Piece[], isTag: boolean): Role[] => {\n\tconst roles: Role[] = [];\n\t// In `props`, `|` joins types and `?:` marks an optional key, until `=` starts a default value.\n\tconst props = isTag && pieces[0]?.text === \"props\";\n\tlet inDefault = false;\n\t// Open `?`s at each bracket depth, so a conditional's `:` can be told from a key's.\n\tconst conditionals: number[] = [0];\n\n\tfor (const [index, piece] of pieces.entries()) {\n\t\tconst previous = roles[index - 1];\n\t\tconst depth = conditionals.length - 1;\n\t\tconst types = props && !inDefault;\n\t\tlet role: Role;\n\n\t\tif (piece.type !== \"punct\") {\n\t\t\trole =\n\t\t\t\t(isTag && index === 0) ||\n\t\t\t\t(piece.type === \"name\" && KEYWORDS.has(piece.text))\n\t\t\t\t\t? \"keyword\"\n\t\t\t\t\t: \"word\";\n\t\t} else {\n\t\t\tswitch (piece.text) {\n\t\t\t\tcase \"(\":\n\t\t\t\tcase \"[\":\n\t\t\t\t\trole = previous === \"word\" || previous === \"close\" ? \"call\" : \"open\";\n\t\t\t\t\tconditionals.push(0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"{\":\n\t\t\t\t\trole = \"open\";\n\t\t\t\t\tconditionals.push(0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \")\":\n\t\t\t\tcase \"]\":\n\t\t\t\tcase \"}\":\n\t\t\t\t\trole = \"close\";\n\t\t\t\t\tif (conditionals.length > 1) conditionals.pop();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \",\":\n\t\t\t\t\trole = \"comma\";\n\t\t\t\t\tif (props && depth === 0) inDefault = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"...\":\n\t\t\t\t\trole = \"spread\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \".\":\n\t\t\t\tcase \"?.\":\n\t\t\t\tcase \"..\":\n\t\t\t\t\trole = \"tight\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"|\":\n\t\t\t\t\trole = types ? \"spaced\" : \"tight\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"-\":\n\t\t\t\tcase \"+\":\n\t\t\t\t\trole =\n\t\t\t\t\t\tprevious === undefined ||\n\t\t\t\t\t\t(previous !== \"word\" && previous !== \"close\")\n\t\t\t\t\t\t\t? \"unary\"\n\t\t\t\t\t\t\t: \"spaced\";\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"?\":\n\t\t\t\tcase \"?:\":\n\t\t\t\t\trole = types ? \"key\" : \"spaced\";\n\t\t\t\t\tif (piece.text === \"?\" && !types) {\n\t\t\t\t\t\tconditionals[depth] = (conditionals[depth] ?? 0) + 1;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \":\":\n\t\t\t\t\tif ((conditionals[depth] ?? 0) > 0) {\n\t\t\t\t\t\tconditionals[depth] = (conditionals[depth] ?? 0) - 1;\n\t\t\t\t\t\trole = \"spaced\";\n\t\t\t\t\t} else role = \"key\";\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\trole = \"spaced\";\n\t\t\t\t\tif (props && depth === 0 && piece.text === \"=\") inDefault = true;\n\t\t\t}\n\t\t}\n\t\troles.push(role);\n\t}\n\treturn roles;\n};\n\n/** The gap between two pieces: a space or nothing. */\nconst gapBetween = (\n\tprevious: Piece,\n\tpreviousRole: Role,\n\tpiece: Piece,\n\trole: Role,\n): string => {\n\tif (role === \"comma\" || role === \"tight\" || role === \"key\" || role === \"call\")\n\t\treturn \"\";\n\tif (role === \"close\") {\n\t\treturn piece.text === \"}\" && previous.text !== \"{\" ? \" \" : \"\";\n\t}\n\tif (previousRole === \"tight\" || previousRole === \"unary\") return \"\";\n\tif (previousRole === \"spread\") return \"\";\n\tif (previousRole === \"open\" || previousRole === \"call\") {\n\t\treturn previous.text === \"{\" ? \" \" : \"\";\n\t}\n\treturn \" \";\n};\n\n/**\n * Prints one output or tag with normalised spacing. Line breaks between tokens stay; each line after one is indented from `indent`, one step further or to match the brackets it sits inside, using `unit` per step.\n *\n * Returns null for a delimiter that never closed, which the formatter leaves as it is.\n */\nexport const printDelimiter = (\n\tsource: string,\n\tpart: DelimitedPart,\n\tindent: string,\n\tunit: string,\n): string | null => {\n\tconst tokens = part.tokens;\n\tconst open = tokens[0];\n\tconst close = tokens[tokens.length - 1];\n\tif (!open || !close || close.value === \"\" || tokens.length < 3) return null;\n\n\tconst pieces = toPieces(source, tokens.slice(1, -1));\n\tconst roles = rolesOf(pieces, part.kind === \"tag\");\n\tlet out = open.value;\n\tlet previousEnd = part.start + open.value.length;\n\t// The line each open bracket started on. Brackets opened on the same line add one step between them.\n\tconst openedOn: number[] = [];\n\tlet line = 0;\n\t/** Steps of indentation for a new line, once `closers` brackets at its start have closed. */\n\tconst steps = (closers: number) =>\n\t\tnew Set(openedOn.slice(0, openedOn.length - closers)).size;\n\n\tfor (const [index, piece] of pieces.entries()) {\n\t\tconst role = roles[index] as Role;\n\t\tconst gap = source.slice(previousEnd, piece.start);\n\t\tif (gap.includes(\"\\n\")) {\n\t\t\tline++;\n\t\t\t// Closing brackets at the start of a line line up with the line that opened them. Only those on this line count.\n\t\t\tlet closers = 0;\n\t\t\twhile (\n\t\t\t\troles[index + closers] === \"close\" &&\n\t\t\t\t(closers === 0 ||\n\t\t\t\t\t!source\n\t\t\t\t\t\t.slice(\n\t\t\t\t\t\t\t(pieces[index + closers - 1] as Piece).end,\n\t\t\t\t\t\t\t(pieces[index + closers] as Piece).start,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.includes(\"\\n\"))\n\t\t\t) {\n\t\t\t\tclosers++;\n\t\t\t}\n\t\t\tout += `\\n${indent}${unit.repeat(closers > 0 ? steps(closers) : Math.max(steps(0), 1))}`;\n\t\t} else if (index === 0) out += \" \";\n\t\telse {\n\t\t\tout += gapBetween(\n\t\t\t\tpieces[index - 1] as Piece,\n\t\t\t\troles[index - 1] as Role,\n\t\t\t\tpiece,\n\t\t\t\trole,\n\t\t\t);\n\t\t}\n\t\tout += piece.text;\n\t\tif (role === \"open\" || role === \"call\") openedOn.push(line);\n\t\telse if (role === \"close\") openedOn.pop();\n\t\tpreviousEnd = piece.end;\n\t}\n\n\tconst closeStart = part.end - close.value.length;\n\tout += source.slice(previousEnd, closeStart).includes(\"\\n\")\n\t\t? `\\n${indent}`\n\t\t: \" \";\n\treturn out + close.value;\n};\n","import { builtinTests } from \"../builtins/functions.js\";\nimport { KilnSyntaxError } from \"../errors.js\";\nimport { createDiagnostic } from \"../errors.js\";\nimport { type DelimitedPart, type Part, scanParts } from \"../lexer/lexer.js\";\nimport { parse } from \"../parser/parser.js\";\nimport { Source } from \"../source.js\";\nimport { printDelimiter } from \"./delimiters.js\";\n\nexport type FormatOptions = {\n\t/** Indent with tabs. Default `true`. */\n\tuseTabs?: boolean;\n\t/** Spaces per step without tabs, and the width of a tab when moving lines. A whole number of at least 1. Default `4`. */\n\ttabWidth?: number;\n\t/** Only format lines that overlap this range of offsets. */\n\trange?: { start: number; end: number };\n};\n\n/** Replace `start` to `end` of the original source with `text`. */\nexport type FormatEdit = { start: number; end: number; text: string };\n\n/** Thrown when formatting would have changed what a template renders. That is a bug in the formatter. */\nexport class KilnFormatError extends Error {\n\toverride name = \"KilnFormatError\";\n}\n\n/**\n * How the formatter treats one line.\n *\n * - `indent`: set its indentation to `level` steps.\n * - `shift`: move it by as much as line `anchor` moved, keeping its own indentation relative to it. For scripts, styles and comments.\n * - `keep`: leave it alone, for `<pre>`, `<textarea>`, `verbatim`, text inside attribute values, and the body of a `set` or `apply` whose tag shares its line.\n * - `inside`: it starts inside an output or tag, which `printDelimiter` indents.\n */\ntype Line =\n\t| { kind: \"indent\"; start: number; level: number }\n\t| { kind: \"shift\"; start: number; anchor: number }\n\t| { kind: \"keep\" | \"inside\"; start: number };\n\ntype Frame = { kind: \"kiln\" | \"html\"; name: string };\n\n/**\n * Where the scanner is in the HTML.\n *\n * - `tag`: inside `<name ...>` or `</name>`, which started on line `line`. `inner` counts block tags open inside it.\n * - `raw`: inside `<script>` or `<style>`, or kept as written inside `<pre>`, `<textarea>` and the like.\n * - `kept`: inside the body of block tag `name`, output as written: `verbatim`, or a `set` or `apply` whose tag shares\n * its line, where Kiln doesn't remove the body's indentation. `depth` counts the tags of that name open.\n */\ntype Mode =\n\t| { kind: \"data\" }\n\t| {\n\t\t\tkind: \"tag\";\n\t\t\tname: string;\n\t\t\tclosing: boolean;\n\t\t\tquote: string | null;\n\t\t\tline: number;\n\t\t\tatLineStart: boolean;\n\t\t\tinner: number;\n\t }\n\t| { kind: \"comment\"; anchor: number; level: number }\n\t| { kind: \"raw\"; name: string; keep: boolean; anchor: number; level: number }\n\t| { kind: \"kept\"; name: string; depth: number; level: number };\n\nconst VOID_ELEMENTS = new Set([\n\t\"area\",\n\t\"base\",\n\t\"br\",\n\t\"col\",\n\t\"embed\",\n\t\"hr\",\n\t\"img\",\n\t\"input\",\n\t\"link\",\n\t\"meta\",\n\t\"source\",\n\t\"track\",\n\t\"wbr\",\n]);\n\n/** Elements written in `<script>` or `<style>`'s own language, whose lines move together. */\nconst RAW_ELEMENTS = new Set([\"script\", \"style\"]);\n/** Elements whose whitespace shows on the page. Nothing closes `<plaintext>`. */\nconst KEPT_ELEMENTS = new Set([\n\t\"pre\",\n\t\"textarea\",\n\t\"xmp\",\n\t\"listing\",\n\t\"plaintext\",\n]);\n\n/** Whether `</name` starts at `index`, as a whole tag name. */\nconst closesElement = (text: string, index: number, name: string): boolean => {\n\tif (name === \"plaintext\") return false;\n\tconst end = index + 2 + name.length;\n\treturn (\n\t\ttext.slice(index, end).toLowerCase() === `</${name}` &&\n\t\t(end === text.length || /[\\s/>]/.test(text[end] as string))\n\t);\n};\n\n/**\n * Whether a line starting with `trimmed` begins with the tag that ends the outermost `kept` body, and the formatter\n * may indent it. It may when Kiln removes the whitespace before the tag: a `-` trim marker, or a tag alone on its line.\n */\nconst endsKept = (\n\ttrimmed: string,\n\tmode: Extract<Mode, { kind: \"kept\" }>,\n): boolean => {\n\tconst match = /^\\{%(-?)\\s*end([A-Za-z]+)\\s*-?%\\}([^\\n]*)/.exec(trimmed);\n\treturn (\n\t\tmatch !== null &&\n\t\tmatch[2] === mode.name &&\n\t\tmode.depth === 1 &&\n\t\t(match[1] === \"-\" || /^[ \\t\\r]*$/.test(match[3] as string))\n\t);\n};\n\n/** Elements that the next of these siblings closes, such as a `<li>` without `</li>`. */\nconst CLOSED_BY: Record<string, readonly string[]> = {\n\tli: [\"li\"],\n\tdt: [\"dt\", \"dd\"],\n\tdd: [\"dt\", \"dd\"],\n\tp: [\"p\"],\n\toption: [\"option\"],\n\ttr: [\"tr\"],\n\ttd: [\"td\", \"th\"],\n\tth: [\"td\", \"th\"],\n\tthead: [\"tbody\", \"tfoot\"],\n\ttbody: [\"tbody\", \"tfoot\"],\n};\n\n/** Block tags that hold a body, and so indent what follows. */\nconst BLOCK_TAGS = new Set([\n\t\"if\",\n\t\"for\",\n\t\"set\",\n\t\"block\",\n\t\"component\",\n\t\"fill\",\n\t\"slot\",\n\t\"macro\",\n\t\"with\",\n\t\"cache\",\n\t\"island\",\n\t\"add\",\n\t\"apply\",\n\t\"autoescape\",\n\t\"verbatim\",\n]);\n\n/** What a tag does to indentation: opens a body, starts another branch of one, closes one, or nothing. */\nconst roleOf = (\n\tpart: DelimitedPart,\n): { kind: \"open\" | \"close\"; name: string } | { kind: \"branch\" } | null => {\n\tconst name = part.tokens[1];\n\tif (name?.type !== \"name\") return null;\n\tif (BLOCK_TAGS.has(name.value)) {\n\t\tif (\n\t\t\tname.value === \"set\" &&\n\t\t\tpart.tokens.some((token) => token.type === \"punct\" && token.value === \"=\")\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\t// `{% block title page.title %}` is the short form, with no body.\n\t\tif (name.value === \"block\" && part.tokens.length > 4) return null;\n\t\treturn { kind: \"open\", name: name.value };\n\t}\n\tif (name.value === \"else\" || name.value === \"elseif\")\n\t\treturn { kind: \"branch\" };\n\tif (name.value.startsWith(\"end\")) {\n\t\treturn { kind: \"close\", name: name.value.slice(3) };\n\t}\n\treturn null;\n};\n\nconst isLetter = (char: string | undefined) =>\n\tchar !== undefined && /[A-Za-z]/.test(char);\n\nconst isTest = (name: string) => Object.hasOwn(builtinTests, name);\n\n/** The start offset of every line. */\nconst lineStarts = (text: string): number[] => {\n\tconst starts = [0];\n\tfor (let index = text.indexOf(\"\\n\"); index !== -1;) {\n\t\tstarts.push(index + 1);\n\t\tindex = text.indexOf(\"\\n\", index + 1);\n\t}\n\treturn starts;\n};\n\n/**\n * Walks the template once, tracking open block tags and HTML elements, and decides how to treat each line.\n *\n * Returns the lines, which lines may lose trailing whitespace, and the outputs and tags to print.\n */\nconst planLines = (text: string, parts: readonly Part[]) => {\n\tconst starts = lineStarts(text);\n\tconst lines: Line[] = [];\n\tconst trimEnd: boolean[] = [];\n\tconst stack: Frame[] = [];\n\tlet mode: Mode = { kind: \"data\" };\n\tlet atLineStart = true;\n\t// A line that starts where the next part does, recorded when that part begins.\n\tlet pendingLine = -1;\n\n\tconst lineText = (index: number) =>\n\t\ttext.slice(starts[index], starts[index + 1] ?? text.length);\n\t/** Whether only spaces and tabs come between `offset` and the end of its line. */\n\tconst endsLine = (offset: number) => {\n\t\tlet index = offset;\n\t\twhile (index < text.length && \" \\t\\r\".includes(text[index] as string)) {\n\t\t\tindex++;\n\t\t}\n\t\treturn index === text.length || text[index] === \"\\n\";\n\t};\n\tconst current = () => lines.length - 1;\n\n\t/** Lowers the current line's level when a closing tag comes before anything else on it. */\n\tconst outdent = (line: number, level: number, first: boolean) => {\n\t\tconst info = lines[line];\n\t\tif (first && info?.kind === \"indent\") {\n\t\t\tinfo.level = Math.min(info.level, level);\n\t\t}\n\t};\n\n\tconst levelOf = (line: number): number => {\n\t\tconst info = lines[line];\n\t\treturn info?.kind === \"indent\" ? info.level : stack.length;\n\t};\n\n\t/** Records how to treat the line that starts at `start`, from the current state. */\n\tconst beginLine = (start: number) => {\n\t\tconst index = lines.length;\n\t\tconst trimmed = lineText(index).trimStart();\n\t\tatLineStart = true;\n\t\tswitch (mode.kind) {\n\t\t\tcase \"data\":\n\t\t\t\tlines.push({ kind: \"indent\", start, level: stack.length });\n\t\t\t\treturn;\n\t\t\tcase \"tag\": {\n\t\t\t\tif (mode.quote !== null) {\n\t\t\t\t\tlines.push({ kind: \"keep\", start });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst base = levelOf(mode.line);\n\t\t\t\tconst level =\n\t\t\t\t\ttrimmed.startsWith(\">\") || trimmed.startsWith(\"/>\")\n\t\t\t\t\t\t? base\n\t\t\t\t\t\t: base + 1 + mode.inner;\n\t\t\t\tlines.push({ kind: \"indent\", start, level });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"comment\":\n\t\t\t\tlines.push(\n\t\t\t\t\ttrimmed.startsWith(\"-->\")\n\t\t\t\t\t\t? { kind: \"indent\", start, level: mode.level }\n\t\t\t\t\t\t: { kind: \"shift\", start, anchor: mode.anchor },\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\tcase \"raw\":\n\t\t\t\tif (mode.keep) lines.push({ kind: \"keep\", start });\n\t\t\t\telse if (closesElement(trimmed, 0, mode.name)) {\n\t\t\t\t\tlines.push({ kind: \"indent\", start, level: mode.level });\n\t\t\t\t} else lines.push({ kind: \"shift\", start, anchor: mode.anchor });\n\t\t\t\treturn;\n\t\t\tcase \"kept\":\n\t\t\t\tlines.push(\n\t\t\t\t\tendsKept(trimmed, mode)\n\t\t\t\t\t\t? { kind: \"indent\", start, level: mode.level }\n\t\t\t\t\t\t: { kind: \"keep\", start },\n\t\t\t\t);\n\t\t}\n\t};\n\n\t/** Whether trailing whitespace on a line ending here can go. Not inside attribute values or kept elements. */\n\tconst canTrimHere = () =>\n\t\t!(mode.kind === \"tag\" && mode.quote !== null) &&\n\t\t!(mode.kind === \"raw\" && mode.keep) &&\n\t\tmode.kind !== \"kept\";\n\n\tconst finishTag = (\n\t\ttag: Extract<Mode, { kind: \"tag\" }>,\n\t\tselfClosing: boolean,\n\t) => {\n\t\tmode = { kind: \"data\" };\n\t\tconst name = tag.name;\n\t\tif (tag.closing) {\n\t\t\t// Close the nearest open element of this name, without crossing a block tag.\n\t\t\tfor (let index = stack.length - 1; index >= 0; index--) {\n\t\t\t\tconst frame = stack[index] as Frame;\n\t\t\t\tif (frame.kind === \"kiln\") break;\n\t\t\t\tif (frame.name === name) {\n\t\t\t\t\tstack.length = index;\n\t\t\t\t\toutdent(tag.line, index, tag.atLineStart);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (name.startsWith(\"!\") || selfClosing || VOID_ELEMENTS.has(name)) return;\n\t\tif (RAW_ELEMENTS.has(name) || KEPT_ELEMENTS.has(name)) {\n\t\t\tmode = {\n\t\t\t\tkind: \"raw\",\n\t\t\t\tname,\n\t\t\t\tkeep: KEPT_ELEMENTS.has(name),\n\t\t\t\tanchor: tag.line,\n\t\t\t\tlevel: stack.length,\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\t\tconst top = stack[stack.length - 1];\n\t\tif (top?.kind === \"html\" && CLOSED_BY[top.name]?.includes(name)) {\n\t\t\tstack.pop();\n\t\t\toutdent(tag.line, stack.length, tag.atLineStart);\n\t\t}\n\t\tstack.push({ kind: \"html\", name });\n\t};\n\n\t/** Starts a tag at `index` if one starts there, and returns where scanning continues. */\n\tconst startTag = (index: number, closing: boolean): number => {\n\t\tconst nameStart = index + (closing ? 2 : 1);\n\t\tlet nameEnd = nameStart;\n\t\twhile (\n\t\t\tnameEnd < text.length &&\n\t\t\t/[A-Za-z0-9:!_-]/.test(text[nameEnd] as string)\n\t\t) {\n\t\t\tnameEnd++;\n\t\t}\n\t\tmode = {\n\t\t\tkind: \"tag\",\n\t\t\tname: text.slice(nameStart, nameEnd).toLowerCase(),\n\t\t\tclosing,\n\t\t\tquote: null,\n\t\t\tline: current(),\n\t\t\tatLineStart,\n\t\t\tinner: 0,\n\t\t};\n\t\tatLineStart = false;\n\t\treturn nameEnd;\n\t};\n\n\t/** Moves through a text part one character at a time. */\n\tconst scanText = (start: number, end: number) => {\n\t\tfor (let index = start; index < end; index++) {\n\t\t\tconst char = text[index] as string;\n\t\t\tif (char === \"\\n\") {\n\t\t\t\ttrimEnd[current()] = canTrimHere();\n\t\t\t\tif (index + 1 < end) beginLine(index + 1);\n\t\t\t\telse if (index + 1 === text.length) {\n\t\t\t\t\t// The file ends with a line break: there is no further line.\n\t\t\t\t} else pendingLine = index + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tswitch (mode.kind) {\n\t\t\t\tcase \"data\":\n\t\t\t\t\tif (char === \" \" || char === \"\\t\" || char === \"\\r\") break;\n\t\t\t\t\tif (char === \"<\") {\n\t\t\t\t\t\tif (text.startsWith(\"<!--\", index)) {\n\t\t\t\t\t\t\tmode = {\n\t\t\t\t\t\t\t\tkind: \"comment\",\n\t\t\t\t\t\t\t\tanchor: current(),\n\t\t\t\t\t\t\t\tlevel: levelOf(current()),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tatLineStart = false;\n\t\t\t\t\t\t\tindex += 3;\n\t\t\t\t\t\t} else if (text[index + 1] === \"/\" && isLetter(text[index + 2])) {\n\t\t\t\t\t\t\tindex = startTag(index, true) - 1;\n\t\t\t\t\t\t} else if (isLetter(text[index + 1]) || text[index + 1] === \"!\") {\n\t\t\t\t\t\t\tindex = startTag(index, false) - 1;\n\t\t\t\t\t\t} else atLineStart = false;\n\t\t\t\t\t} else atLineStart = false;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tag\":\n\t\t\t\t\tif (mode.quote !== null) {\n\t\t\t\t\t\tif (char === mode.quote) mode.quote = null;\n\t\t\t\t\t} else if (char === '\"' || char === \"'\") mode.quote = char;\n\t\t\t\t\telse if (char === \">\") finishTag(mode, text[index - 1] === \"/\");\n\t\t\t\t\telse if (char !== \" \" && char !== \"\\t\" && char !== \"\\r\") {\n\t\t\t\t\t\tatLineStart = false;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"comment\":\n\t\t\t\t\tif (text.startsWith(\"-->\", index)) {\n\t\t\t\t\t\tmode = { kind: \"data\" };\n\t\t\t\t\t\tindex += 2;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"raw\":\n\t\t\t\t\tif (char === \"<\" && closesElement(text, index, mode.name)) {\n\t\t\t\t\t\tindex = startTag(index, true) - 1;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"kept\":\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t};\n\n\t/** Applies a block tag to the stack, or to the count of block tags open inside an HTML tag. */\n\tconst applyTag = (part: DelimitedPart) => {\n\t\tconst role = roleOf(part);\n\t\tconst first = atLineStart;\n\t\tatLineStart = false;\n\t\tif (!role) return;\n\t\tif (mode.kind === \"tag\") {\n\t\t\tconst base = levelOf(mode.line) + 1;\n\t\t\tif (role.kind === \"open\") mode.inner++;\n\t\t\telse if (role.kind === \"close\") {\n\t\t\t\tmode.inner = Math.max(0, mode.inner - 1);\n\t\t\t\toutdent(current(), base + mode.inner, first);\n\t\t\t} else outdent(current(), base + mode.inner - 1, first);\n\t\t\treturn;\n\t\t}\n\t\tif (mode.kind === \"kept\") {\n\t\t\tif (role.kind === \"open\" && role.name === mode.name) mode.depth++;\n\t\t\telse if (\n\t\t\t\trole.kind === \"close\" &&\n\t\t\t\trole.name === mode.name &&\n\t\t\t\t--mode.depth === 0\n\t\t\t) {\n\t\t\t\tconst index = stack.findLastIndex((frame) => frame.kind === \"kiln\");\n\t\t\t\tif (index !== -1) stack.length = index;\n\t\t\t\tmode = { kind: \"data\" };\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (mode.kind !== \"data\") return;\n\t\tif (role.kind === \"open\") {\n\t\t\tstack.push({ kind: \"kiln\", name: role.name });\n\t\t\tif (\n\t\t\t\trole.name === \"verbatim\" ||\n\t\t\t\t((role.name === \"set\" || role.name === \"apply\") &&\n\t\t\t\t\t!(first && endsLine(part.end)))\n\t\t\t) {\n\t\t\t\tmode = {\n\t\t\t\t\tkind: \"kept\",\n\t\t\t\t\tname: role.name,\n\t\t\t\t\tdepth: 1,\n\t\t\t\t\tlevel: stack.length - 1,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst index =\n\t\t\trole.kind === \"branch\"\n\t\t\t\t? stack.findLastIndex((frame) => frame.kind === \"kiln\")\n\t\t\t\t: stack.findLastIndex(\n\t\t\t\t\t\t(frame) => frame.kind === \"kiln\" && frame.name === role.name,\n\t\t\t\t\t);\n\t\tif (index === -1) return;\n\t\t// Elements opened in this body or branch end with it.\n\t\tstack.length = role.kind === \"branch\" ? index + 1 : index;\n\t\toutdent(current(), index, first);\n\t};\n\n\tbeginLine(0);\n\tconst delimiters: { part: DelimitedPart; line: number }[] = [];\n\n\tfor (const part of parts) {\n\t\tif (pendingLine !== -1) {\n\t\t\tbeginLine(pendingLine);\n\t\t\tpendingLine = -1;\n\t\t}\n\t\tif (part.kind === \"text\") {\n\t\t\tscanText(part.start, part.end);\n\t\t\tcontinue;\n\t\t}\n\t\tconst line = current();\n\t\tif (part.kind === \"comment\") atLineStart = false;\n\t\telse {\n\t\t\tdelimiters.push({ part, line });\n\t\t\tif (part.kind === \"tag\") applyTag(part);\n\t\t\telse atLineStart = false;\n\t\t}\n\t\t// Lines that start inside this delimiter.\n\t\tfor (\n\t\t\tlet index = text.indexOf(\"\\n\", part.start);\n\t\t\tindex !== -1 && index < part.end;\n\t\t\tindex = text.indexOf(\"\\n\", index + 1)\n\t\t) {\n\t\t\ttrimEnd[current()] = part.kind === \"comment\";\n\t\t\tlines.push(\n\t\t\t\tpart.kind === \"comment\"\n\t\t\t\t\t? { kind: \"shift\", start: index + 1, anchor: line }\n\t\t\t\t\t: { kind: \"inside\", start: index + 1 },\n\t\t\t);\n\t\t}\n\t}\n\ttrimEnd[current()] ??= canTrimHere();\n\treturn { lines, trimEnd, delimiters };\n};\n\nconst widthOf = (whitespace: string, tabWidth: number): number => {\n\tlet width = 0;\n\tfor (const char of whitespace) width += char === \"\\t\" ? tabWidth : 1;\n\treturn width;\n};\n\n/**\n * Works out the edits that format a template, without checking them. See `formatEdits`.\n */\nconst planEdits = (\n\ttext: string,\n\tparts: readonly Part[],\n\toptions: Required<Omit<FormatOptions, \"range\">>,\n): FormatEdit[] => {\n\tconst { useTabs, tabWidth } = options;\n\tconst unit = useTabs ? \"\\t\" : \" \".repeat(tabWidth);\n\tconst indentOfWidth = (width: number) =>\n\t\tuseTabs\n\t\t\t? \"\\t\".repeat(Math.floor(width / tabWidth)) + \" \".repeat(width % tabWidth)\n\t\t\t: \" \".repeat(width);\n\tconst { lines, trimEnd, delimiters } = planLines(text, parts);\n\n\tconst oldIndent = (line: number) => {\n\t\tconst start = lines[line]?.start ?? 0;\n\t\tlet end = start;\n\t\twhile (text[end] === \" \" || text[end] === \"\\t\") end++;\n\t\treturn text.slice(start, end);\n\t};\n\t/** Where a line's content ends: at its line break, or the end of the file. */\n\tconst lineEnd = (line: number) => {\n\t\tconst next = lines[line + 1]?.start;\n\t\tif (next !== undefined) return next - 1;\n\t\treturn text.endsWith(\"\\n\") ? text.length - 1 : text.length;\n\t};\n\tconst isBlank = (line: number) => {\n\t\tconst info = lines[line] as Line;\n\t\treturn (\n\t\t\tinfo.kind !== \"keep\" &&\n\t\t\tinfo.kind !== \"inside\" &&\n\t\t\ttext.slice(info.start, lineEnd(line)).trim() === \"\"\n\t\t);\n\t};\n\n\t// Whitespace before the first content and after the last is output as written wherever the template is placed, so\n\t// it stays as it is, as does a file of only whitespace.\n\tlet firstContent = 0;\n\twhile (firstContent < lines.length && isBlank(firstContent)) firstContent++;\n\tlet lastContent = lines.length - 1;\n\twhile (lastContent >= 0 && isBlank(lastContent)) lastContent--;\n\n\tconst newIndents = new Map<number, string | null>();\n\tconst newIndent = (line: number): string | null => {\n\t\tif (newIndents.has(line)) return newIndents.get(line) ?? null;\n\t\tconst info = lines[line];\n\t\tlet indent: string | null = null;\n\t\tif (info?.kind === \"indent\") {\n\t\t\t// The first content line keeps its indentation. See `firstContent`.\n\t\t\tindent =\n\t\t\t\tline === firstContent ? oldIndent(line) : unit.repeat(info.level);\n\t\t} else if (info?.kind === \"shift\") {\n\t\t\tconst anchor = newIndent(info.anchor);\n\t\t\tconst moved =\n\t\t\t\tanchor === null\n\t\t\t\t\t? 0\n\t\t\t\t\t: widthOf(anchor, tabWidth) -\n\t\t\t\t\t\twidthOf(oldIndent(info.anchor), tabWidth);\n\t\t\tindent = indentOfWidth(\n\t\t\t\tMath.max(0, widthOf(oldIndent(line), tabWidth) + moved),\n\t\t\t);\n\t\t}\n\t\tnewIndents.set(line, indent);\n\t\treturn indent;\n\t};\n\n\tconst edits: FormatEdit[] = [];\n\t// Blank lines go where the line before is blank too, so runs become one.\n\tlet previousBlank = false;\n\n\tfor (const [index, info] of lines.entries()) {\n\t\tif (index > lastContent) break;\n\t\tif (index < firstContent) continue;\n\t\tconst start = info.start;\n\t\tconst end = lineEnd(index);\n\t\tif (isBlank(index)) {\n\t\t\tif (info.kind === \"indent\" && previousBlank) {\n\t\t\t\tedits.push({ start, end: end + 1, text: \"\" });\n\t\t\t} else if (end > start) edits.push({ start, end, text: \"\" });\n\t\t\tpreviousBlank = true;\n\t\t\tcontinue;\n\t\t}\n\t\tpreviousBlank = false;\n\n\t\tconst indent = newIndent(index);\n\t\tif (indent !== null) {\n\t\t\tconst current = oldIndent(index);\n\t\t\tif (current !== indent) {\n\t\t\t\tedits.push({ start, end: start + current.length, text: indent });\n\t\t\t}\n\t\t}\n\t\tif (trimEnd[index] && info.kind !== \"keep\" && index !== lastContent) {\n\t\t\tlet from = end;\n\t\t\twhile (from > start && /[ \\t\\r]/.test(text[from - 1] as string)) from--;\n\t\t\tif (from < end && !insideDelimiter(delimiters, from)) {\n\t\t\t\tedits.push({ start: from, end, text: \"\" });\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const { part, line } of delimiters) {\n\t\tconst indent = newIndent(line) ?? oldIndent(line);\n\t\tconst printed = printDelimiter(text, part, indent, unit);\n\t\tif (printed !== null && printed !== text.slice(part.start, part.end)) {\n\t\t\tedits.push({ start: part.start, end: part.end, text: printed });\n\t\t}\n\t}\n\n\treturn edits.sort((a, b) => a.start - b.start || a.end - b.end);\n};\n\nconst insideDelimiter = (\n\tdelimiters: readonly { part: DelimitedPart }[],\n\toffset: number,\n) => delimiters.some(({ part }) => offset > part.start && offset < part.end);\n\nconst applyEdits = (text: string, edits: readonly FormatEdit[]): string => {\n\tlet out = \"\";\n\tlet position = 0;\n\tfor (const edit of edits) {\n\t\tout += text.slice(position, edit.start) + edit.text;\n\t\tposition = edit.end;\n\t}\n\treturn out + text.slice(position);\n};\n\n/** Removes whitespace at the start and end of lines, and blank lines, which the formatter may change. */\nconst normaliseText = (value: string) =>\n\tvalue\n\t\t.replace(/[ \\t\\r\\f\\v]+$/gm, \"\")\n\t\t.replace(/^[ \\t\\f\\v]+/gm, \"\")\n\t\t.replace(/\\n{2,}/g, \"\\n\");\n\n/** A copy of a syntax tree without positions or layout, with text normalised and text left empty by that removed. */\nconst normaliseTree = (value: unknown): unknown => {\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.map(normaliseTree)\n\t\t\t.filter(\n\t\t\t\t(item) =>\n\t\t\t\t\t!(\n\t\t\t\t\t\ttypeof item === \"object\" &&\n\t\t\t\t\t\titem !== null &&\n\t\t\t\t\t\t(item as { type?: unknown }).type === \"Text\" &&\n\t\t\t\t\t\t(item as { value?: unknown }).value === \"\"\n\t\t\t\t\t),\n\t\t\t);\n\t}\n\tif (typeof value !== \"object\" || value === null) return value;\n\tconst copy: Record<string, unknown> = {};\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tif (\n\t\t\tkey === \"span\" ||\n\t\t\tkey === \"nameSpan\" ||\n\t\t\tkey === \"lines\" ||\n\t\t\tkey === \"indent\"\n\t\t)\n\t\t\tcontinue;\n\t\tcopy[key] =\n\t\t\tkey === \"value\" && (value as { type?: unknown }).type === \"Text\"\n\t\t\t\t? normaliseText(item as string)\n\t\t\t\t: normaliseTree(item);\n\t}\n\treturn copy;\n};\n\n/** The syntax tree as JSON, without the whitespace the formatter may change, so two trees can be compared. */\nconst shape = (text: string): string =>\n\tJSON.stringify(normaliseTree(parse(text, { isTest }).ast));\n\n/**\n * Works out the edits that format a template, in source order and never overlapping. Editors apply these so the cursor and undo history stay put. See `format` for what it throws.\n */\nexport const formatEdits = (\n\ttext: string,\n\toptions: FormatOptions = {},\n): FormatEdit[] => {\n\tconst parsed = parse(text, { isTest });\n\tif (parsed.problems.length > 0) {\n\t\tconst source = new Source(\"template.kiln\", text);\n\t\tthrow new KilnSyntaxError(\n\t\t\tparsed.problems.map((problem) =>\n\t\t\t\tcreateDiagnostic(source, problem.code, problem.message, problem.span),\n\t\t\t),\n\t\t\tsource,\n\t\t);\n\t}\n\n\tconst tabWidth = options.tabWidth ?? 4;\n\tif (!Number.isInteger(tabWidth) || tabWidth < 1) {\n\t\tthrow new RangeError(\n\t\t\t`tabWidth must be a whole number of at least 1, but got ${tabWidth}`,\n\t\t);\n\t}\n\tlet edits = planEdits(text, scanParts(text).parts, {\n\t\tuseTabs: options.useTabs ?? true,\n\t\ttabWidth,\n\t});\n\tconst range = options.range;\n\tif (range) {\n\t\tedits = edits.filter(\n\t\t\t(edit) => edit.end >= range.start && edit.start <= range.end,\n\t\t);\n\t}\n\n\tconst formatted = applyEdits(text, edits);\n\tconst changed = parse(formatted, { isTest });\n\tif (changed.problems.length > 0 || shape(formatted) !== shape(text)) {\n\t\tthrow new KilnFormatError(\n\t\t\t\"Formatting would have changed what this template renders, so nothing was changed. This is a bug in Kiln's formatter.\",\n\t\t);\n\t}\n\treturn edits;\n};\n\n/**\n * Formats a template: indents it to match its structure, and tidies spacing inside outputs and tags. Never changes what it renders.\n *\n * Throws `KilnSyntaxError` when the template has syntax errors, `KilnFormatError` if the safety check fails, and\n * `RangeError` for a `tabWidth` that isn't a whole number of at least 1.\n *\n * ```ts\n * const formatted = format(source, { useTabs: false, tabWidth: 2 });\n * ```\n */\nexport const format = (text: string, options: FormatOptions = {}): string =>\n\tapplyEdits(text, formatEdits(text, options));\n"],"mappings":"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"}
package/dist/format.js CHANGED
@@ -1 +1 @@
1
- export { t as KilnFormatError, n as format, r as formatEdits } from "./format-CJoqhehX.js";
1
+ export { t as KilnFormatError, n as format, r as formatEdits } from "./format-CLCViAPn.js";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { A as IslandEntry, B as ShapeKey, C as AnalysedProp, D as KilnCache, E as AbortSignalLike, F as deferIslands, G as TemplateLocation, H as Diagnostic, I as islandScript, K as Position, L as streamIslands, M as IslandMode, N as JsonValue, O as RenderInfo, P as StreamedIslands, R as EscapeStrategy, S as PrecompiledTemplate, T as TemplateReference, U as KilnRenderError, V as TypeName, W as KilnSyntaxError, _ as memoryLoader, a as KilnTemplates, b as HtmlCheckLevel, c as Loader, d as TemplateBlock, f as TemplateContext, g as memoryCache, h as createKiln, i as KilnSchema, j as IslandManifest, k as DeferredIslands, l as RenderAware, m as TypeChecks, n as KilnCallable, o as KilnTypes, p as TemplateName, r as KilnOptions, s as Limits, t as Kiln, u as RenderOptions, v as withRender, w as Analysis, x as HtmlChecks, y as HtmlCheck, z as PropType } from "./environment-BlQ8V4tX.js";
1
+ import { c as PropType, l as ShapeKey, m as Position, p as TypeName, r as EscapeStrategy } from "./ast-1akLiy-A.js";
2
+ import { A as AbortSignalLike, B as islandScript, C as HtmlCheck, D as AnalysedProp, E as PrecompiledTemplate, F as IslandManifest, G as TemplateLocation, H as Diagnostic, I as IslandMode, L as JsonValue, M as RenderInfo, N as DeferredIslands, O as Analysis, P as IslandEntry, R as StreamedIslands, S as withRender, T as HtmlChecks, U as KilnRenderError, V as streamIslands, W as KilnSyntaxError, _ as TemplateName, a as KilnGlobals, b as memoryCache, c as KilnTemplates, d as Limits, f as Loader, g as TemplateContext, h as TemplateBlock, i as KilnFunctions, j as KilnCache, k as TemplateReference, l as KilnTests, m as RenderOptions, n as KilnCallable, o as KilnOptions, p as RenderAware, r as KilnFilters, s as KilnSchema, t as Kiln, u as KilnTypes, v as TypeChecks, w as HtmlCheckLevel, x as memoryLoader, y as createKiln, z as deferIslands } from "./environment-DkfrPazl.js";
2
3
  //#region src/runtime/markup.d.ts
3
4
  /**
4
5
  * A string that is already safe to output with one escaping strategy.
@@ -86,5 +87,5 @@ export declare const generateManifest: <State, Schema extends KilnSchema>(kiln:
86
87
  */
87
88
  export declare const generateTypes: (kiln: Pick<Kiln, "analyze">, names: Iterable<string>) => Promise<string>;
88
89
  //#endregion
89
- export { type AbortSignalLike, type AnalysedProp, type Analysis, type DeferredIslands, type Diagnostic, type EscapeStrategy, type HtmlCheck, type HtmlCheckLevel, type HtmlChecks, type IslandEntry, type IslandManifest, type IslandMode, type JsonValue, type Kiln, type KilnCache, type KilnCallable, type KilnManifest, type KilnOptions, KilnRenderError, type KilnSchema, KilnSyntaxError, type KilnTemplates, type KilnTypes, type Limits, type Loader, type Position, type PrecompiledTemplate, type PropType, type RenderAware, type RenderInfo, type RenderOptions, type ShapeKey, type StreamedIslands, type TemplateBlock, type TemplateContext, type TemplateLocation, type TemplateName, type TemplateReference, type TypeChecks, type TypeName, createKiln, deferIslands, islandScript, memoryCache, memoryLoader, streamIslands, withRender };
90
+ export { type AbortSignalLike, type AnalysedProp, type Analysis, type DeferredIslands, type Diagnostic, type EscapeStrategy, type HtmlCheck, type HtmlCheckLevel, type HtmlChecks, type IslandEntry, type IslandManifest, type IslandMode, type JsonValue, type Kiln, type KilnCache, type KilnCallable, type KilnFilters, type KilnFunctions, type KilnGlobals, type KilnManifest, type KilnOptions, KilnRenderError, type KilnSchema, KilnSyntaxError, type KilnTemplates, type KilnTests, type KilnTypes, type Limits, type Loader, type Position, type PrecompiledTemplate, type PropType, type RenderAware, type RenderInfo, type RenderOptions, type ShapeKey, type StreamedIslands, type TemplateBlock, type TemplateContext, type TemplateLocation, type TemplateName, type TemplateReference, type TypeChecks, type TypeName, createKiln, deferIslands, islandScript, memoryCache, memoryLoader, streamIslands, withRender };
90
91
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/runtime/markup.ts","../src/runtime/exposure.ts","../src/html/scanner.ts","../src/manifest.ts","../src/typegen.ts"],"mappings":";;;;;;;qBAOa;WAEF;WACA,UAAU;EAFpB,YACU,eACA,UAAU;EAGpB;;;;;;;qBAUY,OAAI,eACH,WACH,mBACR;;qBAGU,SAAM,mBAAqB,SAAS;;;;;;;;;;;;qBCjBpC;;;;;;;;;;qBAWA;;;;;;;qBCsCA,eAAY;;;;KCvDb;;EAEX;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;;;;;;;;;qBAgBA,mBAAoB,OAAO,eAAe,YAAU,MAC1D,KAAK,OAAO,SAAO;EACd;;;;;;;;;;;;;;qBCsCC,gBAAa,MACnB,KAAK,kBAAgB,OACpB,qBACL"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/runtime/markup.ts","../src/runtime/exposure.ts","../src/html/scanner.ts","../src/manifest.ts","../src/typegen.ts"],"mappings":";;;;;;;;qBAOa;WAEF;WACA,UAAU;EAFpB,YACU,eACA,UAAU;EAGpB;;;;;;;qBAUY,OAAI,eACH,WACH,mBACR;;qBAGU,SAAM,mBAAqB,SAAS;;;;;;;;;;;;qBCjBpC;;;;;;;;;;qBAWA;;;;;;;qBCsCA,eAAY;;;;KCvDb;;EAEX;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;;;;;;;;;qBAgBA,mBAAoB,OAAO,eAAe,YAAU,MAC1D,KAAK,OAAO,SAAO;EACd;;;;;;;;;;;;;;qBCsCC,gBAAa,MACnB,KAAK,kBAAgB,OACpB,qBACL"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { $ as silence, $t as ownerOf, A as addSlotFlags, At as atFront, B as fail, Bt as checkLate, C as scanHtml, Ct as truthy, D as getAttr, Dt as BLOCKED_NAMES, E as toTime, Et as valueLabel, F as consumeAt, Ft as passesFrom, G as indentedText, Gt as Run, H as holdData, Ht as recordAddition, I as embed, It as runAhead, J as locate, Jt as endCapture, K as isPromise, Kt as blockScope, L as embedKeyed, Lt as runSequence, M as all, Mt as isRoot, N as checkLength, Nt as mayRunAhead, O as getMethod, Ot as expose, P as consume, Pt as passesAhead, Q as renderTemplate, Qt as lookup$1, R as embedScope, Rt as OUTLET_INDENT, S as readProperty, St as toStringArgument, T as toPlainData, Tt as unwrap, U as importsFor, Ut as takeOutput, V as hold, Vt as closeOutlets, W as indentText, Wt as RenderState, X as markCaller, Xt as startCapture, Y as locating, Yt as fillScope, Z as passData, Zt as childScope, _ as integerArgument, _t as matchesType, a as ESCAPE_STRATEGIES, an as isSafe, at as FORMATTERS, b as mapContents, bt as toNumber, c as contains, cn as escapeUrl, ct as describeType, d as Kept, dn as resolveTemplateName, dt as hashKeys, en as sectionCopy, et as startPlace, f as checkDepth, fn as KilnIssue, ft as isEmpty, g as insertedPiece, gn as issue, gt as mapKeys, h as functionArgument, hn as createDiagnostic, ht as literalOptions, i as DEFAULT_SLOT, in as Markup, it as write, j as after, jt as contentSets, k as readableNames, kt as lookup, l as operatorFunction, ln as escapeWith, lt as describeValue, m as fromPieces, mn as KilnSyntaxError, mt as isPlainObject, n as parse, nn as variableNames, nt as unhold, o as builtinFunctions, on as safe, ot as MISSING, p as entriesOf, pn as KilnRenderError, pt as isHashLike, q as loadTemplate, qt as emptyOut, r as scanParts, rn as suggest, rt as withCaller, s as builtinTests, sn as escapeHtml, st as compare, t as Source, tn as sectionScope, tt as step, u as rangeOperator, un as TEMPLATE_EXTENSION, ut as equals, v as isHtmlStrategy, vt as setOwn, w as toPieces, wt as typeName, x as markupText, xt as toStr, y as listOf$1, yt as templateFunctions, z as endPlace, zt as additionsIn } from "./source-BQxJGo5Y.js";
1
+ import { $ as withCaller, $t as suggest, A as consume, At as passesAhead, B as indentText, Bt as RenderState, C as getAttr, Ct as BLOCKED_NAMES, D as after, Dt as contentSets, E as addSlotFlags, Et as atFront, F as endPlace, Ft as additionsIn, G as locating, Gt as fillScope, H as isPromise, Ht as blockScope, I as fail, It as checkLate, J as renderTemplate, Jt as lookup$1, K as markCaller, Kt as startCapture, L as hold, Lt as closeOutlets, M as embed, Mt as runAhead, N as embedKeyed, Nt as runSequence, O as all, Ot as isRoot, P as embedScope, Pt as OUTLET_INDENT, Q as unhold, Qt as variableNames, R as holdData, Rt as recordAddition, S as toTime, St as valueLabel, T as readableNames, Tt as lookup, U as loadTemplate, Ut as emptyOut, V as indentedText, Vt as Run, W as locate, Wt as endCapture, X as startPlace, Xt as sectionCopy, Y as silence, Yt as ownerOf, Z as step, Zt as sectionScope, _ as markupText, _t as toStr, a as operatorFunction, an as escapeWith, at as describeValue, b as toPieces, bt as typeName, c as checkDepth, cn as KilnIssue, ct as isEmpty, d as functionArgument, dn as createDiagnostic, dt as literalOptions, en as Markup, et as write, f as insertedPiece, fn as issue, ft as mapKeys, g as mapContents, gt as toNumber, h as listOf$1, ht as templateFunctions, i as contains, in as escapeUrl, it as describeType, j as consumeAt, jt as passesFrom, k as checkLength, kt as mayRunAhead, l as entriesOf, ln as KilnRenderError, lt as isHashLike, m as isHtmlStrategy, mt as setOwn, n as builtinFunctions, nn as safe, nt as MISSING, o as rangeOperator, on as TEMPLATE_EXTENSION, ot as equals, p as integerArgument, pt as matchesType, q as passData, qt as childScope, r as builtinTests, rn as escapeHtml, rt as compare, s as Kept, sn as resolveTemplateName, st as hashKeys, t as Source, tn as isSafe, tt as FORMATTERS, u as fromPieces, un as KilnSyntaxError, ut as isPlainObject, v as readProperty, vt as toStringArgument, w as getMethod, wt as expose, x as toPlainData, xt as unwrap, y as scanHtml, yt as truthy, z as importsFor, zt as takeOutput } from "./source-BHcxowQp.js";
2
+ import { a as ESCAPE_STRATEGIES, i as DEFAULT_SLOT, r as scanParts, t as parse } from "./parser-BU5dThuk.js";
2
3
  //#region src/analyzer.ts
3
4
  const MAX_DEPTH$1 = 1e3;
4
5
  /** The statement lists nested inside a statement. */
@@ -1565,9 +1566,9 @@ const checkReferences = async (env, template) => {
1565
1566
  };
1566
1567
  //#endregion
1567
1568
  //#region src/runtime/cache.ts
1568
- const VERSION = 2;
1569
+ const VERSION = 3;
1569
1570
  /** Makes the value a store saves. */
1570
- const packSaved = ({ html, additions, islands, islandScript }) => JSON.stringify({
1571
+ const packSaved = ({ html, additions, islands, islandScript, tags }) => JSON.stringify({
1571
1572
  v: VERSION,
1572
1573
  html,
1573
1574
  add: additions.map((addition) => [
@@ -1576,7 +1577,8 @@ const packSaved = ({ html, additions, islands, islandScript }) => JSON.stringify
1576
1577
  addition.once
1577
1578
  ]),
1578
1579
  isl: islands.map((island) => [island.template, island.props]),
1579
- scr: islandScript
1580
+ scr: islandScript,
1581
+ tag: tags
1580
1582
  });
1581
1583
  /** Reads a value a store returned, or returns null when it isn't one `packSaved` made. */
1582
1584
  const unpackSaved = (value) => {
@@ -1587,8 +1589,8 @@ const unpackSaved = (value) => {
1587
1589
  return null;
1588
1590
  }
1589
1591
  if (typeof parsed !== "object" || parsed === null) return null;
1590
- const { v, html, add, isl, scr } = parsed;
1591
- if (v !== VERSION || typeof html !== "string" || !Array.isArray(add) || !Array.isArray(isl) || typeof scr !== "boolean") return null;
1592
+ const { v, html, add, isl, scr, tag } = parsed;
1593
+ if (v !== VERSION || typeof html !== "string" || !Array.isArray(add) || !Array.isArray(isl) || typeof scr !== "boolean" || !Array.isArray(tag) || tag.some((item) => typeof item !== "string")) return null;
1592
1594
  const additions = [];
1593
1595
  for (const item of add) {
1594
1596
  if (!Array.isArray(item)) return null;
@@ -1614,7 +1616,8 @@ const unpackSaved = (value) => {
1614
1616
  html,
1615
1617
  additions,
1616
1618
  islands,
1617
- islandScript: scr
1619
+ islandScript: scr,
1620
+ tags: tag
1618
1621
  };
1619
1622
  };
1620
1623
  //#endregion
@@ -3152,7 +3155,8 @@ var Compiler = class {
3152
3155
  const inner = sectionScope(scope);
3153
3156
  const capture = startCapture(run, {
3154
3157
  scope: inner,
3155
- deferred: []
3158
+ deferred: [],
3159
+ tags: /* @__PURE__ */ new Set()
3156
3160
  });
3157
3161
  return after(body(run.within(capture), inner, out), () => {
3158
3162
  endCapture(run, capture);
@@ -3164,6 +3168,8 @@ var Compiler = class {
3164
3168
  return after(found, (value) => {
3165
3169
  const saved = typeof value === "string" ? unpackSaved(value) : null;
3166
3170
  if (saved) {
3171
+ const around = run.capture?.section;
3172
+ if (around) for (const tag of saved.tags) around.tags.add(tag);
3167
3173
  const replay = (front) => {
3168
3174
  const islands = state.islands;
3169
3175
  if (islands?.mode === "defer" && saved.islands.length > 0) {
@@ -3185,17 +3191,20 @@ var Compiler = class {
3185
3191
  const start = out.s.length;
3186
3192
  const hadScript = state.islandScript;
3187
3193
  return after(fresh(), (capture) => {
3194
+ const tags = [...capture.section?.tags ?? []].sort();
3188
3195
  const manifest = state.islands?.mode === "defer" ? state.islands.manifest : null;
3189
3196
  const value = packSaved({
3190
3197
  html: out.s.slice(start),
3191
3198
  additions: additionsIn(capture.additions ?? []),
3192
3199
  islands: manifest ? (capture.section?.deferred ?? []).flatMap((id) => manifest[id] ?? []) : [],
3193
- islandScript: !hadScript && state.islandScript
3200
+ islandScript: !hadScript && state.islandScript,
3201
+ tags
3194
3202
  });
3195
3203
  return locating(run, span, () => store.set({
3196
3204
  key: name,
3197
3205
  value,
3198
- seconds: time
3206
+ seconds: time,
3207
+ tags
3199
3208
  }), "The cache store");
3200
3209
  });
3201
3210
  });
@@ -8565,7 +8574,7 @@ const memoryLoader = (templates) => {
8565
8574
  };
8566
8575
  /**
8567
8576
  * A store for the `cache` tag that keeps output in memory, for a long-running server. Holds up to `max` copies, 1,000
8568
- * by default, and drops the oldest first.
8577
+ * by default, and drops the oldest first. `clear` removes every copy, or those with any of the tags passed.
8569
8578
  *
8570
8579
  * ```ts
8571
8580
  * const kiln = createKiln({ loader, cache: memoryCache() });
@@ -8581,14 +8590,20 @@ const memoryCache = ({ max = 1e3 } = {}) => {
8581
8590
  entries.delete(key);
8582
8591
  return null;
8583
8592
  },
8584
- set: ({ key, value, seconds }) => {
8593
+ set: ({ key, value, seconds, tags }) => {
8585
8594
  entries.delete(key);
8586
8595
  entries.set(key, {
8587
8596
  value,
8588
- expires: seconds === null ? Infinity : Date.now() + seconds * 1e3
8597
+ expires: seconds === null ? Infinity : Date.now() + seconds * 1e3,
8598
+ tags
8589
8599
  });
8590
8600
  const oldest = entries.keys().next();
8591
8601
  if (entries.size > max && !oldest.done) entries.delete(oldest.value);
8602
+ },
8603
+ /** Removes every copy, or with `tags`, the copies that have any of them. */
8604
+ clear: ({ tags } = {}) => {
8605
+ if (!tags) return entries.clear();
8606
+ for (const [key, entry] of entries) if (entry.tags.some((tag) => tags.includes(tag))) entries.delete(key);
8592
8607
  }
8593
8608
  };
8594
8609
  };