dry {#each}/{#await} destructuring (#4596)

pull/4835/head
Tan Li Hau 5 years ago committed by GitHub
parent c743e72a1e
commit 37cc5888f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,9 @@
# Svelte changelog # Svelte changelog
## Unreleased
* Support default values and trailing commas in destructuring `{#await}` ([#4560](https://github.com/sveltejs/svelte/issues/4560), [#4810](https://github.com/sveltejs/svelte/issues/4810))
## 3.22.2 ## 3.22.2
* Fix compiler exception with `a11y-img-redundant-alt` and value-less `alt` attribute ([#4777](https://github.com/sveltejs/svelte/issues/4777)) * Fix compiler exception with `a11y-img-redundant-alt` and value-less `alt` attribute ([#4777](https://github.com/sveltejs/svelte/issues/4777))

@ -3,17 +3,21 @@ import PendingBlock from './PendingBlock';
import ThenBlock from './ThenBlock'; import ThenBlock from './ThenBlock';
import CatchBlock from './CatchBlock'; import CatchBlock from './CatchBlock';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import { Pattern } from 'estree';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import traverse_destructure_pattern from '../utils/traverse_destructure_pattern'; import { Context, unpack_destructuring } from './shared/Context';
import { Node as ESTreeNode } from 'estree';
export default class AwaitBlock extends Node { export default class AwaitBlock extends Node {
type: 'AwaitBlock'; type: 'AwaitBlock';
expression: Expression; expression: Expression;
value: DestructurePattern;
error: DestructurePattern; then_contexts: Context[];
catch_contexts: Context[];
then_node: ESTreeNode | null;
catch_node: ESTreeNode | null;
pending: PendingBlock; pending: PendingBlock;
then: ThenBlock; then: ThenBlock;
@ -24,24 +28,21 @@ export default class AwaitBlock extends Node {
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.value = info.value && new DestructurePattern(info.value); this.then_node = info.value;
this.error = info.error && new DestructurePattern(info.error); this.catch_node = info.error;
if (this.then_node) {
this.then_contexts = [];
unpack_destructuring(this.then_contexts, info.value, node => node);
}
if (this.catch_node) {
this.catch_contexts = [];
unpack_destructuring(this.catch_contexts, info.error, node => node);
}
this.pending = new PendingBlock(component, this, scope, info.pending); this.pending = new PendingBlock(component, this, scope, info.pending);
this.then = new ThenBlock(component, this, scope, info.then); this.then = new ThenBlock(component, this, scope, info.then);
this.catch = new CatchBlock(component, this, scope, info.catch); this.catch = new CatchBlock(component, this, scope, info.catch);
} }
} }
export class DestructurePattern {
pattern: Pattern;
expressions: string[];
identifier_name: string | undefined;
constructor(pattern: Pattern) {
this.pattern = pattern;
this.expressions = [];
traverse_destructure_pattern(pattern, (node) => this.expressions.push(node.name));
this.identifier_name = this.pattern.type === 'Identifier' ? this.pattern.name : undefined;
}
}

@ -13,9 +13,9 @@ export default class CatchBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
if (parent.error) { if (parent.catch_node) {
parent.error.expressions.forEach(expression => { parent.catch_contexts.forEach(context => {
this.scope.add(expression, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
}); });
} }
this.children = map_children(component, parent, this.scope, info.children); this.children = map_children(component, parent, this.scope, info.children);

@ -4,56 +4,8 @@ import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock'; import AbstractBlock from './shared/AbstractBlock';
import Element from './Element'; import Element from './Element';
import { x } from 'code-red'; import { Context, unpack_destructuring } from './shared/Context';
import { Node, Identifier, RestElement } from 'estree'; import { Node } from 'estree';
interface Context {
key: Identifier;
name?: string;
modifier: (node: Node) => Node;
}
function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) {
if (!node) return;
if (node.type === 'Identifier' || (node as any).type === 'RestIdentifier') { // TODO is this right? not RestElement?
contexts.push({
key: node as Identifier,
modifier
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
if (element && (element as any).type === 'RestIdentifier') {
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node);
} else {
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node);
}
});
} else if (node.type === 'ObjectPattern') {
const used_properties = [];
node.properties.forEach((property, i) => {
if ((property as any).kind === 'rest') { // TODO is this right?
const replacement: RestElement = {
type: 'RestElement',
argument: property.key as Identifier
};
node.properties[i] = replacement as any;
unpack_destructuring(
contexts,
property.value,
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node
);
} else {
used_properties.push(x`"${(property.key as Identifier).name}"`);
unpack_destructuring(contexts, property.value, node => x`${modifier(node)}.${(property.key as Identifier).name}` as Node);
}
});
}
}
export default class EachBlock extends AbstractBlock { export default class EachBlock extends AbstractBlock {
type: 'EachBlock'; type: 'EachBlock';

@ -13,9 +13,9 @@ export default class ThenBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
if (parent.value) { if (parent.then_node) {
parent.value.expressions.forEach(expression => { parent.then_contexts.forEach(context => {
this.scope.add(expression, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
}); });
} }
this.children = map_children(component, parent, this.scope, info.children); this.children = map_children(component, parent, this.scope, info.children);

@ -0,0 +1,58 @@
import { x } from 'code-red';
import { Node, Identifier, RestElement, Property } from 'estree';
export interface Context {
key: Identifier;
name?: string;
modifier: (node: Node) => Node;
}
export function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) {
if (!node) return;
if (node.type === 'Identifier') {
contexts.push({
key: node as Identifier,
modifier
});
} else if (node.type === 'RestElement') {
contexts.push({
key: node.argument as Identifier,
modifier
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
if (element && element.type === 'RestElement') {
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node);
} else if (element && element.type === 'AssignmentPattern') {
unpack_destructuring(contexts, element.left, node => x`${modifier(node)}[${i}] !== undefined ? ${modifier(node)}[${i}] : ${element.right}` as Node);
} else {
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node);
}
});
} else if (node.type === 'ObjectPattern') {
const used_properties = [];
node.properties.forEach((property) => {
const props: (RestElement | Property) = (property as any);
if (props.type === 'RestElement') {
unpack_destructuring(
contexts,
props.argument,
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node
);
} else {
const key = property.key as Identifier;
const value = property.value;
used_properties.push(x`"${(key as Identifier).name}"`);
if (value.type === 'AssignmentPattern') {
unpack_destructuring(contexts, value.left, node => x`${modifier(node)}.${key.name} !== undefined ? ${modifier(node)}.${key.name} : ${value.right}` as Node);
} else {
unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node);
}
}
});
}
}

@ -8,27 +8,37 @@ import FragmentWrapper from './Fragment';
import PendingBlock from '../../nodes/PendingBlock'; import PendingBlock from '../../nodes/PendingBlock';
import ThenBlock from '../../nodes/ThenBlock'; import ThenBlock from '../../nodes/ThenBlock';
import CatchBlock from '../../nodes/CatchBlock'; import CatchBlock from '../../nodes/CatchBlock';
import { Identifier } from 'estree'; import { Context } from '../../nodes/shared/Context';
import traverse_destructure_pattern from '../../utils/traverse_destructure_pattern'; import { Identifier, Literal, Node } from 'estree';
type Status = 'pending' | 'then' | 'catch';
class AwaitBlockBranch extends Wrapper { class AwaitBlockBranch extends Wrapper {
parent: AwaitBlockWrapper;
node: PendingBlock | ThenBlock | CatchBlock; node: PendingBlock | ThenBlock | CatchBlock;
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
is_dynamic: boolean; is_dynamic: boolean;
var = null; var = null;
status: Status;
value: string;
value_index: Literal;
value_contexts: Context[];
is_destructured: boolean;
constructor( constructor(
status: string, status: Status,
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
parent: Wrapper, parent: AwaitBlockWrapper,
node: AwaitBlock, node: PendingBlock | ThenBlock | CatchBlock,
strip_whitespace: boolean, strip_whitespace: boolean,
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.status = status;
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component), comment: create_debugging_comment(node, this.renderer.component),
@ -36,6 +46,8 @@ class AwaitBlockBranch extends Wrapper {
type: status type: status
}); });
this.add_context(parent.node[status + '_node'], parent.node[status + '_contexts']);
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,
this.block, this.block,
@ -48,20 +60,43 @@ class AwaitBlockBranch extends Wrapper {
this.is_dynamic = this.block.dependencies.size > 0; this.is_dynamic = this.block.dependencies.size > 0;
} }
add_context(node: Node | null, contexts: Context[]) {
if (!node) return;
if (node.type === 'Identifier') {
this.value = node.name;
this.renderer.add_to_context(this.value, true);
} else {
contexts.forEach(context => {
this.renderer.add_to_context(context.key.name, true);
});
this.value = this.block.parent.get_unique_name('value').name;
this.value_contexts = contexts;
this.renderer.add_to_context(this.value, true);
this.is_destructured = true;
}
this.value_index = this.renderer.context_lookup.get(this.value).index;
}
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
this.fragment.render(block, parent_node, parent_nodes); this.fragment.render(block, parent_node, parent_nodes);
}
render_destructure(block: Block, value, node, index) { if (this.is_destructured) {
if (value && node.pattern.type !== 'Identifier') { this.render_destructure();
traverse_destructure_pattern(node.pattern, (node, parent, index) => { }
parent[index] = x`#ctx[${block.renderer.context_lookup.get(node.name).index}]`; }
});
this.block.chunks.declarations.push(b`(${node.pattern} = #ctx[${index}])`); render_destructure() {
if (this.block.has_update_method) { const props = this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`#ctx[${this.value_index}]`)};`);
this.block.chunks.update.push(b`(${node.pattern} = #ctx[${index}])`); const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`);
this.block.renderer.blocks.push(b`
function ${get_context}(#ctx) {
${props}
} }
`);
this.block.chunks.declarations.push(b`${get_context}(#ctx)`);
if (this.block.has_update_method) {
this.block.chunks.update.push(b`${get_context}(#ctx)`);
} }
} }
} }
@ -73,9 +108,6 @@ export default class AwaitBlockWrapper extends Wrapper {
then: AwaitBlockBranch; then: AwaitBlockBranch;
catch: AwaitBlockBranch; catch: AwaitBlockBranch;
value: string;
error: string;
var: Identifier = { type: 'Identifier', name: 'await_block' }; var: Identifier = { type: 'Identifier', name: 'await_block' };
constructor( constructor(
@ -92,26 +124,12 @@ export default class AwaitBlockWrapper extends Wrapper {
this.not_static_content(); this.not_static_content();
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
if (this.node.value) {
for (const ctx of this.node.value.expressions) {
block.renderer.add_to_context(ctx, true);
}
this.value = this.node.value.identifier_name || block.get_unique_name('value').name;
block.renderer.add_to_context(this.value, true);
}
if (this.node.error) {
for (const ctx of this.node.error.expressions) {
block.renderer.add_to_context(ctx, true);
}
this.error = this.node.error.identifier_name || block.get_unique_name('error').name;
block.renderer.add_to_context(this.error, true);
}
let is_dynamic = false; let is_dynamic = false;
let has_intros = false; let has_intros = false;
let has_outros = false; let has_outros = false;
['pending', 'then', 'catch'].forEach(status => { ['pending', 'then', 'catch'].forEach((status: Status) => {
const child = this.node[status]; const child = this.node[status];
const branch = new AwaitBlockBranch( const branch = new AwaitBlockBranch(
@ -166,9 +184,6 @@ export default class AwaitBlockWrapper extends Wrapper {
block.maintain_context = true; block.maintain_context = true;
const value_index = this.value && block.renderer.context_lookup.get(this.value).index;
const error_index = this.error && block.renderer.context_lookup.get(this.error).index;
const info_props: any = x`{ const info_props: any = x`{
ctx: #ctx, ctx: #ctx,
current: null, current: null,
@ -176,8 +191,8 @@ export default class AwaitBlockWrapper extends Wrapper {
pending: ${this.pending.block.name}, pending: ${this.pending.block.name},
then: ${this.then.block.name}, then: ${this.then.block.name},
catch: ${this.catch.block.name}, catch: ${this.catch.block.name},
value: ${value_index}, value: ${this.then.value_index},
error: ${error_index}, error: ${this.catch.value_index},
blocks: ${this.pending.block.has_outro_method && x`[,,,]`} blocks: ${this.pending.block.has_outro_method && x`[,,,]`}
}`; }`;
@ -232,7 +247,7 @@ export default class AwaitBlockWrapper extends Wrapper {
} else { } else {
const #child_ctx = #ctx.slice(); const #child_ctx = #ctx.slice();
${this.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} ${this.then.value && b`#child_ctx[${this.then.value_index}] = ${info}.resolved;`}
${info}.block.p(#child_ctx, #dirty); ${info}.block.p(#child_ctx, #dirty);
} }
`); `);
@ -246,7 +261,7 @@ export default class AwaitBlockWrapper extends Wrapper {
block.chunks.update.push(b` block.chunks.update.push(b`
{ {
const #child_ctx = #ctx.slice(); const #child_ctx = #ctx.slice();
${this.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} ${this.then.value && b`#child_ctx[${this.then.value_index}] = ${info}.resolved;`}
${info}.block.p(#child_ctx, #dirty); ${info}.block.p(#child_ctx, #dirty);
} }
`); `);
@ -271,7 +286,5 @@ export default class AwaitBlockWrapper extends Wrapper {
[this.pending, this.then, this.catch].forEach(branch => { [this.pending, this.then, this.catch].forEach(branch => {
branch.render(branch.block, null, x`#nodes` as Identifier); branch.render(branch.block, null, x`#nodes` as Identifier);
}); });
this.then.render_destructure(block, this.value, this.node.value, value_index);
this.catch.render_destructure(block, this.error, this.node.error, error_index);
} }
} }

@ -14,7 +14,7 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt
renderer.add_expression(x` renderer.add_expression(x`
function(__value) { function(__value) {
if (@is_promise(__value)) return ${pending}; if (@is_promise(__value)) return ${pending};
return (function(${node.value ? node.value.pattern : ''}) { return ${then}; }(__value)); return (function(${node.then_node ? node.then_node : ''}) { return ${then}; }(__value));
}(${node.expression.node}) }(${node.expression.node})
`); `);
} }

@ -1,35 +0,0 @@
import { Pattern, Identifier, RestElement } from "estree";
import { Node } from "acorn";
export default function traverse_destructure_pattern(
node: Pattern,
callback: (node: Identifier, parent: Node, key: string | number) => void
) {
function traverse(node: Pattern, parent, key) {
switch (node.type) {
case "Identifier":
return callback(node, parent, key);
case "ArrayPattern":
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
traverse(element, node.elements, i);
}
break;
case "ObjectPattern":
for (let i = 0; i < node.properties.length; i++) {
const property = node.properties[i];
if (property.type === "Property") {
traverse(property.value, property, "value");
} else {
traverse((property as any) as RestElement, node.properties, i);
}
}
break;
case "RestElement":
return traverse(node.argument, node, 'argument');
case "AssignmentPattern":
return traverse(node.left, node, 'left');
}
}
traverse(node, null, null);
}

@ -5,9 +5,6 @@ import { reserved } from '../utils/names';
import full_char_code_at from '../utils/full_char_code_at'; import full_char_code_at from '../utils/full_char_code_at';
import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces'; import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
import { is_bracket_open, is_bracket_close, is_bracket_pair, get_bracket_close } from './utils/bracket';
import { parse_expression_at } from './acorn';
import { Pattern } from 'estree';
type ParserState = (parser: Parser) => (ParserState | void); type ParserState = (parser: Parser) => (ParserState | void);
@ -173,51 +170,6 @@ export class Parser {
return identifier; return identifier;
} }
read_destructure_pattern(): Pattern {
const start = this.index;
let i = this.index;
const code = full_char_code_at(this.template, i);
if (isIdentifierStart(code, true)) {
return { type: 'Identifier', name: this.read_identifier() };
}
if (!is_bracket_open(code)) {
this.error({
code: 'unexpected-token',
message: 'Expected identifier or destructure pattern',
});
}
const bracket_stack = [code];
i += code <= 0xffff ? 1 : 2;
while (i < this.template.length) {
const code = full_char_code_at(this.template, i);
if (is_bracket_open(code)) {
bracket_stack.push(code);
} else if (is_bracket_close(code)) {
if (!is_bracket_pair(bracket_stack[bracket_stack.length - 1], code)) {
this.error({
code: 'unexpected-token',
message: `Expected ${String.fromCharCode(get_bracket_close(bracket_stack[bracket_stack.length - 1]))}`
});
}
bracket_stack.pop();
if (bracket_stack.length === 0) {
i += code <= 0xffff ? 1 : 2;
break;
}
}
i += code <= 0xffff ? 1 : 2;
}
this.index = i;
const pattern_string = this.template.slice(start, i);
return (parse_expression_at(`(${pattern_string} = 1)`, 0) as any).left as Pattern;
}
read_until(pattern: RegExp) { read_until(pattern: RegExp) {
if (this.index >= this.template.length) if (this.index >= this.template.length)
this.error({ this.error({

@ -1,202 +1,82 @@
import { Parser } from '../index'; import { Parser } from "../index";
import { reserved } from '../../utils/names'; import { isIdentifierStart } from "acorn";
import full_char_code_at from "../../utils/full_char_code_at";
interface Identifier { import {
start: number; is_bracket_open,
end: number; is_bracket_close,
type: 'Identifier'; is_bracket_pair,
name: string; get_bracket_close
} } from "../utils/bracket";
import { parse_expression_at } from "../acorn";
interface Property { import { Pattern } from "estree";
start: number;
end: number; export default function read_context(
type: 'Property'; parser: Parser
kind: 'init' | 'rest'; ): Pattern & { start: number; end: number } {
shorthand: boolean; const start = parser.index;
key: Identifier; let i = parser.index;
value: Context;
} const code = full_char_code_at(parser.template, i);
if (isIdentifierStart(code, true)) {
interface Context { return {
start: number; type: "Identifier",
end: number; name: parser.read_identifier(),
type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern' | 'RestIdentifier'; start,
name?: string; end: parser.index
elements?: Context[]; };
properties?: Property[]; }
}
function error_on_assignment_pattern(parser: Parser) { if (!is_bracket_open(code)) {
if (parser.eat('=')) {
parser.error({ parser.error({
code: 'invalid-assignment-pattern', code: "unexpected-token",
message: 'Assignment patterns are not supported' message: "Expected identifier or destructure pattern"
}, parser.index - 1); });
} }
}
function error_on_rest_pattern_not_last(parser: Parser) {
parser.error({
code: 'rest-pattern-not-last',
message: 'Rest destructuring expected to be last'
}, parser.index);
}
export default function read_context(parser: Parser) {
const context: Context = {
start: parser.index,
end: null,
type: null
};
if (parser.eat('[')) { const bracket_stack = [code];
context.type = 'ArrayPattern'; i += code <= 0xffff ? 1 : 2;
context.elements = [];
while (i < parser.template.length) {
do { const code = full_char_code_at(parser.template, i);
parser.allow_whitespace(); if (is_bracket_open(code)) {
bracket_stack.push(code);
const lastContext = context.elements[context.elements.length - 1]; } else if (is_bracket_close(code)) {
if (lastContext && lastContext.type === 'RestIdentifier') { if (!is_bracket_pair(bracket_stack[bracket_stack.length - 1], code)) {
error_on_rest_pattern_not_last(parser); parser.error({
code: "unexpected-token",
message: `Expected ${String.fromCharCode(
get_bracket_close(bracket_stack[bracket_stack.length - 1])
)}`
});
} }
bracket_stack.pop();
if (parser.template[parser.index] === ',') { if (bracket_stack.length === 0) {
context.elements.push(null); i += code <= 0xffff ? 1 : 2;
} else {
context.elements.push(read_context(parser));
parser.allow_whitespace();
}
} while (parser.eat(','));
error_on_assignment_pattern(parser);
parser.eat(']', true);
context.end = parser.index;
}
else if (parser.eat('{')) {
context.type = 'ObjectPattern';
context.properties = [];
do {
parser.allow_whitespace();
if (parser.eat('...')) {
parser.allow_whitespace();
const start = parser.index;
const name = parser.read_identifier();
const key: Identifier = {
start,
end: parser.index,
type: 'Identifier',
name
};
const property: Property = {
start,
end: parser.index,
type: 'Property',
kind: 'rest',
shorthand: true,
key,
value: key
};
context.properties.push(property);
parser.allow_whitespace();
if (parser.eat(',')) {
parser.error({
code: `comma-after-rest`,
message: `Comma is not permitted after the rest element`
}, parser.index - 1);
}
break; break;
} }
// TODO: DRY this out somehow
// We don't know whether we want to allow reserved words until we see whether there's a ':' after it
// Probably ideally we'd use Acorn to do all of this
const start = parser.index;
const name = parser.read_identifier(true);
const key: Identifier = {
start,
end: parser.index,
type: 'Identifier',
name
};
parser.allow_whitespace();
let value: Context;
if (parser.eat(':')) {
parser.allow_whitespace();
value = read_context(parser);
} else {
if (reserved.has(name)) {
parser.error({
code: `unexpected-reserved-word`,
message: `'${name}' is a reserved word in JavaScript and cannot be used here`
}, start);
}
value = key;
}
const property: Property = {
start,
end: value.end,
type: 'Property',
kind: 'init',
shorthand: value.type === 'Identifier' && value.name === name,
key,
value
};
context.properties.push(property);
parser.allow_whitespace();
} while (parser.eat(','));
error_on_assignment_pattern(parser);
parser.eat('}', true);
context.end = parser.index;
}
else if (parser.eat('...')) {
const name = parser.read_identifier();
if (name) {
context.type = 'RestIdentifier';
context.end = parser.index;
context.name = name;
}
else {
parser.error({
code: 'invalid-context',
message: 'Expected a rest pattern'
});
} }
i += code <= 0xffff ? 1 : 2;
} }
else { parser.index = i;
const name = parser.read_identifier();
if (name) { const pattern_string = parser.template.slice(start, i);
context.type = 'Identifier'; try {
context.end = parser.index; // the length of the `space_with_newline` has to be start - 1
context.name = name; // because we added a `(` in front of the pattern_string,
} // which shifted the entire string to right by 1
// so we offset it by removing 1 character in the `space_with_newline`
else { // to achieve that, we remove the 1st space encountered,
parser.error({ // so it will not affect the `column` of the node
code: 'invalid-context', let space_with_newline = parser.template.slice(0, start).replace(/[^\n]/g, ' ');
message: 'Expected a name, array pattern or object pattern' const first_space = space_with_newline.indexOf(' ');
}); space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
}
return (parse_expression_at(
error_on_assignment_pattern(parser); `${space_with_newline}(${pattern_string} = 1)`,
start - 1
) as any).left;
} catch (error) {
parser.acorn_error(error);
} }
return context;
} }

@ -196,7 +196,7 @@ export default function mustache(parser: Parser) {
if (!parser.eat('}')) { if (!parser.eat('}')) {
parser.require_whitespace(); parser.require_whitespace();
await_block[is_then ? 'value': 'error'] = parser.read_destructure_pattern(); await_block[is_then ? 'value': 'error'] = read_context(parser);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('}', true); parser.eat('}', true);
} }
@ -305,14 +305,14 @@ export default function mustache(parser: Parser) {
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then'); const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) { if (await_block_shorthand) {
parser.require_whitespace(); parser.require_whitespace();
block.value = parser.read_destructure_pattern(); block.value = read_context(parser);
parser.allow_whitespace(); parser.allow_whitespace();
} }
const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch'); const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
if (await_block_catch_shorthand) { if (await_block_catch_shorthand) {
parser.require_whitespace(); parser.require_whitespace();
block.error = parser.read_destructure_pattern(); block.error = read_context(parser);
parser.allow_whitespace(); parser.allow_whitespace();
} }

@ -26,6 +26,8 @@
}, },
"value": null, "value": null,
"error": { "error": {
"start": 47,
"end": 55,
"type": "Identifier", "type": "Identifier",
"name": "theError" "name": "theError"
}, },

@ -25,10 +25,14 @@
"name": "thePromise" "name": "thePromise"
}, },
"value": { "value": {
"start": 46,
"end": 54,
"type": "Identifier", "type": "Identifier",
"name": "theValue" "name": "theValue"
}, },
"error": { "error": {
"start": 96,
"end": 104,
"type": "Identifier", "type": "Identifier",
"name": "theError" "name": "theError"
}, },

@ -1,3 +1,7 @@
<script>
export let animals;
</script>
{#each animals as [key, value, ...rest]} {#each animals as [key, value, ...rest]}
<p>{key}: {value}</p> <p>{key}: {value}</p>
{/each} {/each}

@ -1,24 +1,31 @@
{ {
"html": { "html": {
"start": 0, "start": 41,
"end": 71, "end": 112,
"type": "Fragment", "type": "Fragment",
"children": [ "children": [
{ {
"start": 0, "start": 39,
"end": 71, "end": 41,
"type": "Text",
"raw": "\n\n",
"data": "\n\n"
},
{
"start": 41,
"end": 112,
"type": "EachBlock", "type": "EachBlock",
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"start": 7, "start": 48,
"end": 14, "end": 55,
"loc": { "loc": {
"start": { "start": {
"line": 1, "line": 5,
"column": 7 "column": 7
}, },
"end": { "end": {
"line": 1, "line": 5,
"column": 14 "column": 14
} }
}, },
@ -26,27 +33,27 @@
}, },
"children": [ "children": [
{ {
"start": 42, "start": 83,
"end": 63, "end": 104,
"type": "Element", "type": "Element",
"name": "p", "name": "p",
"attributes": [], "attributes": [],
"children": [ "children": [
{ {
"start": 45, "start": 86,
"end": 50, "end": 91,
"type": "MustacheTag", "type": "MustacheTag",
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"start": 46, "start": 87,
"end": 49, "end": 90,
"loc": { "loc": {
"start": { "start": {
"line": 2, "line": 6,
"column": 5 "column": 5
}, },
"end": { "end": {
"line": 2, "line": 6,
"column": 8 "column": 8
} }
}, },
@ -54,27 +61,27 @@
} }
}, },
{ {
"start": 50, "start": 91,
"end": 52, "end": 93,
"type": "Text", "type": "Text",
"raw": ": ", "raw": ": ",
"data": ": " "data": ": "
}, },
{ {
"start": 52, "start": 93,
"end": 59, "end": 100,
"type": "MustacheTag", "type": "MustacheTag",
"expression": { "expression": {
"type": "Identifier", "type": "Identifier",
"start": 53, "start": 94,
"end": 58, "end": 99,
"loc": { "loc": {
"start": { "start": {
"line": 2, "line": 6,
"column": 12 "column": 12
}, },
"end": { "end": {
"line": 2, "line": 6,
"column": 17 "column": 17
} }
}, },
@ -85,31 +92,177 @@
} }
], ],
"context": { "context": {
"start": 18,
"end": 39,
"type": "ArrayPattern", "type": "ArrayPattern",
"start": 59,
"end": 80,
"loc": {
"start": {
"line": 5,
"column": 19
},
"end": {
"line": 5,
"column": 40
}
},
"elements": [ "elements": [
{ {
"start": 19,
"end": 22,
"type": "Identifier", "type": "Identifier",
"start": 60,
"end": 63,
"loc": {
"start": {
"line": 5,
"column": 20
},
"end": {
"line": 5,
"column": 23
}
},
"name": "key" "name": "key"
}, },
{ {
"start": 24,
"end": 29,
"type": "Identifier", "type": "Identifier",
"start": 65,
"end": 70,
"loc": {
"start": {
"line": 5,
"column": 25
},
"end": {
"line": 5,
"column": 30
}
},
"name": "value" "name": "value"
}, },
{ {
"start": 31, "type": "RestElement",
"end": 38, "start": 72,
"type": "RestIdentifier", "end": 79,
"name": "rest" "loc": {
"start": {
"line": 5,
"column": 32
},
"end": {
"line": 5,
"column": 39
}
},
"argument": {
"type": "Identifier",
"start": 75,
"end": 79,
"loc": {
"start": {
"line": 5,
"column": 35
},
"end": {
"line": 5,
"column": 39
}
},
"name": "rest"
}
} }
] ]
} }
} }
] ]
},
"instance": {
"type": "Script",
"start": 0,
"end": 39,
"context": "default",
"content": {
"type": "Program",
"start": 8,
"end": 30,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 0
}
},
"body": [
{
"type": "ExportNamedDeclaration",
"start": 10,
"end": 29,
"loc": {
"start": {
"line": 2,
"column": 1
},
"end": {
"line": 2,
"column": 20
}
},
"declaration": {
"type": "VariableDeclaration",
"start": 17,
"end": 29,
"loc": {
"start": {
"line": 2,
"column": 8
},
"end": {
"line": 2,
"column": 20
}
},
"declarations": [
{
"type": "VariableDeclarator",
"start": 21,
"end": 28,
"loc": {
"start": {
"line": 2,
"column": 12
},
"end": {
"line": 2,
"column": 19
}
},
"id": {
"type": "Identifier",
"start": 21,
"end": 28,
"loc": {
"start": {
"line": 2,
"column": 12
},
"end": {
"line": 2,
"column": 19
}
},
"name": "animals"
},
"init": null
}
],
"kind": "let"
},
"specifiers": [],
"source": null
}
],
"sourceType": "module"
}
} }
} }

@ -116,6 +116,8 @@
"raw": "true" "raw": "true"
}, },
"value": { "value": {
"start": 97,
"end": 98,
"type": "Identifier", "type": "Identifier",
"name": "f" "name": "f"
}, },
@ -202,6 +204,8 @@
"raw": "true" "raw": "true"
}, },
"value": { "value": {
"start": 137,
"end": 138,
"type": "Identifier", "type": "Identifier",
"name": "f" "name": "f"
}, },

@ -0,0 +1,22 @@
export default {
props: {
animalEntries: [
{ animal: 'raccoon', class: 'mammal', species: 'P. lotor', kilogram: 25 },
{ animal: 'eagle', class: 'bird', kilogram: 5.4 }
]
},
html: `
<p class="mammal">raccoon - P. lotor - 25kg</p>
<p class="bird">eagle - unknown - 5.4kg</p>
`,
test({ assert, component, target }) {
component.animalEntries = [{ animal: 'cow', class: 'mammal', species: 'B. taurus' }];
assert.htmlEqual(target.innerHTML, `
<p class="mammal">cow - B. taurus - 50kg</p>
`);
},
};

@ -0,0 +1,7 @@
<script>
export let animalEntries;
</script>
{#each animalEntries as { animal, species = 'unknown', kilogram: weight = 50 , ...props } }
<p {...props}>{animal} - {species} - {weight}kg</p>
{/each}

@ -1,5 +1,5 @@
[{ [{
"code": "comma-after-rest", "code": "parse-error",
"message": "Comma is not permitted after the rest element", "message": "Comma is not permitted after the rest element",
"pos": 100, "pos": 100,
"start": { "start": {

@ -1,6 +1,6 @@
[{ [{
"code": "unexpected-reserved-word", "code": "parse-error",
"message": "'case' is a reserved word in JavaScript and cannot be used here", "message": "Unexpected keyword 'case'",
"start": { "start": {
"line": 1, "line": 1,
"column": 18, "column": 18,

@ -1,6 +1,6 @@
[{ [{
"code": "unexpected-reserved-word", "code": "parse-error",
"message": "'case' is a reserved word in JavaScript and cannot be used here", "message": "Unexpected token",
"start": { "start": {
"line": 1, "line": 1,
"column": 17, "column": 17,

Loading…
Cancel
Save