summaryrefslogtreecommitdiff
path: root/packages/astro-parser/src/parse/state/mustache.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro-parser/src/parse/state/mustache.ts')
-rw-r--r--packages/astro-parser/src/parse/state/mustache.ts784
1 files changed, 392 insertions, 392 deletions
diff --git a/packages/astro-parser/src/parse/state/mustache.ts b/packages/astro-parser/src/parse/state/mustache.ts
index d07162049..9300627e1 100644
--- a/packages/astro-parser/src/parse/state/mustache.ts
+++ b/packages/astro-parser/src/parse/state/mustache.ts
@@ -9,404 +9,404 @@ import { TemplateNode } from '../../interfaces.js';
type TODO = any;
function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after: boolean) {
- if (!block.children || block.children.length === 0) return; // AwaitBlock
+ if (!block.children || block.children.length === 0) return; // AwaitBlock
- const first_child = block.children[0];
- const last_child = block.children[block.children.length - 1];
+ const first_child = block.children[0];
+ const last_child = block.children[block.children.length - 1];
- if (first_child.type === 'Text' && trim_before) {
- first_child.data = first_child.data.trimStart();
- if (!first_child.data) block.children.shift();
- }
+ if (first_child.type === 'Text' && trim_before) {
+ first_child.data = first_child.data.trimStart();
+ if (!first_child.data) block.children.shift();
+ }
- if (last_child.type === 'Text' && trim_after) {
- last_child.data = last_child.data.trimEnd();
- if (!last_child.data) block.children.pop();
- }
+ if (last_child.type === 'Text' && trim_after) {
+ last_child.data = last_child.data.trimEnd();
+ if (!last_child.data) block.children.pop();
+ }
- if (block.else) {
- trim_whitespace(block.else, trim_before, trim_after);
- }
+ if (block.else) {
+ trim_whitespace(block.else, trim_before, trim_after);
+ }
- if (first_child.elseif) {
- trim_whitespace(first_child, trim_before, trim_after);
- }
+ if (first_child.elseif) {
+ trim_whitespace(first_child, trim_before, trim_after);
+ }
}
export default function mustache(parser: Parser) {
- const start = parser.index;
- parser.index += 1;
-
- parser.allow_whitespace();
-
- // {/if}, {/each}, {/await} or {/key}
- if (parser.eat('/')) {
- let block = parser.current();
- let expected: TODO;
-
- if (closing_tag_omitted(block.name)) {
- block.end = start;
- parser.stack.pop();
- block = parser.current();
- }
-
- if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
- block.end = start;
- parser.stack.pop();
- block = parser.current();
-
- expected = 'await';
- }
-
- if (block.type === 'IfBlock') {
- expected = 'if';
- } else if (block.type === 'EachBlock') {
- expected = 'each';
- } else if (block.type === 'AwaitBlock') {
- expected = 'await';
- } else if (block.type === 'KeyBlock') {
- expected = 'key';
- } else {
- parser.error({
- code: 'unexpected-block-close',
- message: 'Unexpected block closing tag',
- });
- }
-
- parser.eat(expected, true);
- parser.allow_whitespace();
- parser.eat('}', true);
-
- while (block.elseif) {
- block.end = parser.index;
- parser.stack.pop();
- block = parser.current();
-
- if (block.else) {
- block.else.end = start;
- }
- }
-
- // strip leading/trailing whitespace as necessary
- const char_before = parser.template[block.start - 1];
- const char_after = parser.template[parser.index];
- const trim_before = !char_before || whitespace.test(char_before);
- const trim_after = !char_after || whitespace.test(char_after);
-
- trim_whitespace(block, trim_before, trim_after);
-
- block.end = parser.index;
- parser.stack.pop();
- } else if (parser.eat(':else')) {
- if (parser.eat('if')) {
- parser.error({
- code: 'invalid-elseif',
- message: "'elseif' should be 'else if'",
- });
- }
-
- parser.allow_whitespace();
-
- // :else if
- if (parser.eat('if')) {
- const block = parser.current();
- if (block.type !== 'IfBlock') {
- parser.error({
- code: 'invalid-elseif-placement',
- message: parser.stack.some((block) => block.type === 'IfBlock')
- ? `Expected to close ${to_string(block)} before seeing {:else if ...} block`
- : 'Cannot have an {:else if ...} block outside an {#if ...} block',
- });
- }
-
- parser.require_whitespace();
-
- const expression = read_expression(parser);
-
- parser.allow_whitespace();
- parser.eat('}', true);
-
- block.else = {
- start: parser.index,
- end: null,
- type: 'ElseBlock',
- children: [
- {
- start: parser.index,
- end: null,
- type: 'IfBlock',
- elseif: true,
- expression,
- children: [],
- },
- ],
- };
-
- parser.stack.push(block.else.children[0]);
- } else {
- // :else
- const block = parser.current();
- if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
- parser.error({
- code: 'invalid-else-placement',
- message: parser.stack.some((block) => block.type === 'IfBlock' || block.type === 'EachBlock')
- ? `Expected to close ${to_string(block)} before seeing {:else} block`
- : 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block',
- });
- }
-
- parser.allow_whitespace();
- parser.eat('}', true);
-
- block.else = {
- start: parser.index,
- end: null,
- type: 'ElseBlock',
- children: [],
- };
-
- parser.stack.push(block.else);
- }
- } else if (parser.match(':then') || parser.match(':catch')) {
- const block = parser.current();
- const is_then = parser.eat(':then') || !parser.eat(':catch');
-
- if (is_then) {
- if (block.type !== 'PendingBlock') {
- parser.error({
- code: 'invalid-then-placement',
- message: parser.stack.some((block) => block.type === 'PendingBlock')
- ? `Expected to close ${to_string(block)} before seeing {:then} block`
- : 'Cannot have an {:then} block outside an {#await ...} block',
- });
- }
- } else {
- if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
- parser.error({
- code: 'invalid-catch-placement',
- message: parser.stack.some((block) => block.type === 'ThenBlock' || block.type === 'PendingBlock')
- ? `Expected to close ${to_string(block)} before seeing {:catch} block`
- : 'Cannot have an {:catch} block outside an {#await ...} block',
- });
- }
- }
-
- block.end = start;
- parser.stack.pop();
- const await_block = parser.current();
-
- if (!parser.eat('}')) {
- parser.require_whitespace();
- await_block[is_then ? 'value' : 'error'] = read_context(parser);
- parser.allow_whitespace();
- parser.eat('}', true);
- }
-
- const new_block: TemplateNode = {
- start,
- // @ts-ignore
- end: null,
- type: is_then ? 'ThenBlock' : 'CatchBlock',
- children: [],
- skip: false,
- };
-
- await_block[is_then ? 'then' : 'catch'] = new_block;
- parser.stack.push(new_block);
- } else if (parser.eat('#')) {
- // {#if foo}, {#each foo} or {#await foo}
- let type;
-
- if (parser.eat('if')) {
- type = 'IfBlock';
- } else if (parser.eat('each')) {
- type = 'EachBlock';
- } else if (parser.eat('await')) {
- type = 'AwaitBlock';
- } else if (parser.eat('key')) {
- type = 'KeyBlock';
- } else {
- parser.error({
- code: 'expected-block-type',
- message: 'Expected if, each, await or key',
- });
- }
-
- parser.require_whitespace();
-
- const expression = read_expression(parser);
-
- // @ts-ignore
- const block: TemplateNode =
- type === 'AwaitBlock'
- ? {
- start,
- end: null,
- type,
- expression,
- value: null,
- error: null,
- pending: {
- start: null,
- end: null,
- type: 'PendingBlock',
- children: [],
- skip: true,
- },
- then: {
- start: null,
- end: null,
- type: 'ThenBlock',
- children: [],
- skip: true,
- },
- catch: {
- start: null,
- end: null,
- type: 'CatchBlock',
- children: [],
- skip: true,
- },
- }
- : {
- start,
- end: null,
- type,
- expression,
- children: [],
- };
-
- parser.allow_whitespace();
-
- // {#each} blocks must declare a context – {#each list as item}
- if (type === 'EachBlock') {
- parser.eat('as', true);
- parser.require_whitespace();
-
- block.context = read_context(parser);
-
- parser.allow_whitespace();
-
- if (parser.eat(',')) {
- parser.allow_whitespace();
- block.index = parser.read_identifier();
- if (!block.index) {
- parser.error({
- code: 'expected-name',
- message: 'Expected name',
- });
- }
-
- parser.allow_whitespace();
- }
-
- if (parser.eat('(')) {
- parser.allow_whitespace();
-
- block.key = read_expression(parser);
- parser.allow_whitespace();
- parser.eat(')', true);
- parser.allow_whitespace();
- }
- }
-
- const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
- if (await_block_shorthand) {
- parser.require_whitespace();
- block.value = read_context(parser);
- parser.allow_whitespace();
- }
-
- const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
- if (await_block_catch_shorthand) {
- parser.require_whitespace();
- block.error = read_context(parser);
- parser.allow_whitespace();
- }
-
- parser.eat('}', true);
-
- // @ts-ignore
- parser.current().children.push(block);
- parser.stack.push(block);
-
- if (type === 'AwaitBlock') {
- let child_block;
- if (await_block_shorthand) {
- block.then.skip = false;
- child_block = block.then;
- } else if (await_block_catch_shorthand) {
- block.catch.skip = false;
- child_block = block.catch;
- } else {
- block.pending.skip = false;
- child_block = block.pending;
- }
-
- child_block.start = parser.index;
- parser.stack.push(child_block);
- }
- } else if (parser.eat('@html')) {
- // {@html content} tag
- parser.require_whitespace();
-
- const expression = read_expression(parser);
-
- parser.allow_whitespace();
- parser.eat('}', true);
-
- // @ts-ignore
- parser.current().children.push({
- start,
- end: parser.index,
- type: 'RawMustacheTag',
- expression,
- });
- } else if (parser.eat('@debug')) {
- // let identifiers;
-
- // // Implies {@debug} which indicates "debug all"
- // if (parser.read(/\s*}/)) {
- // identifiers = [];
- // } else {
- // const expression = read_expression(parser);
-
- // identifiers = expression.type === 'SequenceExpression'
- // ? expression.expressions
- // : [expression];
-
- // identifiers.forEach(node => {
- // if (node.type !== 'Identifier') {
- // parser.error({
- // code: 'invalid-debug-args',
- // message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
- // }, node.start);
- // }
- // });
-
- // parser.allow_whitespace();
- // parser.eat('}', true);
- // }
-
- // parser.current().children.push({
- // start,
- // end: parser.index,
- // type: 'DebugTag',
- // identifiers
- // });
- throw new Error('@debug not yet supported');
- } else {
- const expression = read_expression(parser);
-
- parser.allow_whitespace();
- parser.eat('}', true);
-
- // @ts-ignore
- parser.current().children.push({
- start,
- end: parser.index,
- type: 'MustacheTag',
- expression,
- });
- }
+ const start = parser.index;
+ parser.index += 1;
+
+ parser.allow_whitespace();
+
+ // {/if}, {/each}, {/await} or {/key}
+ if (parser.eat('/')) {
+ let block = parser.current();
+ let expected: TODO;
+
+ if (closing_tag_omitted(block.name)) {
+ block.end = start;
+ parser.stack.pop();
+ block = parser.current();
+ }
+
+ if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
+ block.end = start;
+ parser.stack.pop();
+ block = parser.current();
+
+ expected = 'await';
+ }
+
+ if (block.type === 'IfBlock') {
+ expected = 'if';
+ } else if (block.type === 'EachBlock') {
+ expected = 'each';
+ } else if (block.type === 'AwaitBlock') {
+ expected = 'await';
+ } else if (block.type === 'KeyBlock') {
+ expected = 'key';
+ } else {
+ parser.error({
+ code: 'unexpected-block-close',
+ message: 'Unexpected block closing tag',
+ });
+ }
+
+ parser.eat(expected, true);
+ parser.allow_whitespace();
+ parser.eat('}', true);
+
+ while (block.elseif) {
+ block.end = parser.index;
+ parser.stack.pop();
+ block = parser.current();
+
+ if (block.else) {
+ block.else.end = start;
+ }
+ }
+
+ // strip leading/trailing whitespace as necessary
+ const char_before = parser.template[block.start - 1];
+ const char_after = parser.template[parser.index];
+ const trim_before = !char_before || whitespace.test(char_before);
+ const trim_after = !char_after || whitespace.test(char_after);
+
+ trim_whitespace(block, trim_before, trim_after);
+
+ block.end = parser.index;
+ parser.stack.pop();
+ } else if (parser.eat(':else')) {
+ if (parser.eat('if')) {
+ parser.error({
+ code: 'invalid-elseif',
+ message: "'elseif' should be 'else if'",
+ });
+ }
+
+ parser.allow_whitespace();
+
+ // :else if
+ if (parser.eat('if')) {
+ const block = parser.current();
+ if (block.type !== 'IfBlock') {
+ parser.error({
+ code: 'invalid-elseif-placement',
+ message: parser.stack.some((block) => block.type === 'IfBlock')
+ ? `Expected to close ${to_string(block)} before seeing {:else if ...} block`
+ : 'Cannot have an {:else if ...} block outside an {#if ...} block',
+ });
+ }
+
+ parser.require_whitespace();
+
+ const expression = read_expression(parser);
+
+ parser.allow_whitespace();
+ parser.eat('}', true);
+
+ block.else = {
+ start: parser.index,
+ end: null,
+ type: 'ElseBlock',
+ children: [
+ {
+ start: parser.index,
+ end: null,
+ type: 'IfBlock',
+ elseif: true,
+ expression,
+ children: [],
+ },
+ ],
+ };
+
+ parser.stack.push(block.else.children[0]);
+ } else {
+ // :else
+ const block = parser.current();
+ if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
+ parser.error({
+ code: 'invalid-else-placement',
+ message: parser.stack.some((block) => block.type === 'IfBlock' || block.type === 'EachBlock')
+ ? `Expected to close ${to_string(block)} before seeing {:else} block`
+ : 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block',
+ });
+ }
+
+ parser.allow_whitespace();
+ parser.eat('}', true);
+
+ block.else = {
+ start: parser.index,
+ end: null,
+ type: 'ElseBlock',
+ children: [],
+ };
+
+ parser.stack.push(block.else);
+ }
+ } else if (parser.match(':then') || parser.match(':catch')) {
+ const block = parser.current();
+ const is_then = parser.eat(':then') || !parser.eat(':catch');
+
+ if (is_then) {
+ if (block.type !== 'PendingBlock') {
+ parser.error({
+ code: 'invalid-then-placement',
+ message: parser.stack.some((block) => block.type === 'PendingBlock')
+ ? `Expected to close ${to_string(block)} before seeing {:then} block`
+ : 'Cannot have an {:then} block outside an {#await ...} block',
+ });
+ }
+ } else {
+ if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
+ parser.error({
+ code: 'invalid-catch-placement',
+ message: parser.stack.some((block) => block.type === 'ThenBlock' || block.type === 'PendingBlock')
+ ? `Expected to close ${to_string(block)} before seeing {:catch} block`
+ : 'Cannot have an {:catch} block outside an {#await ...} block',
+ });
+ }
+ }
+
+ block.end = start;
+ parser.stack.pop();
+ const await_block = parser.current();
+
+ if (!parser.eat('}')) {
+ parser.require_whitespace();
+ await_block[is_then ? 'value' : 'error'] = read_context(parser);
+ parser.allow_whitespace();
+ parser.eat('}', true);
+ }
+
+ const new_block: TemplateNode = {
+ start,
+ // @ts-ignore
+ end: null,
+ type: is_then ? 'ThenBlock' : 'CatchBlock',
+ children: [],
+ skip: false,
+ };
+
+ await_block[is_then ? 'then' : 'catch'] = new_block;
+ parser.stack.push(new_block);
+ } else if (parser.eat('#')) {
+ // {#if foo}, {#each foo} or {#await foo}
+ let type;
+
+ if (parser.eat('if')) {
+ type = 'IfBlock';
+ } else if (parser.eat('each')) {
+ type = 'EachBlock';
+ } else if (parser.eat('await')) {
+ type = 'AwaitBlock';
+ } else if (parser.eat('key')) {
+ type = 'KeyBlock';
+ } else {
+ parser.error({
+ code: 'expected-block-type',
+ message: 'Expected if, each, await or key',
+ });
+ }
+
+ parser.require_whitespace();
+
+ const expression = read_expression(parser);
+
+ // @ts-ignore
+ const block: TemplateNode =
+ type === 'AwaitBlock'
+ ? {
+ start,
+ end: null,
+ type,
+ expression,
+ value: null,
+ error: null,
+ pending: {
+ start: null,
+ end: null,
+ type: 'PendingBlock',
+ children: [],
+ skip: true,
+ },
+ then: {
+ start: null,
+ end: null,
+ type: 'ThenBlock',
+ children: [],
+ skip: true,
+ },
+ catch: {
+ start: null,
+ end: null,
+ type: 'CatchBlock',
+ children: [],
+ skip: true,
+ },
+ }
+ : {
+ start,
+ end: null,
+ type,
+ expression,
+ children: [],
+ };
+
+ parser.allow_whitespace();
+
+ // {#each} blocks must declare a context – {#each list as item}
+ if (type === 'EachBlock') {
+ parser.eat('as', true);
+ parser.require_whitespace();
+
+ block.context = read_context(parser);
+
+ parser.allow_whitespace();
+
+ if (parser.eat(',')) {
+ parser.allow_whitespace();
+ block.index = parser.read_identifier();
+ if (!block.index) {
+ parser.error({
+ code: 'expected-name',
+ message: 'Expected name',
+ });
+ }
+
+ parser.allow_whitespace();
+ }
+
+ if (parser.eat('(')) {
+ parser.allow_whitespace();
+
+ block.key = read_expression(parser);
+ parser.allow_whitespace();
+ parser.eat(')', true);
+ parser.allow_whitespace();
+ }
+ }
+
+ const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
+ if (await_block_shorthand) {
+ parser.require_whitespace();
+ block.value = read_context(parser);
+ parser.allow_whitespace();
+ }
+
+ const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
+ if (await_block_catch_shorthand) {
+ parser.require_whitespace();
+ block.error = read_context(parser);
+ parser.allow_whitespace();
+ }
+
+ parser.eat('}', true);
+
+ // @ts-ignore
+ parser.current().children.push(block);
+ parser.stack.push(block);
+
+ if (type === 'AwaitBlock') {
+ let child_block;
+ if (await_block_shorthand) {
+ block.then.skip = false;
+ child_block = block.then;
+ } else if (await_block_catch_shorthand) {
+ block.catch.skip = false;
+ child_block = block.catch;
+ } else {
+ block.pending.skip = false;
+ child_block = block.pending;
+ }
+
+ child_block.start = parser.index;
+ parser.stack.push(child_block);
+ }
+ } else if (parser.eat('@html')) {
+ // {@html content} tag
+ parser.require_whitespace();
+
+ const expression = read_expression(parser);
+
+ parser.allow_whitespace();
+ parser.eat('}', true);
+
+ // @ts-ignore
+ parser.current().children.push({
+ start,
+ end: parser.index,
+ type: 'RawMustacheTag',
+ expression,
+ });
+ } else if (parser.eat('@debug')) {
+ // let identifiers;
+
+ // // Implies {@debug} which indicates "debug all"
+ // if (parser.read(/\s*}/)) {
+ // identifiers = [];
+ // } else {
+ // const expression = read_expression(parser);
+
+ // identifiers = expression.type === 'SequenceExpression'
+ // ? expression.expressions
+ // : [expression];
+
+ // identifiers.forEach(node => {
+ // if (node.type !== 'Identifier') {
+ // parser.error({
+ // code: 'invalid-debug-args',
+ // message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
+ // }, node.start);
+ // }
+ // });
+
+ // parser.allow_whitespace();
+ // parser.eat('}', true);
+ // }
+
+ // parser.current().children.push({
+ // start,
+ // end: parser.index,
+ // type: 'DebugTag',
+ // identifiers
+ // });
+ throw new Error('@debug not yet supported');
+ } else {
+ const expression = read_expression(parser);
+
+ parser.allow_whitespace();
+ parser.eat('}', true);
+
+ // @ts-ignore
+ parser.current().children.push({
+ start,
+ end: parser.index,
+ type: 'MustacheTag',
+ expression,
+ });
+ }
}