[feat] support destructured declaration of props (#6578)

pull/6593/head
Tan Li Hau 3 years ago committed by GitHub
parent 520b4751dc
commit b720f0e620
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,10 +24,10 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object';
import Slot from './nodes/Slot';
import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal, ExportDefaultDeclaration, ExportAllDeclaration } from 'estree';
import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, ExpressionStatement, AssignmentExpression, Literal, Property, RestElement, ExportDefaultDeclaration, ExportAllDeclaration } from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red';
import { print, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
import { apply_preprocessor_sourcemap } from '../utils/mapped_code';
import Element from './nodes/Element';
@ -943,7 +943,7 @@ export default class Component {
let scope = instance_scope;
walk(this.ast.instance.content, {
enter(node: Node, parent, key, index) {
enter(node: Node) {
if (/Function/.test(node.type)) {
return this.skip();
}
@ -952,75 +952,130 @@ export default class Component {
scope = map.get(node);
}
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
return this.replace(node.declaration);
}
if (node.type === 'VariableDeclaration') {
// NOTE: `var` does not follow block scoping
if (node.kind === 'var' || scope === instance_scope) {
node.declarations.forEach(declarator => {
if (declarator.id.type !== 'Identifier') {
const inserts = [];
extract_names(declarator.id).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable.export_name) {
// TODO is this still true post-#3539?
return component.error(declarator as any, compiler_errors.destructured_prop);
const inserts = [];
const props = [];
function add_new_props(exported, local, default_value) {
props.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: exported,
value: default_value
? {
type: 'AssignmentPattern',
left: local,
right: default_value
}
: local
});
}
// transform
// ```
// export let { x, y = 123 } = OBJ, z = 456
// ```
// into
// ```
// let { x: x$, y: y$ = 123 } = OBJ;
// let { x = x$, y = y$, z = 456 } = $$props;
// ```
for (let index = 0; index < node.declarations.length; index++) {
const declarator = node.declarations[index];
if (declarator.id.type !== 'Identifier') {
function get_new_name(local) {
const variable = component.var_lookup.get(local.name);
if (variable.subscribable) {
inserts.push(get_insert(variable));
}
});
if (inserts.length) {
parent[key].splice(index + 1, 0, ...inserts);
if (variable.export_name && variable.writable) {
const alias_name = component.get_unique_name(local.name);
add_new_props({ type: 'Identifier', name: variable.export_name }, local, alias_name);
return alias_name;
}
return local;
}
return;
}
const { name } = declarator.id;
const variable = component.var_lookup.get(name);
function rename_identifiers(param: Node) {
switch (param.type) {
case 'ObjectPattern': {
const handle_prop = (prop: Property | RestElement) => {
if (prop.type === 'RestElement') {
rename_identifiers(prop);
} else if (prop.value.type === 'Identifier') {
prop.value = get_new_name(prop.value);
} else {
rename_identifiers(prop.value);
}
};
param.properties.forEach(handle_prop);
break;
}
case 'ArrayPattern': {
const handle_element = (element: Node, index: number, array: Node[]) => {
if (element) {
if (element.type === 'Identifier') {
array[index] = get_new_name(element);
} else {
rename_identifiers(element);
}
}
};
param.elements.forEach(handle_element);
break;
}
case 'RestElement':
param.argument = get_new_name(param.argument);
break;
case 'AssignmentPattern':
param.left = get_new_name(param.left);
break;
}
}
if (variable.export_name && variable.writable) {
declarator.id = {
type: 'ObjectPattern',
properties: [{
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: { type: 'Identifier', name: variable.export_name },
value: declarator.init
? {
type: 'AssignmentPattern',
left: declarator.id,
right: declarator.init
}
: declarator.id
}]
};
declarator.init = x`$$props`;
rename_identifiers(declarator.id);
} else {
const { name } = declarator.id;
const variable = component.var_lookup.get(name);
const is_props = variable.export_name && variable.writable;
if (is_props) {
add_new_props({ type: 'Identifier', name: variable.export_name }, declarator.id, declarator.init);
node.declarations.splice(index--, 1);
}
if (variable.subscribable && (is_props || declarator.init)) {
inserts.push(get_insert(variable));
}
}
}
if (variable.subscribable && declarator.init) {
const insert = get_insert(variable);
parent[key].splice(index + 1, 0, ...insert);
}
});
this.replace(b`
${node.declarations.length ? node : null}
${ props.length > 0 && b`let { ${ props } } = $$props;`}
${inserts}
` as any);
return this.skip();
}
}
},
leave(node: Node, parent, _key, index) {
leave(node: Node) {
if (map.has(node)) {
scope = scope.parent;
}
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
(parent as Program).body[index] = node.declaration;
}
}
});
}

