From 9cf08e4597853e0fae1a1387ae5eab99a7b4d8c0 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 5 Apr 2020 17:51:25 +0800 Subject: [PATCH] support destructure with default --- src/compiler/compile/nodes/shared/Context.ts | 14 +- src/compiler/parse/read/context.ts | 247 ++++-------------- .../await-then-destruct-default/_config.js | 1 - .../_config.js | 22 ++ .../main.svelte | 7 + 5 files changed, 87 insertions(+), 204 deletions(-) create mode 100644 test/runtime/samples/each-block-destructured-default/_config.js create mode 100644 test/runtime/samples/each-block-destructured-default/main.svelte diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts index 9f4df533ae..797405b0fe 100644 --- a/src/compiler/compile/nodes/shared/Context.ts +++ b/src/compiler/compile/nodes/shared/Context.ts @@ -22,8 +22,10 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier: }); } else if (node.type === 'ArrayPattern') { node.elements.forEach((element, i) => { - if (element && (element as any).type === 'RestElement') { + 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); } @@ -41,9 +43,15 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier: node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node ); } else { - used_properties.push(x`"${(property.key as Identifier).name}"`); + const key = property.key as Identifier; + const value = property.value; - unpack_destructuring(contexts, property.value, node => x`${modifier(node)}.${(property.key as Identifier).name}` as Node); + 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); + } } }); } diff --git a/src/compiler/parse/read/context.ts b/src/compiler/parse/read/context.ts index 1a641ba927..f651135691 100644 --- a/src/compiler/parse/read/context.ts +++ b/src/compiler/parse/read/context.ts @@ -1,212 +1,59 @@ -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 RestElement { - start: number; - end: number; - type: 'RestElement'; - argument: Identifier; -} - -interface Context { - start: number; - end: number; - type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern' | 'RestElement'; - name?: string; - argument?: Context; - elements?: Context[]; - properties?: Array; -} +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 { + 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() }; + } -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 === 'RestElement') { - 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(); + 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]) + )}` + }); } - } 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: RestElement = { - start, - end: parser.index, - type: 'RestElement', - argument: 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 = 'RestElement'; - context.end = parser.index; - context.argument = { - type: 'Identifier', - start: context.start + 3, - end: parser.index, - 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; - return context; + const pattern_string = parser.template.slice(start, i); + return (parse_expression_at(`${' '.repeat(start - 1)}(${pattern_string} = 1)`, start - 1) as any) + .left as Pattern; } diff --git a/test/runtime/samples/await-then-destruct-default/_config.js b/test/runtime/samples/await-then-destruct-default/_config.js index 08b8240974..d0e5a49f28 100644 --- a/test/runtime/samples/await-then-destruct-default/_config.js +++ b/test/runtime/samples/await-then-destruct-default/_config.js @@ -1,5 +1,4 @@ export default { - skip: true, async test({ assert, component, target }) { await Promise.resolve(); diff --git a/test/runtime/samples/each-block-destructured-default/_config.js b/test/runtime/samples/each-block-destructured-default/_config.js new file mode 100644 index 0000000000..133fd68532 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-default/_config.js @@ -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: ` +

raccoon - P. lotor - 25kg

+

eagle - unknown - 5.4kg

+ `, + + + + test({ assert, component, target }) { + component.animalEntries = [{ animal: 'cow', class: 'mammal', species: '‎B. taurus' }]; + assert.htmlEqual(target.innerHTML, ` +

cow - ‎B. taurus - 50kg

+ `); + }, +}; diff --git a/test/runtime/samples/each-block-destructured-default/main.svelte b/test/runtime/samples/each-block-destructured-default/main.svelte new file mode 100644 index 0000000000..a91b45299e --- /dev/null +++ b/test/runtime/samples/each-block-destructured-default/main.svelte @@ -0,0 +1,7 @@ + + +{#each animalEntries as { animal, species = 'unknown', kilogram: weight = 50 , ...props } } +

{animal} - {species} - {weight}kg

+{/each}