more tidying up

pull/2252/head
Richard Harris 7 years ago
parent f21c3b7136
commit 63075603f3

@ -4,7 +4,26 @@ import Block from '../render-dom/Block';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren'; import mapChildren from './shared/mapChildren';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import unpackDestructuring from '../../utils/unpackDestructuring'; import { Node as INode } from '../../interfaces';
function unpack_destructuring(contexts: Array<{ name: string, tail: string }>, node: INode, tail: string) {
if (!node) return;
if (node.type === 'Identifier') {
contexts.push({
key: node,
tail
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
unpack_destructuring(contexts, element, `${tail}[${i}]`);
});
} else if (node.type === 'ObjectPattern') {
node.properties.forEach((property) => {
unpack_destructuring(contexts, property.value, `${tail}.${property.key.name}`);
});
}
}
export default class EachBlock extends Node { export default class EachBlock extends Node {
type: 'EachBlock'; type: 'EachBlock';
@ -36,7 +55,7 @@ export default class EachBlock extends Node {
this.scope = scope.child(); this.scope = scope.child();
this.contexts = []; this.contexts = [];
unpackDestructuring(this.contexts, info.context, ''); unpack_destructuring(this.contexts, info.context, '');
this.contexts.forEach(context => { this.contexts.forEach(context => {
this.scope.add(context.key.name, this.expression.dependencies, this); this.scope.add(context.key.name, this.expression.dependencies, this);

@ -14,7 +14,7 @@ import Text from './Text';
import Title from './Title'; import Title from './Title';
import Window from './Window'; import Window from './Window';
import Node from '../../nodes/shared/Node'; import Node from '../../nodes/shared/Node';
import { trimStart, trimEnd } from '../../../utils/trim'; import { trim_start, trim_end } from '../../../utils/trim';
import TextWrapper from './Text'; import TextWrapper from './Text';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
@ -89,7 +89,7 @@ export default class FragmentWrapper {
); );
if (shouldTrim) { if (shouldTrim) {
data = trimEnd(data); data = trim_end(data);
if (!data) continue; if (!data) continue;
} }
} }
@ -121,7 +121,7 @@ export default class FragmentWrapper {
const first = this.nodes[0] as TextWrapper; const first = this.nodes[0] as TextWrapper;
if (first && first.node.type === 'Text') { if (first && first.node.type === 'Text') {
first.data = trimStart(first.data); first.data = trim_start(first.data);
if (!first.data) { if (!first.data) {
first.var = null; first.var = null;
this.nodes.shift(); this.nodes.shift();

@ -15,7 +15,7 @@ export interface Parser {
html: Node; html: Node;
css: Node; css: Node;
js: Node; js: Node;
metaTags: {}; meta_tags: {};
} }
export interface Ast { export interface Ast {

@ -9,7 +9,7 @@ export const parse = (source: string) => Parser.parse(source, {
preserveParens: true preserveParens: true
}); });
export const parseExpressionAt = (source: string, index: number) => Parser.parseExpressionAt(source, index, { export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, {
ecmaVersion: 9, ecmaVersion: 9,
preserveParens: true preserveParens: true
}); });

@ -8,7 +8,6 @@ import error from '../utils/error';
interface ParserOptions { interface ParserOptions {
filename?: string; filename?: string;
bind?: boolean;
customElement?: boolean; customElement?: boolean;
} }
@ -25,9 +24,7 @@ export class Parser {
html: Node; html: Node;
css: Node[] = []; css: Node[] = [];
js: Node[] = []; js: Node[] = [];
metaTags = {}; meta_tags = {};
allowBindings: boolean;
constructor(template: string, options: ParserOptions) { constructor(template: string, options: ParserOptions) {
if (typeof template !== 'string') { if (typeof template !== 'string') {
@ -38,8 +35,6 @@ export class Parser {
this.filename = options.filename; this.filename = options.filename;
this.customElement = options.customElement; this.customElement = options.customElement;
this.allowBindings = options.bind !== false;
this.html = { this.html = {
start: null, start: null,
end: null, end: null,
@ -92,7 +87,7 @@ export class Parser {
return this.stack[this.stack.length - 1]; return this.stack[this.stack.length - 1];
} }
acornError(err: any) { acorn_error(err: any) {
this.error({ this.error({
code: `parse-error`, code: `parse-error`,
message: err.message.replace(/ \(\d+:\d+\)$/, '') message: err.message.replace(/ \(\d+:\d+\)$/, '')
@ -129,14 +124,14 @@ export class Parser {
return this.template.slice(this.index, this.index + str.length) === str; return this.template.slice(this.index, this.index + str.length) === str;
} }
matchRegex(pattern: RegExp) { match_regex(pattern: RegExp) {
const match = pattern.exec(this.template.slice(this.index)); const match = pattern.exec(this.template.slice(this.index));
if (!match || match.index !== 0) return null; if (!match || match.index !== 0) return null;
return match[0]; return match[0];
} }
allowWhitespace() { allow_whitespace() {
while ( while (
this.index < this.template.length && this.index < this.template.length &&
whitespace.test(this.template[this.index]) whitespace.test(this.template[this.index])
@ -146,12 +141,12 @@ export class Parser {
} }
read(pattern: RegExp) { read(pattern: RegExp) {
const result = this.matchRegex(pattern); const result = this.match_regex(pattern);
if (result) this.index += result.length; if (result) this.index += result.length;
return result; return result;
} }
readIdentifier() { read_identifier() {
const start = this.index; const start = this.index;
let i = this.index; let i = this.index;
@ -180,7 +175,7 @@ export class Parser {
return identifier; return identifier;
} }
readUntil(pattern: RegExp) { read_until(pattern: RegExp) {
if (this.index >= this.template.length) if (this.index >= this.template.length)
this.error({ this.error({
code: `unexpected-eof`, code: `unexpected-eof`,
@ -199,7 +194,7 @@ export class Parser {
return this.template.slice(start); return this.template.slice(start);
} }
requireWhitespace() { require_whitespace() {
if (!whitespace.test(this.template[this.index])) { if (!whitespace.test(this.template[this.index])) {
this.error({ this.error({
code: `missing-whitespace`, code: `missing-whitespace`,
@ -207,7 +202,7 @@ export class Parser {
}); });
} }
this.allowWhitespace(); this.allow_whitespace();
} }
} }

@ -26,7 +26,7 @@ type Context = {
properties?: Property[]; properties?: Property[];
} }
function errorOnAssignmentPattern(parser: Parser) { function error_on_assignment_pattern(parser: Parser) {
if (parser.eat('=')) { if (parser.eat('=')) {
parser.error({ parser.error({
code: 'invalid-assignment-pattern', code: 'invalid-assignment-pattern',
@ -35,7 +35,7 @@ function errorOnAssignmentPattern(parser: Parser) {
} }
} }
export default function readContext(parser: Parser) { export default function read_context(parser: Parser) {
const context: Context = { const context: Context = {
start: parser.index, start: parser.index,
end: null, end: null,
@ -47,17 +47,17 @@ export default function readContext(parser: Parser) {
context.elements = []; context.elements = [];
do { do {
parser.allowWhitespace(); parser.allow_whitespace();
if (parser.template[parser.index] === ',') { if (parser.template[parser.index] === ',') {
context.elements.push(null); context.elements.push(null);
} else { } else {
context.elements.push(readContext(parser)); context.elements.push(read_context(parser));
parser.allowWhitespace(); parser.allow_whitespace();
} }
} while (parser.eat(',')); } while (parser.eat(','));
errorOnAssignmentPattern(parser); error_on_assignment_pattern(parser);
parser.eat(']', true); parser.eat(']', true);
context.end = parser.index; context.end = parser.index;
} }
@ -67,20 +67,20 @@ export default function readContext(parser: Parser) {
context.properties = []; context.properties = [];
do { do {
parser.allowWhitespace(); parser.allow_whitespace();
const start = parser.index; const start = parser.index;
const name = parser.readIdentifier(); const name = parser.read_identifier();
const key: Identifier = { const key: Identifier = {
start, start,
end: parser.index, end: parser.index,
type: 'Identifier', type: 'Identifier',
name name
}; };
parser.allowWhitespace(); parser.allow_whitespace();
const value = parser.eat(':') const value = parser.eat(':')
? (parser.allowWhitespace(), readContext(parser)) ? (parser.allow_whitespace(), read_context(parser))
: key; : key;
const property: Property = { const property: Property = {
@ -95,16 +95,16 @@ export default function readContext(parser: Parser) {
context.properties.push(property); context.properties.push(property);
parser.allowWhitespace(); parser.allow_whitespace();
} while (parser.eat(',')); } while (parser.eat(','));
errorOnAssignmentPattern(parser); error_on_assignment_pattern(parser);
parser.eat('}', true); parser.eat('}', true);
context.end = parser.index; context.end = parser.index;
} }
else { else {
const name = parser.readIdentifier(); const name = parser.read_identifier();
if (name) { if (name) {
context.type = 'Identifier'; context.type = 'Identifier';
context.end = parser.index; context.end = parser.index;
@ -118,7 +118,7 @@ export default function readContext(parser: Parser) {
}); });
} }
errorOnAssignmentPattern(parser); error_on_assignment_pattern(parser);
} }
return context; return context;

@ -1,12 +1,12 @@
import { parseExpressionAt } from '../acorn'; import { parse_expression_at } from '../acorn';
import { Parser } from '../index'; import { Parser } from '../index';
const literals = new Map([['true', true], ['false', false], ['null', null]]); const literals = new Map([['true', true], ['false', false], ['null', null]]);
export default function readExpression(parser: Parser) { export default function read_expression(parser: Parser) {
const start = parser.index; const start = parser.index;
const name = parser.readUntil(/\s*}/); const name = parser.read_until(/\s*}/);
if (name && /^[a-z]+$/.test(name)) { if (name && /^[a-z]+$/.test(name)) {
const end = start + name.length; const end = start + name.length;
@ -31,11 +31,11 @@ export default function readExpression(parser: Parser) {
parser.index = start; parser.index = start;
try { try {
const node = parseExpressionAt(parser.template, parser.index); const node = parse_expression_at(parser.template, parser.index);
parser.index = node.end; parser.index = node.end;
return node; return node;
} catch (err) { } catch (err) {
parser.acornError(err); parser.acorn_error(err);
} }
} }

@ -3,7 +3,7 @@ import repeat from '../../utils/repeat';
import { Parser } from '../index'; import { Parser } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
const scriptClosingTag = '</script>'; const script_closing_tag = '</script>';
function get_context(parser: Parser, attributes: Node[], start: number) { function get_context(parser: Parser, attributes: Node[], start: number) {
const context = attributes.find(attribute => attribute.name === 'context'); const context = attributes.find(attribute => attribute.name === 'context');
@ -28,28 +28,28 @@ function get_context(parser: Parser, attributes: Node[], start: number) {
return value; return value;
} }
export default function readScript(parser: Parser, start: number, attributes: Node[]) { export default function read_script(parser: Parser, start: number, attributes: Node[]) {
const scriptStart = parser.index; const script_start = parser.index;
const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart); const script_end = parser.template.indexOf(script_closing_tag, script_start);
if (scriptEnd === -1) parser.error({ if (script_end === -1) parser.error({
code: `unclosed-script`, code: `unclosed-script`,
message: `<script> must have a closing tag` message: `<script> must have a closing tag`
}); });
const source = const source =
repeat(' ', scriptStart) + parser.template.slice(scriptStart, scriptEnd); repeat(' ', script_start) + parser.template.slice(script_start, script_end);
parser.index = scriptEnd + scriptClosingTag.length; parser.index = script_end + script_closing_tag.length;
let ast; let ast;
try { try {
ast = acorn.parse(source); ast = acorn.parse(source);
} catch (err) { } catch (err) {
parser.acornError(err); parser.acorn_error(err);
} }
ast.start = scriptStart; ast.start = script_start;
return { return {
start, start,
end: parser.index, end: parser.index,

@ -3,17 +3,17 @@ import { walk } from 'estree-walker';
import { Parser } from '../index'; import { Parser } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
export default function readStyle(parser: Parser, start: number, attributes: Node[]) { export default function read_style(parser: Parser, start: number, attributes: Node[]) {
const contentStart = parser.index; const content_start = parser.index;
const styles = parser.readUntil(/<\/style>/); const styles = parser.read_until(/<\/style>/);
const contentEnd = parser.index; const content_end = parser.index;
let ast; let ast;
try { try {
ast = parse(styles, { ast = parse(styles, {
positions: true, positions: true,
offset: contentStart, offset: content_start,
}); });
} catch (err) { } catch (err) {
if (err.name === 'CssSyntaxError') { if (err.name === 'CssSyntaxError') {
@ -37,7 +37,7 @@ export default function readStyle(parser: Parser, start: number, attributes: Nod
const a = node.children[i]; const a = node.children[i];
const b = node.children[i + 1]; const b = node.children[i + 1];
if (isRefSelector(a, b)) { if (is_ref_selector(a, b)) {
parser.error({ parser.error({
code: `invalid-ref-selector`, code: `invalid-ref-selector`,
message: 'ref selectors are no longer supported' message: 'ref selectors are no longer supported'
@ -63,14 +63,14 @@ export default function readStyle(parser: Parser, start: number, attributes: Nod
attributes, attributes,
children: ast.children, children: ast.children,
content: { content: {
start: contentStart, start: content_start,
end: contentEnd, end: content_end,
styles, styles,
}, },
}; };
} }
function isRefSelector(a: Node, b: Node) { function is_ref_selector(a: Node, b: Node) {
if (!b) return false; if (!b) return false;
return ( return (

@ -1,32 +1,32 @@
import readContext from '../read/context'; import read_context from '../read/context';
import readExpression from '../read/expression'; import read_expression from '../read/expression';
import { whitespace } from '../../utils/patterns'; import { whitespace } from '../../utils/patterns';
import { trimStart, trimEnd } from '../../utils/trim'; import { trim_start, trim_end } from '../../utils/trim';
import { Parser } from '../index'; import { Parser } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
function trimWhitespace(block: Node, trimBefore: boolean, trimAfter: boolean) { function trim_whitespace(block: Node, 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 firstChild = block.children[0]; const firstChild = block.children[0];
const lastChild = block.children[block.children.length - 1]; const lastChild = block.children[block.children.length - 1];
if (firstChild.type === 'Text' && trimBefore) { if (firstChild.type === 'Text' && trim_before) {
firstChild.data = trimStart(firstChild.data); firstChild.data = trim_start(firstChild.data);
if (!firstChild.data) block.children.shift(); if (!firstChild.data) block.children.shift();
} }
if (lastChild.type === 'Text' && trimAfter) { if (lastChild.type === 'Text' && trim_after) {
lastChild.data = trimEnd(lastChild.data); lastChild.data = trim_end(lastChild.data);
if (!lastChild.data) block.children.pop(); if (!lastChild.data) block.children.pop();
} }
if (block.else) { if (block.else) {
trimWhitespace(block.else, trimBefore, trimAfter); trim_whitespace(block.else, trim_before, trim_after);
} }
if (firstChild.elseif) { if (firstChild.elseif) {
trimWhitespace(firstChild, trimBefore, trimAfter); trim_whitespace(firstChild, trim_before, trim_after);
} }
} }
@ -34,7 +34,7 @@ export default function mustache(parser: Parser) {
const start = parser.index; const start = parser.index;
parser.index += 1; parser.index += 1;
parser.allowWhitespace(); parser.allow_whitespace();
// {/if} or {/each} // {/if} or {/each}
if (parser.eat('/')) { if (parser.eat('/')) {
@ -63,7 +63,7 @@ export default function mustache(parser: Parser) {
} }
parser.eat(expected, true); parser.eat(expected, true);
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
while (block.elseif) { while (block.elseif) {
@ -77,12 +77,12 @@ export default function mustache(parser: Parser) {
} }
// strip leading/trailing whitespace as necessary // strip leading/trailing whitespace as necessary
const charBefore = parser.template[block.start - 1]; const char_before = parser.template[block.start - 1];
const charAfter = parser.template[parser.index]; const char_after = parser.template[parser.index];
const trimBefore = !charBefore || whitespace.test(charBefore); const trim_before = !char_before || whitespace.test(char_before);
const trimAfter = !charAfter || whitespace.test(charAfter); const trim_after = !char_after || whitespace.test(char_after);
trimWhitespace(block, trimBefore, trimAfter); trim_whitespace(block, trim_before, trim_after);
block.end = parser.index; block.end = parser.index;
parser.stack.pop(); parser.stack.pop();
@ -94,7 +94,7 @@ export default function mustache(parser: Parser) {
}); });
} }
parser.allowWhitespace(); parser.allow_whitespace();
// :else if // :else if
if (parser.eat('if')) { if (parser.eat('if')) {
@ -105,11 +105,11 @@ export default function mustache(parser: Parser) {
message: 'Cannot have an {:else if ...} block outside an {#if ...} block' message: 'Cannot have an {:else if ...} block outside an {#if ...} block'
}); });
parser.requireWhitespace(); parser.require_whitespace();
const expression = readExpression(parser); const expression = read_expression(parser);
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
block.else = { block.else = {
@ -141,7 +141,7 @@ export default function mustache(parser: Parser) {
}); });
} }
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
block.else = { block.else = {
@ -155,52 +155,52 @@ export default function mustache(parser: Parser) {
} }
} else if (parser.eat(':then')) { } else if (parser.eat(':then')) {
// TODO DRY out this and the next section // TODO DRY out this and the next section
const pendingBlock = parser.current(); const pending_block = parser.current();
if (pendingBlock.type === 'PendingBlock') { if (pending_block.type === 'PendingBlock') {
pendingBlock.end = start; pending_block.end = start;
parser.stack.pop(); parser.stack.pop();
const awaitBlock = parser.current(); const await_block = parser.current();
if (!parser.eat('}')) { if (!parser.eat('}')) {
parser.requireWhitespace(); parser.require_whitespace();
awaitBlock.value = parser.readIdentifier(); await_block.value = parser.read_identifier();
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
} }
const thenBlock: Node = { const then_block: Node = {
start, start,
end: null, end: null,
type: 'ThenBlock', type: 'ThenBlock',
children: [] children: []
}; };
awaitBlock.then = thenBlock; await_block.then = then_block;
parser.stack.push(thenBlock); parser.stack.push(then_block);
} }
} else if (parser.eat(':catch')) { } else if (parser.eat(':catch')) {
const thenBlock = parser.current(); const then_block = parser.current();
if (thenBlock.type === 'ThenBlock') { if (then_block.type === 'ThenBlock') {
thenBlock.end = start; then_block.end = start;
parser.stack.pop(); parser.stack.pop();
const awaitBlock = parser.current(); const await_block = parser.current();
if (!parser.eat('}')) { if (!parser.eat('}')) {
parser.requireWhitespace(); parser.require_whitespace();
awaitBlock.error = parser.readIdentifier(); await_block.error = parser.read_identifier();
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
} }
const catchBlock: Node = { const catch_block: Node = {
start, start,
end: null, end: null,
type: 'CatchBlock', type: 'CatchBlock',
children: [] children: []
}; };
awaitBlock.catch = catchBlock; await_block.catch = catch_block;
parser.stack.push(catchBlock); parser.stack.push(catch_block);
} }
} else if (parser.eat('#')) { } else if (parser.eat('#')) {
// {#if foo}, {#each foo} or {#await foo} // {#if foo}, {#each foo} or {#await foo}
@ -219,9 +219,9 @@ export default function mustache(parser: Parser) {
}); });
} }
parser.requireWhitespace(); parser.require_whitespace();
const expression = readExpression(parser); const expression = read_expression(parser);
const block: Node = type === 'AwaitBlock' ? const block: Node = type === 'AwaitBlock' ?
{ {
@ -258,50 +258,50 @@ export default function mustache(parser: Parser) {
children: [], children: [],
}; };
parser.allowWhitespace(); parser.allow_whitespace();
// {#each} blocks must declare a context {#each list as item} // {#each} blocks must declare a context {#each list as item}
if (type === 'EachBlock') { if (type === 'EachBlock') {
parser.eat('as', true); parser.eat('as', true);
parser.requireWhitespace(); parser.require_whitespace();
block.context = readContext(parser); block.context = read_context(parser);
parser.allowWhitespace(); parser.allow_whitespace();
if (parser.eat(',')) { if (parser.eat(',')) {
parser.allowWhitespace(); parser.allow_whitespace();
block.index = parser.readIdentifier(); block.index = parser.read_identifier();
if (!block.index) parser.error({ if (!block.index) parser.error({
code: `expected-name`, code: `expected-name`,
message: `Expected name` message: `Expected name`
}); });
parser.allowWhitespace(); parser.allow_whitespace();
} }
if (parser.eat('(')) { if (parser.eat('(')) {
parser.allowWhitespace(); parser.allow_whitespace();
block.key = readExpression(parser); block.key = read_expression(parser);
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat(')', true); parser.eat(')', true);
parser.allowWhitespace(); parser.allow_whitespace();
} else if (parser.eat('@')) { } else if (parser.eat('@')) {
block.key = parser.readIdentifier(); block.key = parser.read_identifier();
if (!block.key) parser.error({ if (!block.key) parser.error({
code: `expected-name`, code: `expected-name`,
message: `Expected name` message: `Expected name`
}); });
parser.allowWhitespace(); parser.allow_whitespace();
} }
} }
let awaitBlockShorthand = type === 'AwaitBlock' && parser.eat('then'); let await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (awaitBlockShorthand) { if (await_block_shorthand) {
parser.requireWhitespace(); parser.require_whitespace();
block.value = parser.readIdentifier(); block.value = parser.read_identifier();
parser.allowWhitespace(); parser.allow_whitespace();
} }
parser.eat('}', true); parser.eat('}', true);
@ -310,17 +310,17 @@ export default function mustache(parser: Parser) {
parser.stack.push(block); parser.stack.push(block);
if (type === 'AwaitBlock') { if (type === 'AwaitBlock') {
const childBlock = awaitBlockShorthand ? block.then : block.pending; const childBlock = await_block_shorthand ? block.then : block.pending;
childBlock.start = parser.index; childBlock.start = parser.index;
parser.stack.push(childBlock); parser.stack.push(childBlock);
} }
} else if (parser.eat('@html')) { } else if (parser.eat('@html')) {
// {@html content} tag // {@html content} tag
parser.requireWhitespace(); parser.require_whitespace();
const expression = readExpression(parser); const expression = read_expression(parser);
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
parser.current().children.push({ parser.current().children.push({
@ -336,7 +336,7 @@ export default function mustache(parser: Parser) {
if (parser.read(/\s*}/)) { if (parser.read(/\s*}/)) {
identifiers = []; identifiers = [];
} else { } else {
const expression = readExpression(parser); const expression = read_expression(parser);
identifiers = expression.type === 'SequenceExpression' identifiers = expression.type === 'SequenceExpression'
? expression.expressions ? expression.expressions
@ -351,7 +351,7 @@ export default function mustache(parser: Parser) {
} }
}); });
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
} }
@ -362,9 +362,9 @@ export default function mustache(parser: Parser) {
identifiers identifiers
}); });
} else { } else {
const expression = readExpression(parser); const expression = read_expression(parser);
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
parser.current().children.push({ parser.current().children.push({

@ -1,36 +1,36 @@
import readExpression from '../read/expression'; import read_expression from '../read/expression';
import readScript from '../read/script'; import read_script from '../read/script';
import readStyle from '../read/style'; import read_style from '../read/style';
import { decodeCharacterReferences } from '../utils/html'; import { decode_character_references } from '../utils/html';
import { is_void } from '../../utils/names'; import { is_void } from '../../utils/names';
import { Parser } from '../index'; import { Parser } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const metaTags = new Map([ const meta_tags = new Map([
['svelte:head', 'Head'], ['svelte:head', 'Head'],
['svelte:options', 'Options'], ['svelte:options', 'Options'],
['svelte:window', 'Window'], ['svelte:window', 'Window'],
['svelte:body', 'Body'] ['svelte:body', 'Body']
]); ]);
const valid_meta_tags = Array.from(metaTags.keys()).concat('svelte:self', 'svelte:component'); const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component');
const specials = new Map([ const specials = new Map([
[ [
'script', 'script',
{ {
read: readScript, read: read_script,
property: 'js', property: 'js',
}, },
], ],
[ [
'style', 'style',
{ {
read: readStyle, read: read_style,
property: 'css', property: 'css',
}, },
], ],
@ -40,7 +40,7 @@ const SELF = /^svelte:self(?=[\s\/>])/;
const COMPONENT = /^svelte:component(?=[\s\/>])/; const COMPONENT = /^svelte:component(?=[\s\/>])/;
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
const disallowedContents = new Map([ const disallowed_contents = new Map([
['li', new Set(['li'])], ['li', new Set(['li'])],
['dt', new Set(['dt', 'dd'])], ['dt', new Set(['dt', 'dd'])],
['dd', new Set(['dt', 'dd'])], ['dd', new Set(['dt', 'dd'])],
@ -64,7 +64,7 @@ const disallowedContents = new Map([
['th', new Set(['td', 'th', 'tr'])], ['th', new Set(['td', 'th', 'tr'])],
]); ]);
function parentIsHead(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];
@ -80,7 +80,7 @@ export default function tag(parser: Parser) {
let parent = parser.current(); let parent = parser.current();
if (parser.eat('!--')) { if (parser.eat('!--')) {
const data = parser.readUntil(/-->/); const data = parser.read_until(/-->/);
parser.eat('-->', true, 'comment was left open, expected -->'); parser.eat('-->', true, 'comment was left open, expected -->');
parser.current().children.push({ parser.current().children.push({
@ -93,13 +93,13 @@ export default function tag(parser: Parser) {
return; return;
} }
const isClosingTag = parser.eat('/'); const is_closing_tag = parser.eat('/');
const name = readTagName(parser); const name = read_tag_name(parser);
if (metaTags.has(name)) { if (meta_tags.has(name)) {
const slug = metaTags.get(name).toLowerCase(); const slug = meta_tags.get(name).toLowerCase();
if (isClosingTag) { 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
@ -110,7 +110,7 @@ export default function tag(parser: Parser) {
}, parser.current().children[0].start); }, parser.current().children[0].start);
} }
} else { } else {
if (name in parser.metaTags) { if (name in parser.meta_tags) {
parser.error({ parser.error({
code: `duplicate-${slug}`, code: `duplicate-${slug}`,
message: `A component can only have one <${name}> tag` message: `A component can only have one <${name}> tag`
@ -124,14 +124,14 @@ export default function tag(parser: Parser) {
}, start); }, start);
} }
parser.metaTags[name] = true; parser.meta_tags[name] = true;
} }
} }
const type = metaTags.has(name) const type = meta_tags.has(name)
? metaTags.get(name) ? meta_tags.get(name)
: (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent' : (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent'
: name === 'title' && parentIsHead(parser.stack) ? 'Title' : name === 'title' && parent_is_head(parser.stack) ? 'Title'
: name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; : name === 'slot' && !parser.customElement ? 'Slot' : 'Element';
const element: Node = { const element: Node = {
@ -143,9 +143,9 @@ export default function tag(parser: Parser) {
children: [], children: [],
}; };
parser.allowWhitespace(); parser.allow_whitespace();
if (isClosingTag) { if (is_closing_tag) {
if (is_void(name)) { if (is_void(name)) {
parser.error({ parser.error({
code: `invalid-void-content`, code: `invalid-void-content`,
@ -173,42 +173,21 @@ export default function tag(parser: Parser) {
parser.stack.pop(); parser.stack.pop();
return; return;
} else if (disallowedContents.has(parent.name)) { } else if (disallowed_contents.has(parent.name)) {
// can this be a child of the parent element, or does it implicitly // can this be a child of the parent element, or does it implicitly
// close it, like `<li>one<li>two`? // close it, like `<li>one<li>two`?
if (disallowedContents.get(parent.name).has(name)) { if (disallowed_contents.get(parent.name).has(name)) {
parent.end = start; parent.end = start;
parser.stack.pop(); parser.stack.pop();
} }
} }
// TODO should this still error in in web component mode? const unique_names = new Set();
// if (name === 'slot') {
// let i = parser.stack.length;
// while (i--) {
// const item = parser.stack[i];
// if (item.type === 'EachBlock') {
// parser.error({
// code: `invalid-slot-placement`,
// message: `<slot> cannot be a child of an each-block`
// }, start);
// }
// }
// }
const uniqueNames = new Set();
let attribute; let attribute;
while ((attribute = readAttribute(parser, uniqueNames))) { while ((attribute = readAttribute(parser, unique_names))) {
if (attribute.type === 'Binding' && !parser.allowBindings) {
parser.error({
code: `binding-disabled`,
message: `Two-way binding is disabled`
}, attribute.start);
}
element.attributes.push(attribute); element.attributes.push(attribute);
parser.allowWhitespace(); parser.allow_whitespace();
} }
if (name === 'svelte:component') { if (name === 'svelte:component') {
@ -244,16 +223,16 @@ export default function tag(parser: Parser) {
parser.current().children.push(element); parser.current().children.push(element);
const selfClosing = parser.eat('/') || is_void(name); const self_closing = parser.eat('/') || is_void(name);
parser.eat('>', true); parser.eat('>', true);
if (selfClosing) { 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 = readSequence( element.children = read_sequence(
parser, parser,
() => () =>
parser.template.slice(parser.index, parser.index + 11) === '</textarea>' parser.template.slice(parser.index, parser.index + 11) === '</textarea>'
@ -263,7 +242,7 @@ export default function tag(parser: Parser) {
} else if (name === 'script') { } else if (name === 'script') {
// special case // special case
const start = parser.index; const start = parser.index;
const data = parser.readUntil(/<\/script>/); const data = parser.read_until(/<\/script>/);
const end = parser.index; const end = parser.index;
element.children.push({ start, end, type: 'Text', data }); element.children.push({ start, end, type: 'Text', data });
parser.eat('</script>', true); parser.eat('</script>', true);
@ -271,7 +250,7 @@ export default function tag(parser: Parser) {
} else if (name === 'style') { } else if (name === 'style') {
// special case // special case
const start = parser.index; const start = parser.index;
const data = parser.readUntil(/<\/style>/); const data = parser.read_until(/<\/style>/);
const end = parser.index; const end = parser.index;
element.children.push({ start, end, type: 'Text', data }); element.children.push({ start, end, type: 'Text', data });
parser.eat('</style>', true); parser.eat('</style>', true);
@ -280,7 +259,7 @@ export default function tag(parser: Parser) {
} }
} }
function readTagName(parser: Parser) { function read_tag_name(parser: Parser) {
const start = parser.index; const start = parser.index;
if (parser.read(SELF)) { if (parser.read(SELF)) {
@ -309,9 +288,9 @@ function readTagName(parser: Parser) {
if (parser.read(COMPONENT)) return 'svelte:component'; if (parser.read(COMPONENT)) return 'svelte:component';
const name = parser.readUntil(/(\s|\/|>)/); const name = parser.read_until(/(\s|\/|>)/);
if (metaTags.has(name)) return name; if (meta_tags.has(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);
@ -325,7 +304,7 @@ function readTagName(parser: Parser) {
}, start); }, start);
} }
if (!validTagName.test(name)) { if (!valid_tag_name.test(name)) {
parser.error({ parser.error({
code: `invalid-tag-name`, code: `invalid-tag-name`,
message: `Expected valid tag name` message: `Expected valid tag name`
@ -335,16 +314,16 @@ function readTagName(parser: Parser) {
return name; return name;
} }
function readAttribute(parser: Parser, uniqueNames: Set<string>) { function readAttribute(parser: Parser, unique_names: Set<string>) {
const start = parser.index; const start = parser.index;
if (parser.eat('{')) { if (parser.eat('{')) {
parser.allowWhitespace(); parser.allow_whitespace();
if (parser.eat('...')) { if (parser.eat('...')) {
const expression = readExpression(parser); const expression = read_expression(parser);
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
return { return {
@ -354,10 +333,10 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
expression expression
}; };
} else { } else {
const valueStart = parser.index; const value_start = parser.index;
const name = parser.readIdentifier(); const name = parser.read_identifier();
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
return { return {
@ -366,12 +345,12 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
type: 'Attribute', type: 'Attribute',
name, name,
value: [{ value: [{
start: valueStart, start: value_start,
end: valueStart + name.length, end: value_start + name.length,
type: 'AttributeShorthand', type: 'AttributeShorthand',
expression: { expression: {
start: valueStart, start: value_start,
end: valueStart + name.length, end: value_start + name.length,
type: 'Identifier', type: 'Identifier',
name name
} }
@ -380,27 +359,27 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
} }
} }
let name = parser.readUntil(/(\s|=|\/|>)/); let name = parser.read_until(/(\s|=|\/|>)/);
if (!name) return null; if (!name) return null;
if (uniqueNames.has(name)) { if (unique_names.has(name)) {
parser.error({ parser.error({
code: `duplicate-attribute`, code: `duplicate-attribute`,
message: 'Attributes need to be unique' message: 'Attributes need to be unique'
}, start); }, start);
} }
uniqueNames.add(name); unique_names.add(name);
let end = parser.index; let end = parser.index;
parser.allowWhitespace(); 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; let value: any[] | true = true;
if (parser.eat('=')) { if (parser.eat('=')) {
value = readAttributeValue(parser); value = read_attribute_value(parser);
end = parser.index; end = parser.index;
} }
@ -470,23 +449,23 @@ function get_directive_type(name) {
if (name === 'in' || name === 'out' || name === 'transition') return 'Transition'; if (name === 'in' || name === 'out' || name === 'transition') return 'Transition';
} }
function readAttributeValue(parser: Parser) { function read_attribute_value(parser: Parser) {
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null; const quote_mark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
const regex = ( const regex = (
quoteMark === `'` ? /'/ : quote_mark === `'` ? /'/ :
quoteMark === `"` ? /"/ : quote_mark === `"` ? /"/ :
/(\/>|[\s"'=<>`])/ /(\/>|[\s"'=<>`])/
); );
const value = readSequence(parser, () => !!parser.matchRegex(regex)); const value = read_sequence(parser, () => !!parser.match_regex(regex));
if (quoteMark) parser.index += 1; if (quote_mark) parser.index += 1;
return value; return value;
} }
function readSequence(parser: Parser, done: () => boolean) { function read_sequence(parser: Parser, done: () => boolean) {
let currentChunk: Node = { let current_chunk: Node = {
start: parser.index, start: parser.index,
end: null, end: null,
type: 'Text', type: 'Text',
@ -499,25 +478,25 @@ function readSequence(parser: Parser, done: () => boolean) {
const index = parser.index; const index = parser.index;
if (done()) { if (done()) {
currentChunk.end = parser.index; current_chunk.end = parser.index;
if (currentChunk.data) chunks.push(currentChunk); if (current_chunk.data) chunks.push(current_chunk);
chunks.forEach(chunk => { chunks.forEach(chunk => {
if (chunk.type === 'Text') if (chunk.type === 'Text')
chunk.data = decodeCharacterReferences(chunk.data); chunk.data = decode_character_references(chunk.data);
}); });
return chunks; return chunks;
} else if (parser.eat('{')) { } else if (parser.eat('{')) {
if (currentChunk.data) { if (current_chunk.data) {
currentChunk.end = index; current_chunk.end = index;
chunks.push(currentChunk); chunks.push(current_chunk);
} }
parser.allowWhitespace(); parser.allow_whitespace();
const expression = readExpression(parser); const expression = read_expression(parser);
parser.allowWhitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
chunks.push({ chunks.push({
@ -527,14 +506,14 @@ function readSequence(parser: Parser, done: () => boolean) {
expression, expression,
}); });
currentChunk = { current_chunk = {
start: parser.index, start: parser.index,
end: null, end: null,
type: 'Text', type: 'Text',
data: '', data: '',
}; };
} else { } else {
currentChunk.data += parser.template[parser.index++]; current_chunk.data += parser.template[parser.index++];
} }
} }

@ -1,4 +1,4 @@
import { decodeCharacterReferences } from '../utils/html'; import { decode_character_references } from '../utils/html';
import { Parser } from '../index'; import { Parser } from '../index';
export default function text(parser: Parser) { export default function text(parser: Parser) {
@ -18,6 +18,6 @@ export default function text(parser: Parser) {
start, start,
end: parser.index, end: parser.index,
type: 'Text', type: 'Text',
data: decodeCharacterReferences(data), data: decode_character_references(data),
}); });
} }

@ -1,6 +1,6 @@
import htmlEntities from './entities'; import entities from './entities';
const windows1252 = [ const windows_1252 = [
8364, 8364,
129, 129,
8218, 8218,
@ -34,18 +34,19 @@ const windows1252 = [
382, 382,
376, 376,
]; ];
const entityPattern = new RegExp(
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(htmlEntities).join('|')}));?`, const entity_pattern = new RegExp(
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}));?`,
'g' 'g'
); );
export function decodeCharacterReferences(html: string) { export function decode_character_references(html: string) {
return html.replace(entityPattern, (match, entity) => { return html.replace(entity_pattern, (match, entity) => {
let code; let code;
// Handle named entities // Handle named entities
if (entity[0] !== '#') { if (entity[0] !== '#') {
code = htmlEntities[entity]; code = entities[entity];
} else if (entity[1] === 'x') { } else if (entity[1] === 'x') {
code = parseInt(entity.substring(2), 16); code = parseInt(entity.substring(2), 16);
} else { } else {
@ -56,7 +57,7 @@ export function decodeCharacterReferences(html: string) {
return match; return match;
} }
return String.fromCodePoint(validateCode(code)); return String.fromCodePoint(validate_code(code));
}); });
} }
@ -67,7 +68,7 @@ const NUL = 0;
// to replace them ourselves // to replace them ourselves
// //
// Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters // Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters
function validateCode(code: number) { function validate_code(code: number) {
// line feed becomes generic whitespace // line feed becomes generic whitespace
if (code === 10) { if (code === 10) {
return 32; return 32;
@ -81,7 +82,7 @@ function validateCode(code: number) {
// code points 128-159 are dealt with leniently by browsers, but they're incorrect. We need // code points 128-159 are dealt with leniently by browsers, but they're incorrect. We need
// to correct the mistake or we'll end up with missing € signs and so on // to correct the mistake or we'll end up with missing € signs and so on
if (code <= 159) { if (code <= 159) {
return windows1252[code - 128]; return windows_1252[code - 128];
} }
// basic multilingual plane // basic multilingual plane

@ -21,17 +21,17 @@ interface Processed {
dependencies?: string[]; dependencies?: string[];
} }
function parseAttributeValue(value: string) { function parse_attribute_value(value: string) {
return /^['"]/.test(value) ? return /^['"]/.test(value) ?
value.slice(1, -1) : value.slice(1, -1) :
value; value;
} }
function parseAttributes(str: string) { function parse_attributes(str: string) {
const attrs = {}; const attrs = {};
str.split(/\s+/).filter(Boolean).forEach(attr => { str.split(/\s+/).filter(Boolean).forEach(attr => {
const [name, value] = attr.split('='); const [name, value] = attr.split('=');
attrs[name] = value ? parseAttributeValue(value) : true; attrs[name] = value ? parse_attribute_value(value) : true;
}); });
return attrs; return attrs;
} }
@ -99,7 +99,7 @@ export default async function preprocess(
async (match, attributes, content) => { async (match, attributes, content) => {
const processed: Processed = await fn({ const processed: Processed = await fn({
content, content,
attributes: parseAttributes(attributes), attributes: parse_attributes(attributes),
filename filename
}); });
if (processed && processed.dependencies) dependencies.push(...processed.dependencies); if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
@ -115,7 +115,7 @@ export default async function preprocess(
async (match, attributes, content) => { async (match, attributes, content) => {
const processed: Processed = await fn({ const processed: Processed = await fn({
content, content,
attributes: parseAttributes(attributes), attributes: parse_attributes(attributes),
filename filename
}); });
if (processed && processed.dependencies) dependencies.push(...processed.dependencies); if (processed && processed.dependencies) dependencies.push(...processed.dependencies);

@ -1,13 +1,13 @@
import { whitespace } from './patterns'; import { whitespace } from './patterns';
export function trimStart(str: string) { export function trim_start(str: string) {
let i = 0; let i = 0;
while (whitespace.test(str[i])) i += 1; while (whitespace.test(str[i])) i += 1;
return str.slice(i); return str.slice(i);
} }
export function trimEnd(str: string) { export function trim_end(str: string) {
let i = str.length; let i = str.length;
while (whitespace.test(str[i - 1])) i -= 1; while (whitespace.test(str[i - 1])) i -= 1;

@ -1,22 +0,0 @@
export default function unpackDestructuring(
contexts: Array<{ name: string, tail: string }>,
node: Node,
tail: string
) {
if (!node) return;
if (node.type === 'Identifier') {
contexts.push({
key: node,
tail
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
unpackDestructuring(contexts, element, `${tail}[${i}]`);
});
} else if (node.type === 'ObjectPattern') {
node.properties.forEach((property) => {
unpackDestructuring(contexts, property.value, `${tail}.${property.key.name}`);
});
}
}
Loading…
Cancel
Save