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

pull/8569/head
Simon Holthausen 3 years ago
parent de53409e21
commit 8ceff7ba8a

@ -1,20 +1,15 @@
import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore'; import { extract_svelte_ignore } from '../../utils/extract_svelte_ignore';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import { is_void } from '../../../shared/utils/names'; import { is_void } from '../../../shared/utils/names';
import parser_errors from '../errors'; import parser_errors from '../errors';
import { Parser } from '../index';
import read_expression from '../read/expression'; import read_expression from '../read/expression';
import read_script from '../read/script'; import read_script from '../read/script';
import read_style from '../read/style'; import read_style from '../read/style';
import { closing_tag_omitted, decode_character_references } from '../utils/html'; import { closing_tag_omitted, decode_character_references } from '../utils/html';
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
/** Invalid attribute characters if the attribute is not surrounded by quotes */ /** Invalid attribute characters if the attribute is not surrounded by quotes */
const regex_starts_with_invalid_attr_value = /^(\/>|[\s"'=<>`])/; const regex_starts_with_invalid_attr_value = /^(\/>|[\s"'=<>`])/;
const meta_tags = new Map([ const meta_tags = new Map([
['svelte:head', 'Head'], ['svelte:head', 'Head'],
['svelte:options', 'Options'], ['svelte:options', 'Options'],
@ -22,14 +17,7 @@ const meta_tags = new Map([
['svelte:document', 'Document'], ['svelte:document', 'Document'],
['svelte:body', 'Body'] ['svelte:body', 'Body']
]); ]);
const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment', 'svelte:element');
const valid_meta_tags = Array.from(meta_tags.keys()).concat(
'svelte:self',
'svelte:component',
'svelte:fragment',
'svelte:element'
);
const specials = new Map([ const specials = new Map([
[ [
'script', 'script',
@ -46,35 +34,34 @@ const specials = new Map([
} }
] ]
]); ]);
const SELF = /^svelte:self(?=[\s/>])/; const SELF = /^svelte:self(?=[\s/>])/;
const COMPONENT = /^svelte:component(?=[\s/>])/; const COMPONENT = /^svelte:component(?=[\s/>])/;
const SLOT = /^svelte:fragment(?=[\s/>])/; const SLOT = /^svelte:fragment(?=[\s/>])/;
const ELEMENT = /^svelte:element(?=[\s/>])/; const ELEMENT = /^svelte:element(?=[\s/>])/;
function parent_is_head(stack) { function parent_is_head(stack) {
let i = stack.length; let i = stack.length;
while (i--) { while (i--) {
const { type } = stack[i]; const { type } = stack[i];
if (type === 'Head') return true; if (type === 'Head')
if (type === 'Element' || type === 'InlineComponent') return false; return true;
if (type === 'Element' || type === 'InlineComponent')
return false;
} }
return false; return false;
} }
const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i; const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
const regex_closing_comment = /-->/; const regex_closing_comment = /-->/;
const regex_capital_letter = /[A-Z]/; const regex_capital_letter = /[A-Z]/;
export default function tag(parser: Parser) { /**
* @param {Parser} parser
*/
export default function tag(parser) {
const start = parser.index++; const start = parser.index++;
let parent = parser.current(); let parent = parser.current();
if (parser.eat('!--')) { if (parser.eat('!--')) {
const data = parser.read_until(regex_closing_comment); const data = parser.read_until(regex_closing_comment);
parser.eat('-->', true, parser_errors.unclosed_comment); parser.eat('-->', true, parser_errors.unclosed_comment);
parser.current().children.push({ parser.current().children.push({
start, start,
end: parser.index, end: parser.index,
@ -82,39 +69,28 @@ export default function tag(parser: Parser) {
data, data,
ignores: extract_svelte_ignore(data) ignores: extract_svelte_ignore(data)
}); });
return; return;
} }
const is_closing_tag = parser.eat('/'); const is_closing_tag = parser.eat('/');
const name = read_tag_name(parser); const name = read_tag_name(parser);
if (meta_tags.has(name)) { if (meta_tags.has(name)) {
const slug = meta_tags.get(name).toLowerCase(); const slug = meta_tags.get(name).toLowerCase();
if (is_closing_tag) { if (is_closing_tag) {
if ( if ((name === 'svelte:window' || name === 'svelte:body') &&
(name === 'svelte:window' || name === 'svelte:body') && parser.current().children.length) {
parser.current().children.length parser.error(parser_errors.invalid_element_content(slug, name), parser.current().children[0].start);
) { }
parser.error( }
parser_errors.invalid_element_content(slug, name), else {
parser.current().children[0].start
);
}
} else {
if (name in parser.meta_tags) { if (name in parser.meta_tags) {
parser.error(parser_errors.duplicate_element(slug, name), start); parser.error(parser_errors.duplicate_element(slug, name), start);
} }
if (parser.stack.length > 1) { if (parser.stack.length > 1) {
parser.error(parser_errors.invalid_element_placement(slug, name), start); parser.error(parser_errors.invalid_element_placement(slug, name), start);
} }
parser.meta_tags[name] = true; parser.meta_tags[name] = true;
} }
} }
const type = meta_tags.has(name) const type = meta_tags.has(name)
? meta_tags.get(name) ? meta_tags.get(name)
: regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component' : regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component'
@ -127,49 +103,43 @@ export default function tag(parser: Parser) {
? 'Slot' ? 'Slot'
: 'Element'; : 'Element';
const element: TemplateNode = { /**
* @type {TemplateNode}
*/
const element = {
start, start,
end: null, // filled in later end: null,
type, type,
name, name,
attributes: [], attributes: [],
children: [] children: []
}; };
parser.allow_whitespace(); parser.allow_whitespace();
if (is_closing_tag) { if (is_closing_tag) {
if (is_void(name)) { if (is_void(name)) {
parser.error(parser_errors.invalid_void_content(name), start); parser.error(parser_errors.invalid_void_content(name), start);
} }
parser.eat('>', true); parser.eat('>', true);
// close any elements that don't have their own closing tags, e.g. <div><p></div> // close any elements that don't have their own closing tags, e.g. <div><p></div>
while (parent.name !== name) { while (parent.name !== name) {
if (parent.type !== 'Element') { if (parent.type !== 'Element') {
const error = const error = parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name
parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name
? parser_errors.invalid_closing_tag_autoclosed(name, parser.last_auto_closed_tag.reason) ? parser_errors.invalid_closing_tag_autoclosed(name, parser.last_auto_closed_tag.reason)
: parser_errors.invalid_closing_tag_unopened(name); : parser_errors.invalid_closing_tag_unopened(name);
parser.error(error, start); parser.error(error, start);
} }
parent.end = start; parent.end = start;
parser.stack.pop(); parser.stack.pop();
parent = parser.current(); parent = parser.current();
} }
parent.end = parser.index; parent.end = parser.index;
parser.stack.pop(); parser.stack.pop();
if (parser.last_auto_closed_tag && parser.stack.length < parser.last_auto_closed_tag.depth) { if (parser.last_auto_closed_tag && parser.stack.length < parser.last_auto_closed_tag.depth) {
parser.last_auto_closed_tag = null; parser.last_auto_closed_tag = null;
} }
return; return;
} else if (closing_tag_omitted(parent.name, name)) { }
else if (closing_tag_omitted(parent.name, name)) {
parent.end = start; parent.end = start;
parser.stack.pop(); parser.stack.pop();
parser.last_auto_closed_tag = { parser.last_auto_closed_tag = {
@ -179,78 +149,62 @@ export default function tag(parser: Parser) {
}; };
} }
const unique_names: Set<string> = new Set(); /**
* @type {Set<string>}
*/
const unique_names = new Set();
let attribute; let attribute;
while ((attribute = read_attribute(parser, unique_names))) { while ((attribute = read_attribute(parser, unique_names))) {
element.attributes.push(attribute); element.attributes.push(attribute);
parser.allow_whitespace(); parser.allow_whitespace();
} }
if (name === 'svelte:component') { if (name === 'svelte:component') {
const index = element.attributes.findIndex( const index = element.attributes.findIndex((attr) => attr.type === 'Attribute' && attr.name === 'this');
(attr) => attr.type === 'Attribute' && attr.name === 'this'
);
if (index === -1) { if (index === -1) {
parser.error(parser_errors.missing_component_definition, start); parser.error(parser_errors.missing_component_definition, start);
} }
const definition = element.attributes.splice(index, 1)[0]; const definition = element.attributes.splice(index, 1)[0];
if ( if (definition.value === true ||
definition.value === true ||
definition.value.length !== 1 || definition.value.length !== 1 ||
definition.value[0].type === 'Text' definition.value[0].type === 'Text') {
) {
parser.error(parser_errors.invalid_component_definition, definition.start); parser.error(parser_errors.invalid_component_definition, definition.start);
} }
element.expression = definition.value[0].expression; element.expression = definition.value[0].expression;
} }
if (name === 'svelte:element') { if (name === 'svelte:element') {
const index = element.attributes.findIndex( const index = element.attributes.findIndex((attr) => attr.type === 'Attribute' && attr.name === 'this');
(attr) => attr.type === 'Attribute' && attr.name === 'this'
);
if (index === -1) { if (index === -1) {
parser.error(parser_errors.missing_element_definition, start); parser.error(parser_errors.missing_element_definition, start);
} }
const definition = element.attributes.splice(index, 1)[0]; const definition = element.attributes.splice(index, 1)[0];
if (definition.value === true) { if (definition.value === true) {
parser.error(parser_errors.invalid_element_definition, definition.start); parser.error(parser_errors.invalid_element_definition, definition.start);
} }
element.tag = definition.value[0].data || definition.value[0].expression; element.tag = definition.value[0].data || definition.value[0].expression;
} }
// special cases top-level <script> and <style> // special cases top-level <script> and <style>
if (specials.has(name) && parser.stack.length === 1) { if (specials.has(name) && parser.stack.length === 1) {
const special = specials.get(name); const special = specials.get(name);
parser.eat('>', true); parser.eat('>', true);
const content = special.read(parser, start, element.attributes); const content = special.read(parser, start, element.attributes);
if (content) parser[special.property].push(content); if (content)
parser[special.property].push(content);
return; return;
} }
parser.current().children.push(element); parser.current().children.push(element);
const self_closing = parser.eat('/') || is_void(name); const self_closing = parser.eat('/') || is_void(name);
parser.eat('>', true); parser.eat('>', true);
if (self_closing) { if (self_closing) {
// don't push self-closing elements onto the stack // don't push self-closing elements onto the stack
element.end = parser.index; element.end = parser.index;
} else if (name === 'textarea') { }
else if (name === 'textarea') {
// special case // special case
element.children = read_sequence( element.children = read_sequence(parser, () => regex_closing_textarea_tag.test(parser.template.slice(parser.index)), 'inside <textarea>');
parser,
() => regex_closing_textarea_tag.test(parser.template.slice(parser.index)),
'inside <textarea>'
);
parser.read(regex_closing_textarea_tag); parser.read(regex_closing_textarea_tag);
element.end = parser.index; element.end = parser.index;
} else if (name === 'script' || name === 'style') { }
else if (name === 'script' || name === 'style') {
// special case // special case
const start = parser.index; const start = parser.index;
const data = parser.read_until(new RegExp(`</${name}>`)); const data = parser.read_until(new RegExp(`</${name}>`));
@ -258,105 +212,97 @@ export default function tag(parser: Parser) {
element.children.push({ start, end, type: 'Text', data }); element.children.push({ start, end, type: 'Text', data });
parser.eat(`</${name}>`, true); parser.eat(`</${name}>`, true);
element.end = parser.index; element.end = parser.index;
} else { }
else {
parser.stack.push(element); parser.stack.push(element);
} }
} }
const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/; const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
function read_tag_name(parser: Parser) { /**
* @param {Parser} parser
*/
function read_tag_name(parser) {
const start = parser.index; const start = parser.index;
if (parser.read(SELF)) { if (parser.read(SELF)) {
// check we're inside a block, otherwise this // check we're inside a block, otherwise this
// will cause infinite recursion // will cause infinite recursion
let i = parser.stack.length; let i = parser.stack.length;
let legal = false; let legal = false;
while (i--) { while (i--) {
const fragment = parser.stack[i]; const fragment = parser.stack[i];
if ( if (fragment.type === 'IfBlock' ||
fragment.type === 'IfBlock' ||
fragment.type === 'EachBlock' || fragment.type === 'EachBlock' ||
fragment.type === 'InlineComponent' fragment.type === 'InlineComponent') {
) {
legal = true; legal = true;
break; break;
} }
} }
if (!legal) { if (!legal) {
parser.error(parser_errors.invalid_self_placement, start); parser.error(parser_errors.invalid_self_placement, start);
} }
return 'svelte:self'; return 'svelte:self';
} }
if (parser.read(COMPONENT))
if (parser.read(COMPONENT)) return 'svelte:component'; return 'svelte:component';
if (parser.read(ELEMENT)) return 'svelte:element'; if (parser.read(ELEMENT))
return 'svelte:element';
if (parser.read(SLOT)) return 'svelte:fragment'; if (parser.read(SLOT))
return 'svelte:fragment';
const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag); const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
if (meta_tags.has(name))
if (meta_tags.has(name)) return name; return name;
if (name.startsWith('svelte:')) { if (name.startsWith('svelte:')) {
const match = fuzzymatch(name.slice(7), valid_meta_tags); const match = fuzzymatch(name.slice(7), valid_meta_tags);
parser.error(parser_errors.invalid_tag_name_svelte_element(valid_meta_tags, match), start); parser.error(parser_errors.invalid_tag_name_svelte_element(valid_meta_tags, match), start);
} }
if (!valid_tag_name.test(name)) { if (!valid_tag_name.test(name)) {
parser.error(parser_errors.invalid_tag_name, start); parser.error(parser_errors.invalid_tag_name, start);
} }
return name; return name;
} }
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const regex_token_ending_character = /[\s=\/>"']/; const regex_token_ending_character = /[\s=\/>"']/;
const regex_starts_with_quote_characters = /^["']/; const regex_starts_with_quote_characters = /^["']/;
function read_attribute(parser: Parser, unique_names: Set<string>) { /**
* @param {Parser} parser
* @param {Set<string>} unique_names
*/
function read_attribute(parser, unique_names) {
const start = parser.index; const start = parser.index;
function check_unique(name: string) { /**
* @param {string} name
*/
function check_unique(name) {
if (unique_names.has(name)) { if (unique_names.has(name)) {
parser.error(parser_errors.duplicate_attribute, start); parser.error(parser_errors.duplicate_attribute, start);
} }
unique_names.add(name); unique_names.add(name);
} }
if (parser.eat('{')) { if (parser.eat('{')) {
parser.allow_whitespace(); parser.allow_whitespace();
if (parser.eat('...')) { if (parser.eat('...')) {
const expression = read_expression(parser); const expression = read_expression(parser);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
return { return {
start, start,
end: parser.index, end: parser.index,
type: 'Spread', type: 'Spread',
expression expression
}; };
} else { }
else {
const value_start = parser.index; const value_start = parser.index;
const name = parser.read_identifier(); const name = parser.read_identifier();
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
if (name === null) { if (name === null) {
parser.error(parser_errors.empty_attribute_shorthand, start); parser.error(parser_errors.empty_attribute_shorthand, start);
} }
check_unique(name); check_unique(name);
return { return {
start, start,
end: parser.index, end: parser.index,
@ -378,43 +324,40 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}; };
} }
} }
const name = parser.read_until(regex_token_ending_character); const name = parser.read_until(regex_token_ending_character);
if (!name) return null; if (!name)
return null;
let end = parser.index; let end = parser.index;
parser.allow_whitespace(); parser.allow_whitespace();
const colon_index = name.indexOf(':'); const colon_index = name.indexOf(':');
const type = colon_index !== -1 && get_directive_type(name.slice(0, colon_index)); const type = colon_index !== -1 && get_directive_type(name.slice(0, colon_index));
let value: any[] | true = true; /**
* @type {any[] | true}
*/
let value = true;
if (parser.eat('=')) { if (parser.eat('=')) {
parser.allow_whitespace(); parser.allow_whitespace();
value = read_attribute_value(parser); value = read_attribute_value(parser);
end = parser.index; end = parser.index;
} else if (parser.match_regex(regex_starts_with_quote_characters)) { }
else if (parser.match_regex(regex_starts_with_quote_characters)) {
parser.error(parser_errors.unexpected_token('='), parser.index); parser.error(parser_errors.unexpected_token('='), parser.index);
} }
if (type) { if (type) {
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|'); const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
if (directive_name === '') { if (directive_name === '') {
parser.error(parser_errors.empty_directive_name(type), start + colon_index + 1); parser.error(parser_errors.empty_directive_name(type), start + colon_index + 1);
} }
if (type === 'Binding' && directive_name !== 'this') { if (type === 'Binding' && directive_name !== 'this') {
check_unique(directive_name); check_unique(directive_name);
} else if (type !== 'EventHandler' && type !== 'Action') { }
else if (type !== 'EventHandler' && type !== 'Action') {
check_unique(name); check_unique(name);
} }
if (type === 'Ref') { if (type === 'Ref') {
parser.error(parser_errors.invalid_ref_directive(directive_name), start); parser.error(parser_errors.invalid_ref_directive(directive_name), start);
} }
if (type === 'StyleDirective') { if (type === 'StyleDirective') {
return { return {
start, start,
@ -425,20 +368,18 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
value value
}; };
} }
const first_value = value[0]; const first_value = value[0];
let expression = null; let expression = null;
if (first_value) { if (first_value) {
const attribute_contains_text = (value as any[]).length > 1 || first_value.type === 'Text'; const attribute_contains_text = value.length > 1 || first_value.type === 'Text';
if (attribute_contains_text) { if (attribute_contains_text) {
parser.error(parser_errors.invalid_directive_value, first_value.start); parser.error(parser_errors.invalid_directive_value, first_value.start);
} else { }
else {
expression = first_value.expression; expression = first_value.expression;
} }
} }
const directive = {
const directive: Directive = {
start, start,
end, end,
type, type,
@ -446,13 +387,11 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
modifiers, modifiers,
expression expression
}; };
if (type === 'Transition') { if (type === 'Transition') {
const direction = name.slice(0, colon_index); const direction = name.slice(0, colon_index);
directive.intro = direction === 'in' || direction === 'transition'; directive.intro = direction === 'in' || direction === 'transition';
directive.outro = direction === 'out' || direction === 'transition'; directive.outro = direction === 'out' || direction === 'transition';
} }
// Directive name is expression, e.g. <p class:isRed /> // Directive name is expression, e.g. <p class:isRed />
if (!directive.expression && (type === 'Binding' || type === 'Class')) { if (!directive.expression && (type === 'Binding' || type === 'Class')) {
directive.expression = { directive.expression = {
@ -460,14 +399,11 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
end: directive.end, end: directive.end,
type: 'Identifier', type: 'Identifier',
name: directive.name name: directive.name
} as any; };
} }
return directive; return directive;
} }
check_unique(name); check_unique(name);
return { return {
start, start,
end, end,
@ -477,19 +413,35 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}; };
} }
function get_directive_type(name: string): DirectiveType { /**
if (name === 'use') return 'Action'; * @param {string} name
if (name === 'animate') return 'Animation'; * @returns {DirectiveType}
if (name === 'bind') return 'Binding'; */
if (name === 'class') return 'Class'; function get_directive_type(name) {
if (name === 'style') return 'StyleDirective'; if (name === 'use')
if (name === 'on') return 'EventHandler'; return 'Action';
if (name === 'let') return 'Let'; if (name === 'animate')
if (name === 'ref') return 'Ref'; return 'Animation';
if (name === 'in' || name === 'out' || name === 'transition') return 'Transition'; if (name === 'bind')
} return 'Binding';
if (name === 'class')
function read_attribute_value(parser: Parser) { return 'Class';
if (name === 'style')
return 'StyleDirective';
if (name === 'on')
return 'EventHandler';
if (name === 'let')
return 'Let';
if (name === 'ref')
return 'Ref';
if (name === 'in' || name === 'out' || name === 'transition')
return 'Transition';
}
/**
* @param {Parser} parser
*/
function read_attribute_value(parser) {
const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null; const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
if (quote_mark && parser.eat(quote_mark)) { if (quote_mark && parser.eat(quote_mark)) {
return [ return [
@ -502,19 +454,16 @@ function read_attribute_value(parser: Parser) {
} }
]; ];
} }
let value; let value;
try { try {
value = read_sequence( value = read_sequence(parser, () => {
parser,
() => {
// handle common case of quote marks existing outside of regex for performance reasons // handle common case of quote marks existing outside of regex for performance reasons
if (quote_mark) return parser.match(quote_mark); if (quote_mark)
return parser.match(quote_mark);
return !!parser.match_regex(regex_starts_with_invalid_attr_value); return !!parser.match_regex(regex_starts_with_invalid_attr_value);
}, }, 'in attribute value');
'in attribute value' }
); catch (error) {
} catch (error) {
if (error.code === 'parse-error') { if (error.code === 'parse-error') {
// if the attribute value didn't close + self-closing tag // if the attribute value didn't close + self-closing tag
// eg: `<Component test={{a:1} />` // eg: `<Component test={{a:1} />`
@ -526,17 +475,26 @@ function read_attribute_value(parser: Parser) {
} }
throw error; throw error;
} }
if (value.length === 0 && !quote_mark) { if (value.length === 0 && !quote_mark) {
parser.error(parser_errors.missing_attribute_value); parser.error(parser_errors.missing_attribute_value);
} }
if (quote_mark)
if (quote_mark) parser.index += 1; parser.index += 1;
return value; return value;
} }
function read_sequence(parser: Parser, done: () => boolean, location: string): TemplateNode[] { /**
let current_chunk: Text = { * @param {Parser} parser
* @param {() => boolean} done
* @param {string} location
* @returns {TemplateNode[]}
*/
function read_sequence(parser, done, location) {
/**
* @type {Text}
*/
let current_chunk = {
start: parser.index, start: parser.index,
end: null, end: null,
type: 'Text', type: 'Text',
@ -544,49 +502,51 @@ function read_sequence(parser: Parser, done: () => boolean, location: string): T
data: null data: null
}; };
const chunks: TemplateNode[] = []; /**
* @type {TemplateNode[]}
*/
const chunks = [];
function flush(end: number) { /**
* @param {number} end
*/
function flush(end) {
if (current_chunk.raw) { if (current_chunk.raw) {
current_chunk.data = decode_character_references(current_chunk.raw, true); current_chunk.data = decode_character_references(current_chunk.raw, true);
current_chunk.end = end; current_chunk.end = end;
chunks.push(current_chunk); chunks.push(current_chunk);
} }
} }
while (parser.index < parser.template.length) { while (parser.index < parser.template.length) {
const index = parser.index; const index = parser.index;
if (done()) { if (done()) {
flush(parser.index); flush(parser.index);
return chunks; return chunks;
} else if (parser.eat('{')) { }
else if (parser.eat('{')) {
if (parser.match('#')) { if (parser.match('#')) {
const index = parser.index - 1; const index = parser.index - 1;
parser.eat('#'); parser.eat('#');
const name = parser.read_until(/[^a-z]/); const name = parser.read_until(/[^a-z]/);
parser.error(parser_errors.invalid_logic_block_placement(location, name), index); parser.error(parser_errors.invalid_logic_block_placement(location, name), index);
} else if (parser.match('@')) { }
else if (parser.match('@')) {
const index = parser.index - 1; const index = parser.index - 1;
parser.eat('@'); parser.eat('@');
const name = parser.read_until(/[^a-z]/); const name = parser.read_until(/[^a-z]/);
parser.error(parser_errors.invalid_tag_placement(location, name), index); parser.error(parser_errors.invalid_tag_placement(location, name), index);
} }
flush(parser.index - 1); flush(parser.index - 1);
parser.allow_whitespace(); parser.allow_whitespace();
const expression = read_expression(parser); const expression = read_expression(parser);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
chunks.push({ chunks.push({
start: index, start: index,
end: parser.index, end: parser.index,
type: 'MustacheTag', type: 'MustacheTag',
expression expression
}); });
current_chunk = { current_chunk = {
start: parser.index, start: parser.index,
end: null, end: null,
@ -594,10 +554,14 @@ function read_sequence(parser: Parser, done: () => boolean, location: string): T
raw: '', raw: '',
data: null data: null
}; };
} else { }
else {
current_chunk.raw += parser.template[parser.index++]; current_chunk.raw += parser.template[parser.index++];
} }
} }
parser.error(parser_errors.unexpected_eof); parser.error(parser_errors.unexpected_eof);
} }

Loading…
Cancel
Save