destructuring

pull/1385/head
Rich Harris 7 years ago
parent f5048fcf10
commit 506ab3952e

@ -6,6 +6,7 @@ import createDebuggingComment from '../../utils/createDebuggingComment';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren'; import mapChildren from './shared/mapChildren';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import unpackDestructuring from '../../utils/unpackDestructuring';
export default class EachBlock extends Node { export default class EachBlock extends Node {
type: 'EachBlock'; type: 'EachBlock';
@ -18,7 +19,7 @@ export default class EachBlock extends Node {
context: string; context: string;
key: Expression; key: Expression;
scope: TemplateScope; scope: TemplateScope;
destructuredContexts: string[]; contexts: Array<{ name: string, tail: string }>;
children: Node[]; children: Node[];
else?: ElseBlock; else?: ElseBlock;
@ -27,7 +28,7 @@ export default class EachBlock extends Node {
super(compiler, parent, scope, info); super(compiler, parent, scope, info);
this.expression = new Expression(compiler, this, scope, info.expression); this.expression = new Expression(compiler, this, scope, info.expression);
this.context = info.context; this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring
this.index = info.index; this.index = info.index;
this.key = info.key this.key = info.key
@ -36,7 +37,12 @@ export default class EachBlock extends Node {
this.scope = scope.child(); this.scope = scope.child();
this.scope.add(this.context, this.expression.dependencies); this.contexts = [];
unpackDestructuring(this.contexts, info.context, '');
this.contexts.forEach(context => {
this.scope.add(context.key.name, this.expression.dependencies);
});
if (this.index) { if (this.index) {
// index can only change if this is a keyed each block // index can only change if this is a keyed each block
@ -44,12 +50,6 @@ export default class EachBlock extends Node {
this.scope.add(this.index, dependencies); this.scope.add(this.index, dependencies);
} }
// TODO more general approach to destructuring
this.destructuredContexts = info.destructuredContexts || [];
this.destructuredContexts.forEach(name => {
this.scope.add(name, this.expression.dependencies);
});
this.children = mapChildren(compiler, this, this.scope, info.children); this.children = mapChildren(compiler, this, this.scope, info.children);
this.else = info.else this.else = info.else
@ -90,17 +90,13 @@ export default class EachBlock extends Node {
this.block.getUniqueName(this.index); // this prevents name collisions (#1254) this.block.getUniqueName(this.index); // this prevents name collisions (#1254)
} }
this.contextProps = [ this.contextProps = this.contexts.map(prop => `${prop.key.name}: list[i]${prop.tail}`);
// TODO only add these if necessary
this.contextProps.push(
`${listName}: list`, `${listName}: list`,
`${this.context}: list[i]`,
`${indexName}: i` `${indexName}: i`
]; );
if (this.destructuredContexts) {
for (let i = 0; i < this.destructuredContexts.length; i += 1) {
this.contextProps.push(`${this.destructuredContexts[i]}: list[i][${i}]`);
}
}
this.compiler.target.blocks.push(this.block); this.compiler.target.blocks.push(this.block);
this.initChildren(this.block, stripWhitespace, nextSibling); this.initChildren(this.block, stripWhitespace, nextSibling);
@ -481,8 +477,7 @@ export default class EachBlock extends Node {
const { compiler } = this; const { compiler } = this;
const { snippet } = this.expression; const { snippet } = this.expression;
const props = [`${this.context}: item`] const props = this.contexts.map(prop => `${prop.key.name}: item${prop.tail}`);
.concat(this.destructuredContexts.map((name, i) => `${name}: item[${i}]`));
const getContext = this.index const getContext = this.index
? `(item, i) => Object.assign({}, ctx, { ${props.join(', ')}, ${this.index}: i })` ? `(item, i) => Object.assign({}, ctx, { ${props.join(', ')}, ${this.index}: i })`

@ -0,0 +1,114 @@
import { Parser } from '../index';
type Identifier = {
start: number;
end: number;
type: 'Identifier';
name: string;
};
type Property = {
start: number;
end: number;
type: 'Property';
key: Identifier;
value: Context;
};
type Context = {
start: number;
end: number;
type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern';
name?: string;
elements?: Context[];
properties?: Property[];
}
function errorOnAssignmentPattern(parser: Parser) {
if (parser.eat('=')) {
parser.error({
code: 'invalid-assignment-pattern',
message: 'Assignment patterns are not supported'
}, parser.index - 1);
}
}
export default function readContext(parser: Parser) {
const context: Context = {
start: parser.index,
end: null,
type: null
};
if (parser.eat('[')) {
context.type = 'ArrayPattern';
context.elements = [];
do {
parser.allowWhitespace();
context.elements.push(readContext(parser));
parser.allowWhitespace();
} while (parser.eat(','));
errorOnAssignmentPattern(parser);
parser.eat(']', true);
}
else if (parser.eat('{')) {
context.type = 'ObjectPattern';
context.properties = [];
do {
parser.allowWhitespace();
const start = parser.index;
const name = parser.readIdentifier();
const key: Identifier = {
start,
end: parser.index,
type: 'Identifier',
name
};
parser.allowWhitespace();
const value = parser.eat(':')
? readContext(parser)
: key;
const property: Property = {
start,
end: value.end,
type: 'Property',
key,
value
};
context.properties.push(property);
parser.allowWhitespace();
} while (parser.eat(','));
errorOnAssignmentPattern(parser);
parser.eat('}', true);
}
else {
const name = parser.readIdentifier();
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'
});
}
errorOnAssignmentPattern(parser);
}
return context;
}

@ -1,3 +1,4 @@
import readContext from '../read/context';
import readExpression from '../read/expression'; import readExpression from '../read/expression';
import { whitespace } from '../../utils/patterns'; import { whitespace } from '../../utils/patterns';
import { trimStart, trimEnd } from '../../utils/trim'; import { trimStart, trimEnd } from '../../utils/trim';
@ -248,40 +249,7 @@ export default function mustache(parser: Parser) {
parser.eat('as', true); parser.eat('as', true);
parser.requireWhitespace(); parser.requireWhitespace();
if (parser.eat('[')) { block.context = readContext(parser);
parser.allowWhitespace();
block.destructuredContexts = [];
do {
parser.allowWhitespace();
const destructuredContext = parser.readIdentifier();
if (!destructuredContext) parser.error({
code: `expected-name`,
message: `Expected name`
});
block.destructuredContexts.push(destructuredContext);
parser.allowWhitespace();
} while (parser.eat(','));
if (!block.destructuredContexts.length) parser.error({
code: `expected-name`,
message: `Expected name`
});
block.context = block.destructuredContexts.join('_');
parser.allowWhitespace();
parser.eat(']', true);
} else {
block.context = parser.readIdentifier();
if (!block.context) parser.error({
code: `expected-name`,
message: `Expected name`
});
}
parser.allowWhitespace(); parser.allowWhitespace();

@ -0,0 +1,20 @@
export default function unpackDestructuring(
contexts: Array<{ name: string, tail: string }>,
node: Node,
tail: string
) {
if (node.type === 'Identifier') {
contexts.push({
key: node,
tail
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
unpackDestructuring(contexts, element, `${tail}[${i}]`);
});
} else if (node.type === 'ObjectPattern') {
node.properties.forEach((property) => {
unpackDestructuring(contexts, property.value, `${tail}.${property.key.name}`);
});
}
}

@ -8,6 +8,7 @@ import fuzzymatch from '../utils/fuzzymatch'
import flattenReference from '../../utils/flattenReference'; import flattenReference from '../../utils/flattenReference';
import { Validator } from '../index'; import { Validator } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
import unpackDestructuring from '../../utils/unpackDestructuring';
function isEmptyBlock(node: Node) { function isEmptyBlock(node: Node) {
if (!/Block$/.test(node.type) || !node.children) return false; if (!/Block$/.test(node.type) || !node.children) return false;
@ -60,19 +61,17 @@ export default function validateHtml(validator: Validator, html: Node) {
} }
else if (node.type === 'EachBlock') { else if (node.type === 'EachBlock') {
if (validator.helpers.has(node.context)) { const contexts = [];
let c: number = node.expression.end; unpackDestructuring(contexts, node.context, '');
// find start of context contexts.forEach(prop => {
while (/\s/.test(validator.source[c])) c += 1; if (validator.helpers.has(prop.key.name)) {
c += 2; validator.warn(prop.key, {
while (/\s/.test(validator.source[c])) c += 1;
validator.warn({ start: c, end: c + node.context.length }, {
code: `each-context-clash`, code: `each-context-clash`,
message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity` message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`
}); });
} }
});
} }
if (validator.options.dev && isEmptyBlock(node)) { if (validator.options.dev && isEmptyBlock(node)) {

@ -250,8 +250,8 @@ function create_each_block(component, ctx) {
function get_each_context(ctx, list, i) { function get_each_context(ctx, list, i) {
return assign(assign({}, ctx), { return assign(assign({}, ctx), {
each_value: list,
node: list[i], node: list[i],
each_value: list,
node_index: i node_index: i
}); });
} }

@ -98,8 +98,8 @@ function create_each_block(component, ctx) {
function get_each_context(ctx, list, i) { function get_each_context(ctx, list, i) {
return assign(assign({}, ctx), { return assign(assign({}, ctx), {
each_value: list,
node: list[i], node: list[i],
each_value: list,
node_index: i node_index: i
}); });
} }

@ -297,8 +297,8 @@ function create_each_block(component, ctx) {
function get_each_context(ctx, list, i) { function get_each_context(ctx, list, i) {
return assign(assign({}, ctx), { return assign(assign({}, ctx), {
each_value: list,
comment: list[i], comment: list[i],
each_value: list,
i: i i: i
}); });
} }

@ -143,8 +143,8 @@ function create_each_block(component, ctx) {
function get_each_context(ctx, list, i) { function get_each_context(ctx, list, i) {
return assign(assign({}, ctx), { return assign(assign({}, ctx), {
each_value: list,
comment: list[i], comment: list[i],
each_value: list,
i: i i: i
}); });
} }

@ -1,5 +1,4 @@
{ {
"hash": "gtdm5e",
"html": { "html": {
"start": 0, "start": 0,
"end": 62, "end": 62,
@ -54,11 +53,25 @@
] ]
} }
], ],
"destructuredContexts": [ "context": {
"key", "start": 18,
"value" "end": null,
], "type": "ArrayPattern",
"context": "key_value" "elements": [
{
"start": 19,
"end": 22,
"type": "Identifier",
"name": "key"
},
{
"start": 24,
"end": 29,
"type": "Identifier",
"name": "value"
}
]
}
} }
] ]
}, },

@ -1,5 +1,4 @@
{ {
"hash": "ljl07n",
"html": { "html": {
"start": 0, "start": 0,
"end": 77, "end": 77,
@ -37,7 +36,12 @@
] ]
} }
], ],
"context": "animal", "context": {
"start": 18,
"end": 24,
"type": "Identifier",
"name": "animal"
},
"else": { "else": {
"start": 50, "start": 50,
"end": 70, "end": 70,

@ -1,5 +1,4 @@
{ {
"hash": "1143n2g",
"html": { "html": {
"start": 0, "start": 0,
"end": 58, "end": 58,
@ -54,7 +53,12 @@
] ]
} }
], ],
"context": "animal", "context": {
"start": 18,
"end": 24,
"type": "Identifier",
"name": "animal"
},
"index": "i" "index": "i"
} }
] ]

