You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte/src/parse/state/mustache.ts

371 lines
8.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import read_context from '../read/context';
import read_expression from '../read/expression';
import { whitespace } from '../../utils/patterns';
import { trim_start, trim_end } from '../../utils/trim';
import { Parser } from '../index';
import { Node } from '../../interfaces';
function trim_whitespace(block: Node, trim_before: boolean, trim_after: boolean) {
if (!block.children || block.children.length === 0) return; // AwaitBlock
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 = trim_start(first_child.data);
if (!first_child.data) block.children.shift();
}
if (last_child.type === 'Text' && trim_after) {
last_child.data = trim_end(last_child.data);
if (!last_child.data) block.children.pop();
}
if (block.else) {
trim_whitespace(block.else, 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} or {/await}
if (parser.eat('/')) {
let block = parser.current();
let expected;
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 {
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: '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: '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.eat(':then')) {
// TODO DRY out this and the next section
const pending_block = parser.current();
if (pending_block.type === 'PendingBlock') {
pending_block.end = start;
parser.stack.pop();
const await_block = parser.current();
if (!parser.eat('}')) {
parser.require_whitespace();
await_block.value = parser.read_identifier();
parser.allow_whitespace();
parser.eat('}', true);
}
const then_block: Node = {
start,
end: null,
type: 'ThenBlock',
children: []
};
await_block.then = then_block;
parser.stack.push(then_block);
}
} else if (parser.eat(':catch')) {
const then_block = parser.current();
if (then_block.type === 'ThenBlock') {
then_block.end = start;
parser.stack.pop();
const await_block = parser.current();
if (!parser.eat('}')) {
parser.require_whitespace();
await_block.error = parser.read_identifier();
parser.allow_whitespace();
parser.eat('}', true);
}
const catch_block: Node = {
start,
end: null,
type: 'CatchBlock',
children: []
};
await_block.catch = catch_block;
parser.stack.push(catch_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 {
parser.error({
code: `expected-block-type`,
message: `Expected if, each or await`
});
}
parser.require_whitespace();
const expression = read_expression(parser);
const block: Node = type === 'AwaitBlock' ?
{
start,
end: null,
type,
expression,
value: null,
error: null,
pending: {
start: null,
end: null,
type: 'PendingBlock',
children: []
},
then: {
start: null,
end: null,
type: 'ThenBlock',
children: []
},
catch: {
start: null,
end: null,
type: 'CatchBlock',
children: []
},
} :
{
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();
}
}
let await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) {
parser.require_whitespace();
block.value = parser.read_identifier();
parser.allow_whitespace();
}
parser.eat('}', true);
parser.current().children.push(block);
parser.stack.push(block);
if (type === 'AwaitBlock') {
const child_block = await_block_shorthand ? block.then : 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);
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
});
} else {
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
parser.current().children.push({
start,
end: parser.index,
type: 'MustacheTag',
expression,
});
}
}