destructuring

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

@ -6,6 +6,7 @@ import createDebuggingComment from '../../utils/createDebuggingComment';
import Expression from './shared/Expression';
import mapChildren from './shared/mapChildren';
import TemplateScope from './shared/TemplateScope';
import unpackDestructuring from '../../utils/unpackDestructuring';
export default class EachBlock extends Node {
type: 'EachBlock';
@ -18,7 +19,7 @@ export default class EachBlock extends Node {
context: string;
key: Expression;
scope: TemplateScope;
destructuredContexts: string[];
contexts: Array<{ name: string, tail: string }>;
children: Node[];
else?: ElseBlock;
@ -27,7 +28,7 @@ export default class EachBlock extends Node {
super(compiler, parent, scope, info);
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.key = info.key
@ -36,7 +37,12 @@ export default class EachBlock extends Node {
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) {
// 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);
}
// 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.else = info.else
@ -90,17 +90,13 @@ export default class EachBlock extends Node {
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`,
`${this.context}: list[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.initChildren(this.block, stripWhitespace, nextSibling);
@ -481,8 +477,7 @@ export default class EachBlock extends Node {
const { compiler } = this;
const { snippet } = this.expression;
const props = [`${this.context}: item`]
.concat(this.destructuredContexts.map((name, i) => `${name}: item[${i}]`));
const props = this.contexts.map(prop => `${prop.key.name}: item${prop.tail}`);
const getContext = this.index
? `(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 { whitespace } from '../../utils/patterns';
import { trimStart, trimEnd } from '../../utils/trim';
@ -248,40 +249,7 @@ export default function mustache(parser: Parser) {
parser.eat('as', true);
parser.requireWhitespace();
if (parser.eat('[')) {
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`
});
}
block.context = readContext(parser);
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 { Validator } from '../index';
import { Node } from '../../interfaces';
import unpackDestructuring from '../../utils/unpackDestructuring';
function isEmptyBlock(node: Node) {
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') {
if (validator.helpers.has(node.context)) {
let c: number = node.expression.end;
// find start of context
while (/\s/.test(validator.source[c])) c += 1;
c += 2;
while (/\s/.test(validator.source[c])) c += 1;
validator.warn({ start: c, end: c + node.context.length }, {
code: `each-context-clash`,
message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`
});
}
const contexts = [];
unpackDestructuring(contexts, node.context, '');
contexts.forEach(prop => {
if (validator.helpers.has(prop.key.name)) {
validator.warn(prop.key, {
code: `each-context-clash`,
message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`
});
}
});
}
if (validator.options.dev && isEmptyBlock(node)) {

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

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

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

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

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

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

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

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

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

@ -1,5 +1,4 @@
{
"hash": "8weqxs",
"html": {
"start": 0,
"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