@ -186,10 +186,6 @@ export default {
code: 'illegal-global',
message: `${name} is an illegal variable name`
}),
destructured_prop: {
code: 'destructured-prop',
message: 'Cannot declare props in destructured declaration'
},
cyclical_reactive_declaration: (cycle: string[]) => ({
code: 'cyclical-reactive-declaration',
message: `Cyclical dependency detected: ${cycle.join(' → ')}`

@ -0,0 +1,24 @@
<script>
import { writable } from 'svelte/store';
const THING = { a: 1, b: { c: 2, d: [3, 4, writable(5)] }, e: [6], h: 8 };
const default_g = 9;
export let { a, b: { c, d: [d_one,,d_three], f }, e: [e_one], g = default_g } = THING;
export const { a: A, b: { c: C } } = THING;
</script>
<div>
a: {a},
b: {typeof b},
c: {c},
d_one: {d_one},
d_three: {$d_three},
f: {f},
g: {g},
e: {typeof e},
e_one: {e_one},
A: {A},
C: {C}
</div>
<div>{JSON.stringify(THING)}</div>

@ -0,0 +1,9 @@
export default {
html: `
<div>a: 1, b: undefined, c: 2, d_one: 3, d_three: 5, f: undefined, g: 9, e: undefined, e_one: 6, A: 1, C: 2</div>
<div>{"a":1,"b":{"c":2,"d":[3,4,{}]},"e":[6],"h":8}</div>
<br>
<div>a: a, b: undefined, c: 2, d_one: d_one, d_three: 5, f: f, g: g, e: undefined, e_one: 6, A: 1, C: 2</div>
<div>{"a":1,"b":{"c":2,"d":[3,4,{}]},"e":[6],"h":8}</div>
`
};

@ -0,0 +1,7 @@
<script>
import A from './A.svelte';
</script>
<A />
<br />
<A a="a" d_one="d_one" list_one="list_one" f="f" list_two_b="list_two_b" g="g" A="A" C="C" />

@ -0,0 +1,21 @@
<script>
import { writable } from 'svelte/store';
let default_b = 5;
const LIST = [1, { a: 2 }, [3, writable(4)]];
export const [x, { a: list_two_a, b: list_two_b = default_b }, [, y]] = LIST;
export let [m, { a: n, b: o = default_b }, [p, q]] = LIST;
</script>
<div>
x: {x},
list_two_a: {list_two_a},
list_two_b: {list_two_b},
y: {$y},
m: {m},
n: {n},
o: {o},
p: {p},
q: {$q}
</div>
<div>{JSON.stringify(LIST)}</div>

@ -0,0 +1,19 @@
export default {
html: `
<div>x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: 1, n: 2, o: 5, p: 3, q: 4</div>
<div>[1,{"a":2},[3,{}]]</div>
<br><div>x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: m, n: n, o: o, p: p, q: q</div>
<div>[1,{"a":2},[3,{}]]</div>
`,
async test({ component, assert, target }) {
await component.update();
assert.htmlEqual(target.innerHTML, `
<div>x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: 1, n: 2, o: 5, p: 3, q: 4</div>
<div>[1,{"a":2},[3,{}]]</div>
<br><div>x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: MM, n: NN, o: OO, p: PP, q: QQ</div>
<div>[1,{"a":2},[3,{}]]</div>
`);
}
};

@ -0,0 +1,30 @@
<script>
import A from './A.svelte';
import { writable } from 'svelte/store';
let x = 'x',
list_two_a = 'list_two_a',
list_two_b = 'list_two_b',
y = writable('y'),
m = 'm',
n = 'n',
o = 'o',
p = 'p',
q = writable('q');
export function update() {
x = 'XX';
list_two_a = 'LIST_TWO_A';
list_two_b = 'LIST_TWO_B';
y = writable('YY');
m = 'MM';
n = 'NN';
o = 'OO';
p = 'PP';
q = writable('QQ');
}
</script>
<A />
<br />
<A {x} {list_two_a} {list_two_b} {y} {m} {n} {o} {p} {q} />

@ -0,0 +1,10 @@
<script>
import { writable } from 'svelte/store';
const { i, j, k } = { i: 9, j: 10, k: writable(11) }, l = 12, m = 13, n = writable(14);
let { a, b, c } = { a: 9, b: 10, c: writable(11) }, d = 12, e = 13, f = writable(14);
export { i, k, l, n, a, c, d, f };
</script>
<div>i: {i}, j: {j}, k: {$k}, l: {l}, m: {m}, n: {$n}, a: {a}, b: {b}, c: {$c}, d: {d}, e: {e}, f: {$f}</div>

@ -0,0 +1,15 @@
export default {
html: `
<div>i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: 9, b: 10, c: 11, d: 12, e: 13, f: 14</div>
<br>
<div>i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: a, b: 10, c: c, d: d, e: 13, f: f</div>
`,
async test({ component, target, assert }) {
await component.update();
assert.htmlEqual(target.innerHTML, `
<div>i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: 9, b: 10, c: 11, d: 12, e: 13, f: 14</div>
<br>
<div>i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: aa, b: 10, c: cc, d: dd, e: 13, f: ff</div>
`);
}
};

@ -0,0 +1,29 @@
<script>
import A from './A.svelte';
import { writable } from 'svelte/store';
let i = 'i',
k = writable('k'),
l = 'l',
n = writable('n'),
a = 'a',
c = writable('c'),
d = 'd',
f = writable('f');
export function update() {
i = 'ii';
k = writable('kk');
l = 'll';
n = writable('nn');
a = 'aa';
c = writable('cc');
d = 'dd';
f = writable('ff');
}
</script>
<A />
<br />
<A {i} {k} {l} {n} {a} {c} {d} {f} />
Loading…
Cancel
Save