mirror of https://github.com/sveltejs/svelte
dry {#each}/{#await} destructuring (#4596)
parent
c743e72a1e
commit
37cc5888f8
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
key: Identifier;
|
|
||||||
value: Context;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Context {
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern' | 'RestIdentifier';
|
|
||||||
name?: string;
|
|
||||||
elements?: Context[];
|
|
||||||
properties?: Property[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function error_on_assignment_pattern(parser: Parser) {
|
|
||||||
if (parser.eat('=')) {
|
|
||||||
parser.error({
|
|
||||||
code: 'invalid-assignment-pattern',
|
|
||||||
message: 'Assignment patterns are not supported'
|
|
||||||
}, 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('[')) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 start = parser.index;
|
||||||
const name = parser.read_identifier();
|
let i = parser.index;
|
||||||
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;
|
const code = full_char_code_at(parser.template, i);
|
||||||
}
|
if (isIdentifierStart(code, true)) {
|
||||||
|
return {
|
||||||
// TODO: DRY this out somehow
|
type: "Identifier",
|
||||||
// We don't know whether we want to allow reserved words until we see whether there's a ':' after it
|
name: parser.read_identifier(),
|
||||||
// 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,
|
start,
|
||||||
end: parser.index,
|
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 = {
|
if (!is_bracket_open(code)) {
|
||||||
start,
|
parser.error({
|
||||||
end: value.end,
|
code: "unexpected-token",
|
||||||
type: 'Property',
|
message: "Expected identifier or destructure pattern"
|
||||||
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 bracket_stack = [code];
|
||||||
const name = parser.read_identifier();
|
i += code <= 0xffff ? 1 : 2;
|
||||||
if (name) {
|
|
||||||
context.type = 'RestIdentifier';
|
|
||||||
context.end = parser.index;
|
|
||||||
context.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
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({
|
parser.error({
|
||||||
code: 'invalid-context',
|
code: "unexpected-token",
|
||||||
message: 'Expected a rest pattern'
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
|
||||||
const name = parser.read_identifier();
|
|
||||||
if (name) {
|
|
||||||
context.type = 'Identifier';
|
|
||||||
context.end = parser.index;
|
|
||||||
context.name = name;
|
|
||||||
}
|
}
|
||||||
|
i += code <= 0xffff ? 1 : 2;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
Loading…
Reference in new issue