@ -36,7 +36,12 @@
] ]
} }
], ],
"context": "todo", "context": {
"start": 16,
"end": 20,
"type": "Identifier",
"name": "todo"
},
"key": { "key": {
"type": "MemberExpression", "type": "MemberExpression",
"start": 22, "start": 22,

@ -1,5 +1,4 @@
{ {
"hash": "mzeq0s",
"html": { "html": {
"start": 0, "start": 0,
"end": 50, "end": 50,
@ -37,7 +36,12 @@
] ]
} }
], ],
"context": "animal" "context": {
"start": 18,
"end": 24,
"type": "Identifier",
"name": "animal"
}
} }
] ]
}, },

@ -1,5 +1,4 @@
{ {
"hash": "8weqxs",
"html": { "html": {
"start": 0, "start": 0,
"end": 41, "end": 41,
@ -37,7 +36,12 @@
] ]
} }
], ],
"context": "𐊧" "context": {
"start": 17,
"end": 19,
"type": "Identifier",
"name": "𐊧"
}
} }
] ]
}, },

@ -0,0 +1,22 @@
export default {
data: {
animalPawsEntries: [
{ animal: 'raccoon', pawType: 'hands' },
{ animal: 'eagle', pawType: 'wings' }
]
},
html: `
<p>raccoon: hands</p>
<p>eagle: wings</p>
`,
test ( assert, component, target ) {
component.set({
animalPawsEntries: [{ animal: 'cow', pawType: 'hooves' }]
});
assert.htmlEqual( target.innerHTML, `
<p>cow: hooves</p>
`);
},
};

@ -0,0 +1,3 @@
{#each animalPawsEntries as { animal, pawType } }
<p>{animal}: {pawType}</p>
{/each}
Loading…
Cancel
Save