Convert src/compiler/parse/state/mustache.ts to JavaScript

pull/8569/head
Simon Holthausen 3 years ago
parent 2c541f66c1
commit 73f3fbc954

@ -4,422 +4,378 @@ import { closing_tag_omitted } from '../utils/html';
import { regex_whitespace } from '../../utils/patterns'; import { regex_whitespace } from '../../utils/patterns';
import { trim_start, trim_end } from '../../utils/trim'; import { trim_start, trim_end } from '../../utils/trim';
import { to_string } from '../utils/node'; import { to_string } from '../utils/node';
import { Parser } from '../index';
import { TemplateNode } from '../../interfaces';
import parser_errors from '../errors'; import parser_errors from '../errors';
function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after: boolean) { /**
if (!block.children || block.children.length === 0) return; // AwaitBlock * @param {TemplateNode} block
* @param {boolean} trim_before
const first_child = block.children[0]; * @param {boolean} trim_after
const last_child = block.children[block.children.length - 1]; */
function trim_whitespace(block, trim_before, trim_after) {
if (first_child.type === 'Text' && trim_before) { if (!block.children || block.children.length === 0)
first_child.data = trim_start(first_child.data); return; // AwaitBlock
if (!first_child.data) block.children.shift(); const first_child = block.children[0];
} const last_child = block.children[block.children.length - 1];
if (first_child.type === 'Text' && trim_before) {
if (last_child.type === 'Text' && trim_after) { first_child.data = trim_start(first_child.data);
last_child.data = trim_end(last_child.data); if (!first_child.data)
if (!last_child.data) block.children.pop(); block.children.shift();
} }
if (last_child.type === 'Text' && trim_after) {
if (block.else) { last_child.data = trim_end(last_child.data);
trim_whitespace(block.else, trim_before, trim_after); if (!last_child.data)
} block.children.pop();
}
if (first_child.elseif) { if (block.else) {
trim_whitespace(first_child, trim_before, trim_after); trim_whitespace(block.else, trim_before, trim_after);
} }
if (first_child.elseif) {
trim_whitespace(first_child, trim_before, trim_after);
}
} }
const regex_whitespace_with_closing_curly_brace = /^\s*}/; const regex_whitespace_with_closing_curly_brace = /^\s*}/;
export default function mustache(parser: Parser) { /**
const start = parser.index; * @param {Parser} parser
parser.index += 1; */
export default function mustache(parser) {
parser.allow_whitespace(); const start = parser.index;
parser.index += 1;
// {/if}, {/each}, {/await} or {/key} parser.allow_whitespace();
if (parser.eat('/')) { // {/if}, {/each}, {/await} or {/key}
let block = parser.current(); if (parser.eat('/')) {
let expected; let block = parser.current();
let expected;
if (closing_tag_omitted(block.name)) { if (closing_tag_omitted(block.name)) {
block.end = start; block.end = start;
parser.stack.pop(); parser.stack.pop();
block = parser.current(); block = parser.current();
} }
if (block.type === 'ElseBlock' ||
if ( block.type === 'PendingBlock' ||
block.type === 'ElseBlock' || block.type === 'ThenBlock' ||
block.type === 'PendingBlock' || block.type === 'CatchBlock') {
block.type === 'ThenBlock' || block.end = start;
block.type === 'CatchBlock' parser.stack.pop();
) { block = parser.current();
block.end = start; expected = 'await';
parser.stack.pop(); }
block = parser.current(); if (block.type === 'IfBlock') {
expected = 'if';
expected = 'await'; }
} else if (block.type === 'EachBlock') {
expected = 'each';
if (block.type === 'IfBlock') { }
expected = 'if'; else if (block.type === 'AwaitBlock') {
} else if (block.type === 'EachBlock') { expected = 'await';
expected = 'each'; }
} else if (block.type === 'AwaitBlock') { else if (block.type === 'KeyBlock') {
expected = 'await'; expected = 'key';
} else if (block.type === 'KeyBlock') { }
expected = 'key'; else {
} else { parser.error(parser_errors.unexpected_block_close);
parser.error(parser_errors.unexpected_block_close); }
} parser.eat(expected, true);
parser.allow_whitespace();
parser.eat(expected, true); parser.eat('}', true);
parser.allow_whitespace(); while (block.elseif) {
parser.eat('}', true); block.end = parser.index;
parser.stack.pop();
while (block.elseif) { block = parser.current();
block.end = parser.index; if (block.else) {
parser.stack.pop(); block.else.end = start;
block = parser.current(); }
}
if (block.else) { // strip leading/trailing whitespace as necessary
block.else.end = start; const char_before = parser.template[block.start - 1];
} const char_after = parser.template[parser.index];
} const trim_before = !char_before || regex_whitespace.test(char_before);
const trim_after = !char_after || regex_whitespace.test(char_after);
// strip leading/trailing whitespace as necessary trim_whitespace(block, trim_before, trim_after);
const char_before = parser.template[block.start - 1]; block.end = parser.index;
const char_after = parser.template[parser.index]; parser.stack.pop();
const trim_before = !char_before || regex_whitespace.test(char_before); }
const trim_after = !char_after || regex_whitespace.test(char_after); else if (parser.eat(':else')) {
if (parser.eat('if')) {
trim_whitespace(block, trim_before, trim_after); parser.error(parser_errors.invalid_elseif);
}
block.end = parser.index; parser.allow_whitespace();
parser.stack.pop(); // :else if
} else if (parser.eat(':else')) { if (parser.eat('if')) {
if (parser.eat('if')) { const block = parser.current();
parser.error(parser_errors.invalid_elseif); if (block.type !== 'IfBlock') {
} parser.error(parser.stack.some((block) => block.type === 'IfBlock')
? parser_errors.invalid_elseif_placement_unclosed_block(to_string(block))
parser.allow_whitespace(); : parser_errors.invalid_elseif_placement_outside_if);
}
// :else if parser.require_whitespace();
if (parser.eat('if')) { const expression = read_expression(parser);
const block = parser.current(); parser.allow_whitespace();
if (block.type !== 'IfBlock') { parser.eat('}', true);
parser.error( block.else = {
parser.stack.some((block) => block.type === 'IfBlock') start: parser.index,
? parser_errors.invalid_elseif_placement_unclosed_block(to_string(block)) end: null,
: parser_errors.invalid_elseif_placement_outside_if type: 'ElseBlock',
); children: [
} {
start: parser.index,
parser.require_whitespace(); end: null,
type: 'IfBlock',
const expression = read_expression(parser); elseif: true,
expression,
parser.allow_whitespace(); children: []
parser.eat('}', true); }
]
block.else = { };
start: parser.index, parser.stack.push(block.else.children[0]);
end: null, }
type: 'ElseBlock', else {
children: [ // :else
{ const block = parser.current();
start: parser.index, if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
end: null, parser.error(parser.stack.some((block) => block.type === 'IfBlock' || block.type === 'EachBlock')
type: 'IfBlock', ? parser_errors.invalid_else_placement_unclosed_block(to_string(block))
elseif: true, : parser_errors.invalid_else_placement_outside_if);
expression, }
children: [] parser.allow_whitespace();
} parser.eat('}', true);
] block.else = {
}; start: parser.index,
end: null,
parser.stack.push(block.else.children[0]); type: 'ElseBlock',
} else { children: []
// :else };
const block = parser.current(); parser.stack.push(block.else);
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') { }
parser.error( }
parser.stack.some((block) => block.type === 'IfBlock' || block.type === 'EachBlock') else if (parser.match(':then') || parser.match(':catch')) {
? parser_errors.invalid_else_placement_unclosed_block(to_string(block)) const block = parser.current();
: parser_errors.invalid_else_placement_outside_if const is_then = parser.eat(':then') || !parser.eat(':catch');
); if (is_then) {
} if (block.type !== 'PendingBlock') {
parser.error(parser.stack.some((block) => block.type === 'PendingBlock')
parser.allow_whitespace(); ? parser_errors.invalid_then_placement_unclosed_block(to_string(block))
parser.eat('}', true); : parser_errors.invalid_then_placement_without_await);
}
block.else = { }
start: parser.index, else {
end: null, if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
type: 'ElseBlock', parser.error(parser.stack.some((block) => block.type === 'ThenBlock' || block.type === 'PendingBlock')
children: [] ? parser_errors.invalid_catch_placement_unclosed_block(to_string(block))
}; : parser_errors.invalid_catch_placement_without_await);
}
parser.stack.push(block.else); }
} block.end = start;
} else if (parser.match(':then') || parser.match(':catch')) { parser.stack.pop();
const block = parser.current(); const await_block = parser.current();
const is_then = parser.eat(':then') || !parser.eat(':catch'); if (!parser.eat('}')) {
parser.require_whitespace();
if (is_then) { await_block[is_then ? 'value' : 'error'] = read_context(parser);
if (block.type !== 'PendingBlock') { parser.allow_whitespace();
parser.error( parser.eat('}', true);
parser.stack.some((block) => block.type === 'PendingBlock') }
? parser_errors.invalid_then_placement_unclosed_block(to_string(block)) const new_block = {
: parser_errors.invalid_then_placement_without_await start,
); end: null,
} type: is_then ? 'ThenBlock' : 'CatchBlock',
} else { children: [],
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') { skip: false
parser.error( };
parser.stack.some((block) => block.type === 'ThenBlock' || block.type === 'PendingBlock') await_block[is_then ? 'then' : 'catch'] = new_block;
? parser_errors.invalid_catch_placement_unclosed_block(to_string(block)) parser.stack.push(new_block);
: parser_errors.invalid_catch_placement_without_await }
); else if (parser.eat('#')) {
} // {#if foo}, {#each foo} or {#await foo}
} let type;
if (parser.eat('if')) {
block.end = start; type = 'IfBlock';
parser.stack.pop(); }
const await_block = parser.current(); else if (parser.eat('each')) {
type = 'EachBlock';
if (!parser.eat('}')) { }
parser.require_whitespace(); else if (parser.eat('await')) {
await_block[is_then ? 'value' : 'error'] = read_context(parser); type = 'AwaitBlock';
parser.allow_whitespace(); }
parser.eat('}', true); else if (parser.eat('key')) {
} type = 'KeyBlock';
}
const new_block: TemplateNode = { else {
start, parser.error(parser_errors.expected_block_type);
end: null, }
type: is_then ? 'ThenBlock' : 'CatchBlock', parser.require_whitespace();
children: [], const expression = read_expression(parser);
skip: false const block = type === 'AwaitBlock'
}; ? {
start,
await_block[is_then ? 'then' : 'catch'] = new_block; end: null,
parser.stack.push(new_block); type,
} else if (parser.eat('#')) { expression,
// {#if foo}, {#each foo} or {#await foo} value: null,
let type; error: null,
pending: {
if (parser.eat('if')) { start: null,
type = 'IfBlock'; end: null,
} else if (parser.eat('each')) { type: 'PendingBlock',
type = 'EachBlock'; children: [],
} else if (parser.eat('await')) { skip: true
type = 'AwaitBlock'; },
} else if (parser.eat('key')) { then: {
type = 'KeyBlock'; start: null,
} else { end: null,
parser.error(parser_errors.expected_block_type); type: 'ThenBlock',
} children: [],
skip: true
parser.require_whitespace(); },
catch: {
const expression = read_expression(parser); start: null,
end: null,
const block: TemplateNode = type: 'CatchBlock',
type === 'AwaitBlock' children: [],
? { skip: true
start, }
end: null, }
type, : {
expression, start,
value: null, end: null,
error: null, type,
pending: { expression,
start: null, children: []
end: null, };
type: 'PendingBlock', parser.allow_whitespace();
children: [], // {#each} blocks must declare a context {#each list as item}
skip: true if (type === 'EachBlock') {
}, parser.eat('as', true);
then: { parser.require_whitespace();
start: null, block.context = read_context(parser);
end: null, parser.allow_whitespace();
type: 'ThenBlock', if (parser.eat(',')) {
children: [], parser.allow_whitespace();
skip: true block.index = parser.read_identifier();
}, if (!block.index)
catch: { parser.error(parser_errors.expected_name);
start: null, parser.allow_whitespace();
end: null, }
type: 'CatchBlock', if (parser.eat('(')) {
children: [], parser.allow_whitespace();
skip: true block.key = read_expression(parser);
} parser.allow_whitespace();
} parser.eat(')', true);
: { parser.allow_whitespace();
start, }
end: null, }
type, const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
expression, if (await_block_shorthand) {
children: [] if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
}; parser.allow_whitespace();
}
parser.allow_whitespace(); else {
parser.require_whitespace();
// {#each} blocks must declare a context {#each list as item} block.value = read_context(parser);
if (type === 'EachBlock') { parser.allow_whitespace();
parser.eat('as', true); }
parser.require_whitespace(); }
const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
block.context = read_context(parser); if (await_block_catch_shorthand) {
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
parser.allow_whitespace(); parser.allow_whitespace();
}
if (parser.eat(',')) { else {
parser.allow_whitespace(); parser.require_whitespace();
block.index = parser.read_identifier(); block.error = read_context(parser);
if (!block.index) parser.error(parser_errors.expected_name); parser.allow_whitespace();
}
parser.allow_whitespace(); }
} parser.eat('}', true);
parser.current().children.push(block);
if (parser.eat('(')) { parser.stack.push(block);
parser.allow_whitespace(); if (type === 'AwaitBlock') {
let child_block;
block.key = read_expression(parser); if (await_block_shorthand) {
parser.allow_whitespace(); block.then.skip = false;
parser.eat(')', true); child_block = block.then;
parser.allow_whitespace(); }
} else if (await_block_catch_shorthand) {
} block.catch.skip = false;
child_block = block.catch;
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then'); }
if (await_block_shorthand) { else {
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) { block.pending.skip = false;
parser.allow_whitespace(); child_block = block.pending;
} else { }
parser.require_whitespace(); child_block.start = parser.index;
block.value = read_context(parser); parser.stack.push(child_block);
parser.allow_whitespace(); }
} }
} else if (parser.eat('@html')) {
// {@html content} tag
const await_block_catch_shorthand = parser.require_whitespace();
!await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch'); const expression = read_expression(parser);
if (await_block_catch_shorthand) { parser.allow_whitespace();
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) { parser.eat('}', true);
parser.allow_whitespace(); parser.current().children.push({
} else { start,
parser.require_whitespace(); end: parser.index,
block.error = read_context(parser); type: 'RawMustacheTag',
parser.allow_whitespace(); expression
} });
} }
else if (parser.eat('@debug')) {
parser.eat('}', true); let identifiers;
// Implies {@debug} which indicates "debug all"
parser.current().children.push(block); if (parser.read(regex_whitespace_with_closing_curly_brace)) {
parser.stack.push(block); identifiers = [];
}
if (type === 'AwaitBlock') { else {
let child_block; const expression = read_expression(parser);
if (await_block_shorthand) { identifiers =
block.then.skip = false; expression.type === 'SequenceExpression' ? expression.expressions : [expression];
child_block = block.then; identifiers.forEach((node) => {
} else if (await_block_catch_shorthand) { if (node.type !== 'Identifier') {
block.catch.skip = false; parser.error(parser_errors.invalid_debug_args, node.start);
child_block = block.catch; }
} else { });
block.pending.skip = false; parser.allow_whitespace();
child_block = block.pending; parser.eat('}', true);
} }
parser.current().children.push({
child_block.start = parser.index; start,
parser.stack.push(child_block); end: parser.index,
} type: 'DebugTag',
} else if (parser.eat('@html')) { identifiers
// {@html content} tag });
parser.require_whitespace(); }
else if (parser.eat('@const')) {
const expression = read_expression(parser); // {@const a = b}
parser.require_whitespace();
parser.allow_whitespace(); const expression = read_expression(parser);
parser.eat('}', true); if (!(expression.type === 'AssignmentExpression' && expression.operator === '=')) {
parser.error({
parser.current().children.push({ code: 'invalid-const-args',
start, message: '{@const ...} must be an assignment.'
end: parser.index, }, start);
type: 'RawMustacheTag', }
expression parser.allow_whitespace();
}); parser.eat('}', true);
} else if (parser.eat('@debug')) { parser.current().children.push({
let identifiers; start,
end: parser.index,
// Implies {@debug} which indicates "debug all" type: 'ConstTag',
if (parser.read(regex_whitespace_with_closing_curly_brace)) { expression
identifiers = []; });
} else { }
const expression = read_expression(parser); else {
const expression = read_expression(parser);
identifiers = parser.allow_whitespace();
expression.type === 'SequenceExpression' ? expression.expressions : [expression]; parser.eat('}', true);
parser.current().children.push({
identifiers.forEach((node) => { start,
if (node.type !== 'Identifier') { end: parser.index,
parser.error(parser_errors.invalid_debug_args, node.start); type: 'MustacheTag',
} expression
}); });
}
parser.allow_whitespace(); }
parser.eat('}', true);
}
parser.current().children.push({
start,
end: parser.index,
type: 'DebugTag',
identifiers
});
} else if (parser.eat('@const')) {
// {@const a = b}
parser.require_whitespace();
const expression = read_expression(parser);
if (!(expression.type === 'AssignmentExpression' && expression.operator === '=')) {
parser.error(
{
code: 'invalid-const-args',
message: '{@const ...} must be an assignment.'
},
start
);
}
parser.allow_whitespace();
parser.eat('}', true);
parser.current().children.push({
start,
end: parser.index,
type: 'ConstTag',
expression
});
} else {
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
parser.current().children.push({
start,
end: parser.index,
type: 'MustacheTag',
expression
});
}
}

Loading…
Cancel
Save