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
## 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
* 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 CatchBlock from './CatchBlock';
import Expression from './shared/Expression';
import { Pattern } from 'estree';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
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 {
type: 'AwaitBlock';
expression: Expression;
value: DestructurePattern;
error: DestructurePattern;
then_contexts: Context[];
catch_contexts: Context[];
then_node: ESTreeNode | null;
catch_node: ESTreeNode | null;
pending: PendingBlock;
then: ThenBlock;
@ -24,24 +28,21 @@ export default class AwaitBlock extends Node {
this.expression = new Expression(component, this, scope, info.expression);
this.value = info.value && new DestructurePattern(info.value);
this.error = info.error && new DestructurePattern(info.error);
this.then_node = info.value;
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.then = new ThenBlock(component, this, scope, info.then);
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);
this.scope = scope.child();
if (parent.error) {
parent.error.expressions.forEach(expression => {
this.scope.add(expression, parent.expression.dependencies, this);
if (parent.catch_node) {
parent.catch_contexts.forEach(context => {
this.scope.add(context.key.name, parent.expression.dependencies, this);
});
}
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 AbstractBlock from './shared/AbstractBlock';
import Element from './Element';
import { x } from 'code-red';
import { Node, Identifier, RestElement } 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);
}
});
}
}
import { Context, unpack_destructuring } from './shared/Context';
import { Node } from 'estree';
export default class EachBlock extends AbstractBlock {
type: 'EachBlock';

@ -13,9 +13,9 @@ export default class ThenBlock extends AbstractBlock {
super(component, parent, scope, info);
this.scope = scope.child();
if (parent.value) {
parent.value.expressions.forEach(expression => {
this.scope.add(expression, parent.expression.dependencies, this);
if (parent.then_node) {
parent.then_contexts.forEach(context => {
this.scope.add(context.key.name, parent.expression.dependencies, this);
});
}
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 ThenBlock from '../../nodes/ThenBlock';
import CatchBlock from '../../nodes/CatchBlock';
import { Identifier } from 'estree';
import traverse_destructure_pattern from '../../utils/traverse_destructure_pattern';
import { Context } from '../../nodes/shared/Context';
import { Identifier, Literal, Node } from 'estree';
type Status = 'pending' | 'then' | 'catch';
class AwaitBlockBranch extends Wrapper {
parent: AwaitBlockWrapper;
node: PendingBlock | ThenBlock | CatchBlock;
block: Block;
fragment: FragmentWrapper;
is_dynamic: boolean;
var = null;
status: Status;
value: string;
value_index: Literal;
value_contexts: Context[];
is_destructured: boolean;
constructor(
status: string,
status: Status,
renderer: Renderer,
block: Block,
parent: Wrapper,
node: AwaitBlock,
parent: AwaitBlockWrapper,
node: PendingBlock | ThenBlock | CatchBlock,
strip_whitespace: boolean,
next_sibling: Wrapper
) {
super(renderer, block, parent, node);
this.status = status;
this.block = block.child({
comment: create_debugging_comment(node, this.renderer.component),
@ -36,6 +46,8 @@ class AwaitBlockBranch extends Wrapper {
type: status
});
this.add_context(parent.node[status + '_node'], parent.node[status + '_contexts']);
this.fragment = new FragmentWrapper(
renderer,
this.block,
@ -48,20 +60,43 @@ class AwaitBlockBranch extends Wrapper {
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) {
this.fragment.render(block, parent_node, parent_nodes);
}
render_destructure(block: Block, value, node, index) {
if (value && node.pattern.type !== 'Identifier') {
traverse_destructure_pattern(node.pattern, (node, parent, index) => {
parent[index] = x`#ctx[${block.renderer.context_lookup.get(node.name).index}]`;
});
if (this.is_destructured) {
this.render_destructure();
}
}
this.block.chunks.declarations.push(b`(${node.pattern} = #ctx[${index}])`);
if (this.block.has_update_method) {
this.block.chunks.update.push(b`(${node.pattern} = #ctx[${index}])`);
render_destructure() {
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}]`)};`);
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;
catch: AwaitBlockBranch;
value: string;
error: string;
var: Identifier = { type: 'Identifier', name: 'await_block' };
constructor(
@ -92,26 +124,12 @@ export default class AwaitBlockWrapper extends Wrapper {
this.not_static_content();
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 has_intros = false;
let has_outros = false;
['pending', 'then', 'catch'].forEach(status => {
['pending', 'then', 'catch'].forEach((status: Status) => {
const child = this.node[status];
const branch = new AwaitBlockBranch(
@ -166,9 +184,6 @@ export default class AwaitBlockWrapper extends Wrapper {
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`{
ctx: #ctx,
current: null,
@ -176,8 +191,8 @@ export default class AwaitBlockWrapper extends Wrapper {
pending: ${this.pending.block.name},
then: ${this.then.block.name},
catch: ${this.catch.block.name},
value: ${value_index},
error: ${error_index},
value: ${this.then.value_index},
error: ${this.catch.value_index},
blocks: ${this.pending.block.has_outro_method && x`[,,,]`}
}`;
@ -232,7 +247,7 @@ export default class AwaitBlockWrapper extends Wrapper {
} else {
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);
}
`);
@ -246,7 +261,7 @@ export default class AwaitBlockWrapper extends Wrapper {
block.chunks.update.push(b`
{
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);
}
`);
@ -271,7 +286,5 @@ export default class AwaitBlockWrapper extends Wrapper {
[this.pending, this.then, this.catch].forEach(branch => {
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`
function(__value) {
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})
`);
}

@ -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 { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces';
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);
@ -173,51 +170,6 @@ export class Parser {
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) {
if (this.index >= this.template.length)
this.error({

@ -1,202 +1,82 @@
import { Parser } from '../index';
import { reserved } from '../../utils/names';
interface Identifier {
start: number;
end: number;
type: 'Identifier';
name: string;
}
interface Property {
start: number;
end: number;
type: 'Property';
kind: 'init' | 'rest';
shorthand: boolean;
key: Identifier;
value: Context;
}
interface Context {
start: number;
end: number;
type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern' | 'RestIdentifier';
name?: string;
elements?: Context[];
properties?: Property[];
}
import { Parser } from "../index";
import { isIdentifierStart } from "acorn";
import full_char_code_at from "../../utils/full_char_code_at";
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";
export default function read_context(
parser: Parser
): Pattern & { start: number; end: number } {
const start = parser.index;
let i = parser.index;
const code = full_char_code_at(parser.template, i);
if (isIdentifierStart(code, true)) {
return {
type: "Identifier",
name: parser.read_identifier(),
start,
end: parser.index
};
}
function error_on_assignment_pattern(parser: Parser) {
if (parser.eat('=')) {
if (!is_bracket_open(code)) {
parser.error({
code: 'invalid-assignment-pattern',
message: 'Assignment patterns are not supported'
}, parser.index - 1);
code: "unexpected-token",
message: "Expected identifier or destructure pattern"
});
}
}
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('[')) {
context.type = 'ArrayPattern';
context.elements = [];
do {
parser.allow_whitespace();
const lastContext = context.elements[context.elements.length - 1];
if (lastContext && lastContext.type === 'RestIdentifier') {
error_on_rest_pattern_not_last(parser);
const bracket_stack = [code];
i += code <= 0xffff ? 1 : 2;
while (i < parser.template.length) {
const code = full_char_code_at(parser.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)) {
parser.error({
code: "unexpected-token",
message: `Expected ${String.fromCharCode(
get_bracket_close(bracket_stack[bracket_stack.length - 1])
)}`
});
}
if (parser.template[parser.index] === ',') {
context.elements.push(null);
} 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);
}
bracket_stack.pop();
if (bracket_stack.length === 0) {
i += code <= 0xffff ? 1 : 2;
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 {
const name = parser.read_identifier();
if (name) {
context.type = 'Identifier';
context.end = parser.index;
context.name = name;
}
else {
parser.error({
code: 'invalid-context',
message: 'Expected a name, array pattern or object pattern'
});
}
error_on_assignment_pattern(parser);
parser.index = i;
const pattern_string = parser.template.slice(start, i);
try {
// the length of the `space_with_newline` has to be start - 1
// 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`
// to achieve that, we remove the 1st space encountered,
// so it will not affect the `column` of the node
let space_with_newline = parser.template.slice(0, start).replace(/[^\n]/g, ' ');
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(
`${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('}')) {
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.eat('}', true);
}
@ -305,14 +305,14 @@ export default function mustache(parser: Parser) {
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) {
parser.require_whitespace();
block.value = parser.read_destructure_pattern();
block.value = read_context(parser);
parser.allow_whitespace();
}
const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
if (await_block_catch_shorthand) {
parser.require_whitespace();
block.error = parser.read_destructure_pattern();
block.error = read_context(parser);
parser.allow_whitespace();
}

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

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

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

@ -1,24 +1,31 @@
{
"html": {
"start": 0,
"end": 71,
"start": 41,
"end": 112,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 71,
"start": 39,
"end": 41,
"type": "Text",
"raw": "\n\n",
"data": "\n\n"
},
{
"start": 41,
"end": 112,
"type": "EachBlock",
"expression": {
"type": "Identifier",
"start": 7,
"end": 14,
"start": 48,
"end": 55,
"loc": {
"start": {
"line": 1,
"line": 5,
"column": 7
},
"end": {
"line": 1,
"line": 5,
"column": 14
}
},
@ -26,27 +33,27 @@
},
"children": [
{
"start": 42,
"end": 63,
"start": 83,
"end": 104,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 45,
"end": 50,
"start": 86,
"end": 91,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 46,
"end": 49,
"start": 87,
"end": 90,
"loc": {
"start": {
"line": 2,
"line": 6,
"column": 5
},
"end": {
"line": 2,
"line": 6,
"column": 8
}
},
@ -54,27 +61,27 @@
}
},
{
"start": 50,
"end": 52,
"start": 91,
"end": 93,
"type": "Text",
"raw": ": ",
"data": ": "
},
{
"start": 52,
"end": 59,
"start": 93,
"end": 100,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 53,
"end": 58,
"start": 94,
"end": 99,
"loc": {
"start": {
"line": 2,
"line": 6,
"column": 12
},
"end": {
"line": 2,
"line": 6,
"column": 17
}
},
@ -85,31 +92,177 @@
}
],
"context": {
"start": 18,
"end": 39,
"type": "ArrayPattern",
"start": 59,
"end": 80,
"loc": {
"start": {
"line": 5,
"column": 19
},
"end": {
"line": 5,
"column": 40
}
},
"elements": [
{
"start": 19,
"end": 22,
"type": "Identifier",
"start": 60,
"end": 63,
"loc": {
"start": {
"line": 5,
"column": 20
},
"end": {
"line": 5,
"column": 23
}
},
"name": "key"
},
{
"start": 24,
"end": 29,
"type": "Identifier",
"start": 65,
"end": 70,
"loc": {
"start": {
"line": 5,
"column": 25
},
"end": {
"line": 5,
"column": 30
}
},
"name": "value"
},
{
"start": 31,
"end": 38,
"type": "RestIdentifier",
"name": "rest"
"type": "RestElement",
"start": 72,
"end": 79,
"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"
},
"value": {
"start": 97,
"end": 98,
"type": "Identifier",
"name": "f"
},
@ -202,6 +204,8 @@
"raw": "true"
},
"value": {
"start": 137,
"end": 138,
"type": "Identifier",
"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",
"pos": 100,
"start": {

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

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

Loading…
Cancel
Save