Skip to content

Commit 506ab39

Browse files
committed
destructuring
1 parent f5048fc commit 506ab39

File tree

17 files changed

+241
-86
lines changed

17 files changed

+241
-86
lines changed

src/compile/nodes/EachBlock.ts

+15-20
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import createDebuggingComment from '../../utils/createDebuggingComment';
66
import Expression from './shared/Expression';
77
import mapChildren from './shared/mapChildren';
88
import TemplateScope from './shared/TemplateScope';
9+
import unpackDestructuring from '../../utils/unpackDestructuring';
910

1011
export default class EachBlock extends Node {
1112
type: 'EachBlock';
@@ -18,7 +19,7 @@ export default class EachBlock extends Node {
1819
context: string;
1920
key: Expression;
2021
scope: TemplateScope;
21-
destructuredContexts: string[];
22+
contexts: Array<{ name: string, tail: string }>;
2223

2324
children: Node[];
2425
else?: ElseBlock;
@@ -27,7 +28,7 @@ export default class EachBlock extends Node {
2728
super(compiler, parent, scope, info);
2829

2930
this.expression = new Expression(compiler, this, scope, info.expression);
30-
this.context = info.context;
31+
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring
3132
this.index = info.index;
3233

3334
this.key = info.key
@@ -36,20 +37,19 @@ export default class EachBlock extends Node {
3637

3738
this.scope = scope.child();
3839

39-
this.scope.add(this.context, this.expression.dependencies);
40+
this.contexts = [];
41+
unpackDestructuring(this.contexts, info.context, '');
42+
43+
this.contexts.forEach(context => {
44+
this.scope.add(context.key.name, this.expression.dependencies);
45+
});
4046

4147
if (this.index) {
4248
// index can only change if this is a keyed each block
4349
const dependencies = this.key ? this.expression.dependencies : [];
4450
this.scope.add(this.index, dependencies);
4551
}
4652

47-
// TODO more general approach to destructuring
48-
this.destructuredContexts = info.destructuredContexts || [];
49-
this.destructuredContexts.forEach(name => {
50-
this.scope.add(name, this.expression.dependencies);
51-
});
52-
5353
this.children = mapChildren(compiler, this, this.scope, info.children);
5454

5555
this.else = info.else
@@ -90,17 +90,13 @@ export default class EachBlock extends Node {
9090
this.block.getUniqueName(this.index); // this prevents name collisions (#1254)
9191
}
9292

93-
this.contextProps = [
93+
this.contextProps = this.contexts.map(prop => `${prop.key.name}: list[i]${prop.tail}`);
94+
95+
// TODO only add these if necessary
96+
this.contextProps.push(
9497
`${listName}: list`,
95-
`${this.context}: list[i]`,
9698
`${indexName}: i`
97-
];
98-
99-
if (this.destructuredContexts) {
100-
for (let i = 0; i < this.destructuredContexts.length; i += 1) {
101-
this.contextProps.push(`${this.destructuredContexts[i]}: list[i][${i}]`);
102-
}
103-
}
99+
);
104100

105101
this.compiler.target.blocks.push(this.block);
106102
this.initChildren(this.block, stripWhitespace, nextSibling);
@@ -481,8 +477,7 @@ export default class EachBlock extends Node {
481477
const { compiler } = this;
482478
const { snippet } = this.expression;
483479

484-
const props = [`${this.context}: item`]
485-
.concat(this.destructuredContexts.map((name, i) => `${name}: item[${i}]`));
480+
const props = this.contexts.map(prop => `${prop.key.name}: item${prop.tail}`);
486481

487482
const getContext = this.index
488483
? `(item, i) => Object.assign({}, ctx, { ${props.join(', ')}, ${this.index}: i })`

src/parse/read/context.ts

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Parser } from '../index';
2+
3+
type Identifier = {
4+
start: number;
5+
end: number;
6+
type: 'Identifier';
7+
name: string;
8+
};
9+
10+
type Property = {
11+
start: number;
12+
end: number;
13+
type: 'Property';
14+
key: Identifier;
15+
value: Context;
16+
};
17+
18+
type Context = {
19+
start: number;
20+
end: number;
21+
type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern';
22+
name?: string;
23+
elements?: Context[];
24+
properties?: Property[];
25+
}
26+
27+
function errorOnAssignmentPattern(parser: Parser) {
28+
if (parser.eat('=')) {
29+
parser.error({
30+
code: 'invalid-assignment-pattern',
31+
message: 'Assignment patterns are not supported'
32+
}, parser.index - 1);
33+
}
34+
}
35+
36+
export default function readContext(parser: Parser) {
37+
const context: Context = {
38+
start: parser.index,
39+
end: null,
40+
type: null
41+
};
42+
43+
if (parser.eat('[')) {
44+
context.type = 'ArrayPattern';
45+
context.elements = [];
46+
47+
do {
48+
parser.allowWhitespace();
49+
context.elements.push(readContext(parser));
50+
parser.allowWhitespace();
51+
} while (parser.eat(','));
52+
53+
errorOnAssignmentPattern(parser);
54+
parser.eat(']', true);
55+
}
56+
57+
else if (parser.eat('{')) {
58+
context.type = 'ObjectPattern';
59+
context.properties = [];
60+
61+
do {
62+
parser.allowWhitespace();
63+
64+
const start = parser.index;
65+
const name = parser.readIdentifier();
66+
const key: Identifier = {
67+
start,
68+
end: parser.index,
69+
type: 'Identifier',
70+
name
71+
};
72+
parser.allowWhitespace();
73+
74+
const value = parser.eat(':')
75+
? readContext(parser)
76+
: key;
77+
78+
const property: Property = {
79+
start,
80+
end: value.end,
81+
type: 'Property',
82+
key,
83+
value
84+
};
85+
86+
context.properties.push(property);
87+
88+
parser.allowWhitespace();
89+
} while (parser.eat(','));
90+
91+
errorOnAssignmentPattern(parser);
92+
parser.eat('}', true);
93+
}
94+
95+
else {
96+
const name = parser.readIdentifier();
97+
if (name) {
98+
context.type = 'Identifier';
99+
context.end = parser.index;
100+
context.name = name;
101+
}
102+
103+
else {
104+
parser.error({
105+
code: 'invalid-context',
106+
message: 'Expected a name, array pattern or object pattern'
107+
});
108+
}
109+
110+
errorOnAssignmentPattern(parser);
111+
}
112+
113+
return context;
114+
}

src/parse/state/mustache.ts

+2-34
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import readContext from '../read/context';
12
import readExpression from '../read/expression';
23
import { whitespace } from '../../utils/patterns';
34
import { trimStart, trimEnd } from '../../utils/trim';
@@ -248,40 +249,7 @@ export default function mustache(parser: Parser) {
248249
parser.eat('as', true);
249250
parser.requireWhitespace();
250251

251-
if (parser.eat('[')) {
252-
parser.allowWhitespace();
253-
254-
block.destructuredContexts = [];
255-
256-
do {
257-
parser.allowWhitespace();
258-
259-
const destructuredContext = parser.readIdentifier();
260-
if (!destructuredContext) parser.error({
261-
code: `expected-name`,
262-
message: `Expected name`
263-
});
264-
265-
block.destructuredContexts.push(destructuredContext);
266-
parser.allowWhitespace();
267-
} while (parser.eat(','));
268-
269-
if (!block.destructuredContexts.length) parser.error({
270-
code: `expected-name`,
271-
message: `Expected name`
272-
});
273-
274-
block.context = block.destructuredContexts.join('_');
275-
276-
parser.allowWhitespace();
277-
parser.eat(']', true);
278-
} else {
279-
block.context = parser.readIdentifier();
280-
if (!block.context) parser.error({
281-
code: `expected-name`,
282-
message: `Expected name`
283-
});
284-
}
252+
block.context = readContext(parser);
285253

286254
parser.allowWhitespace();
287255

src/utils/unpackDestructuring.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export default function unpackDestructuring(
2+
contexts: Array<{ name: string, tail: string }>,
3+
node: Node,
4+
tail: string
5+
) {
6+
if (node.type === 'Identifier') {
7+
contexts.push({
8+
key: node,
9+
tail
10+
});
11+
} else if (node.type === 'ArrayPattern') {
12+
node.elements.forEach((element, i) => {
13+
unpackDestructuring(contexts, element, `${tail}[${i}]`);
14+
});
15+
} else if (node.type === 'ObjectPattern') {
16+
node.properties.forEach((property) => {
17+
unpackDestructuring(contexts, property.value, `${tail}.${property.key.name}`);
18+
});
19+
}
20+
}

src/validate/html/index.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import fuzzymatch from '../utils/fuzzymatch'
88
import flattenReference from '../../utils/flattenReference';
99
import { Validator } from '../index';
1010
import { Node } from '../../interfaces';
11+
import unpackDestructuring from '../../utils/unpackDestructuring';
1112

1213
function isEmptyBlock(node: Node) {
1314
if (!/Block$/.test(node.type) || !node.children) return false;
@@ -60,19 +61,17 @@ export default function validateHtml(validator: Validator, html: Node) {
6061
}
6162

6263
else if (node.type === 'EachBlock') {
63-
if (validator.helpers.has(node.context)) {
64-
let c: number = node.expression.end;
65-
66-
// find start of context
67-
while (/\s/.test(validator.source[c])) c += 1;
68-
c += 2;
69-
while (/\s/.test(validator.source[c])) c += 1;
70-
71-
validator.warn({ start: c, end: c + node.context.length }, {
72-
code: `each-context-clash`,
73-
message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`
74-
});
75-
}
64+
const contexts = [];
65+
unpackDestructuring(contexts, node.context, '');
66+
67+
contexts.forEach(prop => {
68+
if (validator.helpers.has(prop.key.name)) {
69+
validator.warn(prop.key, {
70+
code: `each-context-clash`,
71+
message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`
72+
});
73+
}
74+
});
7675
}
7776

7877
if (validator.options.dev && isEmptyBlock(node)) {

test/js/samples/deconflict-builtins/expected-bundle.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ function create_each_block(component, ctx) {
250250

251251
function get_each_context(ctx, list, i) {
252252
return assign(assign({}, ctx), {
253-
each_value: list,
254253
node: list[i],
254+
each_value: list,
255255
node_index: i
256256
});
257257
}

test/js/samples/deconflict-builtins/expected.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ function create_each_block(component, ctx) {
9898

9999
function get_each_context(ctx, list, i) {
100100
return assign(assign({}, ctx), {
101-
each_value: list,
102101
node: list[i],
102+
each_value: list,
103103
node_index: i
104104
});
105105
}

test/js/samples/each-block-changed-check/expected-bundle.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ function create_each_block(component, ctx) {
297297

298298
function get_each_context(ctx, list, i) {
299299
return assign(assign({}, ctx), {
300-
each_value: list,
301300
comment: list[i],
301+
each_value: list,
302302
i: i
303303
});
304304
}

test/js/samples/each-block-changed-check/expected.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ function create_each_block(component, ctx) {
143143

144144
function get_each_context(ctx, list, i) {
145145
return assign(assign({}, ctx), {
146-
each_value: list,
147146
comment: list[i],
147+
each_value: list,
148148
i: i
149149
});
150150
}

test/parser/samples/each-block-destructured/output.json

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
"hash": "gtdm5e",
32
"html": {
43
"start": 0,
54
"end": 62,
@@ -54,11 +53,25 @@
5453
]
5554
}
5655
],
57-
"destructuredContexts": [
58-
"key",
59-
"value"
60-
],
61-
"context": "key_value"
56+
"context": {
57+
"start": 18,
58+
"end": null,
59+
"type": "ArrayPattern",
60+
"elements": [
61+
{
62+
"start": 19,
63+
"end": 22,
64+
"type": "Identifier",
65+
"name": "key"
66+
},
67+
{
68+
"start": 24,
69+
"end": 29,
70+
"type": "Identifier",
71+
"name": "value"
72+
}
73+
]
74+
}
6275
}
6376
]
6477
},

0 commit comments

Comments
 (0)