pull/1367/head
Rich Harris 7 years ago
parent 7825c1230a
commit 32774a821d

@ -6,13 +6,13 @@ export default class Action extends Node {
name: string;
expression: Expression;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.name = info.name;
this.expression = info.expression
? new Expression(compiler, this, info.expression)
? new Expression(compiler, this, scope, info.expression)
: null;
}
}

@ -28,8 +28,8 @@ export default class Attribute extends Node {
dependencies: Set<string>;
expression: Node;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.name = info.name;
this.isTrue = info.value === true;
@ -41,7 +41,7 @@ export default class Attribute extends Node {
: info.value.map(node => {
if (node.type === 'Text') return node;
const expression = new Expression(compiler, this, node.expression);
const expression = new Expression(compiler, this, scope, node.expression);
addToSet(this.dependencies, expression.dependencies);
return expression;

@ -20,11 +20,11 @@ export default class Binding extends Node {
obj: string;
prop: string;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.name = info.name;
this.value = new Expression(compiler, this, info.value);
this.value = new Expression(compiler, this, scope, info.value);
// const contextual = block.contexts.has(name);
const contextual = false; // TODO

@ -24,8 +24,8 @@ export default class Component extends Node {
children: Node[];
ref: string;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
compiler.hasComponents = true;
@ -39,15 +39,15 @@ export default class Component extends Node {
switch (node.type) {
case 'Attribute':
// TODO spread
this.attributes.push(new Attribute(compiler, this, node));
this.attributes.push(new Attribute(compiler, this, scope, node));
break;
case 'Binding':
this.bindings.push(new Binding(compiler, this, node));
this.bindings.push(new Binding(compiler, this, scope, node));
break;
case 'EventHandler':
this.handlers.push(new EventHandler(compiler, this, node));
this.handlers.push(new EventHandler(compiler, this, scope, node));
break;
case 'Ref':
@ -63,7 +63,7 @@ export default class Component extends Node {
}
});
this.children = mapChildren(compiler, this, info.children);
this.children = mapChildren(compiler, this, scope, info.children);
}
init(
@ -150,7 +150,7 @@ export default class Component extends Node {
? '{}'
: stringifyProps(
// this.attributes.map(attr => `${attr.name}: ${attr.value}`)
this.attributes.map(attr => `${attr.name}: "TODO"`)
this.attributes.map(attr => `${attr.name}: ${attr.getValue()}`)
);
if (this.attributes.length || this.bindings.length) {

@ -21,14 +21,19 @@ export default class EachBlock extends Node {
children: Node[];
else?: ElseBlock;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.expression = new Expression(compiler, this, info.expression);
this.expression = new Expression(compiler, this, scope, info.expression);
this.context = info.context;
this.key = info.key;
this.children = mapChildren(compiler, this, info.children);
this.scope = scope.child();
// TODO handle indexes and destructuring
this.scope.add(this.context, this.expression.dependencies);
this.children = mapChildren(compiler, this, this.scope, info.children);
}
init(

@ -33,8 +33,8 @@ export default class Element extends Node {
ref: string;
namespace: string;
constructor(compiler, parent, info: any) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info: any) {
super(compiler, parent, scope, info);
this.name = info.name;
const parentElement = parent.findNearest(/^Element/);
@ -53,26 +53,26 @@ export default class Element extends Node {
info.attributes.forEach(node => {
switch (node.type) {
case 'Action':
this.actions.push(new Action(compiler, this, node));
this.actions.push(new Action(compiler, this, scope, node));
break;
case 'Attribute':
// special case
if (node.name === 'xmlns') this.namespace = node.value[0].data;
this.attributes.push(new Attribute(compiler, this, node));
this.attributes.push(new Attribute(compiler, this, scope, node));
break;
case 'Binding':
this.bindings.push(new Binding(compiler, this, node));
this.bindings.push(new Binding(compiler, this, scope, node));
break;
case 'EventHandler':
this.handlers.push(new EventHandler(compiler, this, node));
this.handlers.push(new EventHandler(compiler, this, scope, node));
break;
case 'Transition':
const transition = new Transition(compiler, this, node);
const transition = new Transition(compiler, this, scope, node);
if (node.intro) this.intro = transition;
if (node.outro) this.outro = transition;
break;
@ -92,7 +92,7 @@ export default class Element extends Node {
// TODO break out attributes and directives here
this.children = mapChildren(compiler, this, info.children);
this.children = mapChildren(compiler, this, scope, info.children);
}
init(

@ -6,8 +6,8 @@ export default class ElseBlock extends Node {
type: 'ElseBlock';
children: Node[];
constructor(compiler, parent, info) {
super(compiler, parent, info);
this.children = mapChildren(compiler, this, info.children);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.children = mapChildren(compiler, this, scope, info.children);
}
}

@ -13,8 +13,8 @@ export default class EventHandler extends Node {
args: Expression[];
snippet: string;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.name = info.name;
this.dependencies = new Set();
@ -23,7 +23,7 @@ export default class EventHandler extends Node {
this.callee = flattenReference(info.expression.callee);
this.insertionPoint = info.expression.start;
this.args = info.expression.arguments.map(param => {
const expression = new Expression(compiler, this, param);
const expression = new Expression(compiler, this, scope, param);
addToSet(this.dependencies, expression.dependencies);
return expression;
});

@ -4,13 +4,39 @@ import Generator from '../Generator';
import mapChildren from './shared/mapChildren';
import Block from '../dom/Block';
class TemplateScope {
names: Set<string>;
indexes: Set<string>;
dependenciesForName: Map<string, string>;
constructor(parent?: TemplateScope) {
this.names = new Set(parent ? parent.names : []);
this.indexes = new Set(parent ? parent.names : []);
this.dependenciesForName = new Map(parent ? parent.dependenciesForName : []);
}
add(name, dependencies) {
this.names.add(name);
this.dependenciesForName.set(name, dependencies);
}
child() {
return new TemplateScope(this);
}
}
export default class Fragment extends Node {
block: Block;
children: Node[];
scope: TemplateScope;
constructor(compiler: Generator, info: any) {
super(compiler, null, info);
this.children = mapChildren(compiler, this, info.children);
const scope = new TemplateScope();
super(compiler, null, scope, info);
this.scope = scope;
this.children = mapChildren(compiler, this, scope, info.children);
}
init() {

@ -25,14 +25,14 @@ export default class IfBlock extends Node {
block: Block;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.expression = new Expression(compiler, this, info.expression);
this.children = mapChildren(compiler, this, info.children);
this.expression = new Expression(compiler, this, scope, info.expression);
this.children = mapChildren(compiler, this, scope, info.children);
this.else = info.else
? new ElseBlock(compiler, this, info.else)
? new ElseBlock(compiler, this, scope, info.else)
: null;
}

@ -31,10 +31,10 @@ export default class Slot extends Element {
parentNode: string,
parentNodes: string
) {
const { generator } = this;
const { compiler } = this;
const slotName = this.getStaticAttributeValue('name') || 'default';
generator.slots.add(slotName);
compiler.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${slotName}`);
const prop = !isValidIdentifier(slotName) ? `["${slotName}"]` : `.${slotName}`;

@ -33,8 +33,8 @@ export default class Text extends Node {
data: string;
shouldSkip: boolean;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.data = info.data;
}

@ -6,13 +6,13 @@ export default class Transition extends Node {
name: string;
expression: Expression;
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.name = info.name;
this.expression = info.expression
? new Expression(compiler, this, info.expression)
? new Expression(compiler, this, scope, info.expression)
: null;
}
}

@ -38,17 +38,17 @@ export default class Window extends Node {
handlers: EventHandler[];
bindings: Binding[];
constructor(compiler, parent, info) {
super(compiler, parent, info);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.handlers = [];
this.bindings = [];
info.attributes.forEach(node => {
if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(compiler, this, node));
this.handlers.push(new EventHandler(compiler, this, scope, node));
} else if (node.type === 'Binding') {
this.bindings.push(new Binding(compiler, this, node));
this.bindings.push(new Binding(compiler, this, scope, node));
}
});
}

@ -14,7 +14,7 @@ export default class Expression {
contexts: Set<string>;
indexes: Set<string>;
constructor(compiler, parent, info) {
constructor(compiler, parent, scope, info) {
this.compiler = compiler;
this.node = info;
@ -27,7 +27,7 @@ export default class Expression {
const { code, helpers } = compiler;
let { map, scope } = createScopes(info);
let { map, scope: currentScope } = createScopes(info);
const isEventHandler = parent.type === 'EventHandler';
walk(info, {
@ -36,22 +36,23 @@ export default class Expression {
code.addSourcemapLocation(node.end);
if (map.has(node)) {
scope = map.get(node);
currentScope = map.get(node);
return;
}
if (isReference(node, parent)) {
const { name } = flattenReference(node);
if (scope && scope.has(name) || helpers.has(name) || (name === 'event' && isEventHandler)) return;
if (currentScope && currentScope.has(name) || helpers.has(name) || (name === 'event' && isEventHandler)) return;
code.prependRight(node.start, 'ctx.');
if (contextDependencies.has(name)) {
contextDependencies.get(name).forEach(dependency => {
if (scope.names.has(name)) {
scope.dependenciesForName.get(name).forEach(dependency => {
dependencies.add(dependency);
});
} else if (!indexes.has(name)) {
dependencies.add(name);
compiler.expectedProperties.add(name);
}
this.skip();
@ -59,7 +60,7 @@ export default class Expression {
},
leave(node: Node, parent: Node) {
if (map.has(node)) scope = scope.parent;
if (map.has(node)) currentScope = currentScope.parent;
}
});

@ -16,11 +16,11 @@ export default class Node {
canUseInnerHTML: boolean;
var: string;
constructor(compiler: Generator, parent, info: any) {
this.start = info.start;
this.end = info.end;
constructor(compiler: Generator, parent, scope, info: any) {
this.compiler = compiler;
this.parent = parent;
this.start = info.start;
this.end = info.end;
this.type = info.type;
}

@ -5,9 +5,9 @@ import Block from '../../dom/Block';
export default class Tag extends Node {
expression: Expression;
constructor(compiler, parent, info) {
super(compiler, parent, info);
this.expression = new Expression(compiler, this, info.expression);
constructor(compiler, parent, scope, info) {
super(compiler, parent, scope, info);
this.expression = new Expression(compiler, this, scope, info.expression);
}
renameThisMethod(
@ -19,8 +19,8 @@ export default class Tag extends Node {
const hasChangeableIndex = Array.from(indexes).some(index => block.changeableIndexes.get(index));
const shouldCache = (
this.expression.type !== 'Identifier' ||
block.contexts.has(this.expression.name) ||
this.expression.node.type !== 'Identifier' ||
block.contexts.has(this.expression.node.name) ||
hasChangeableIndex
);

@ -2,6 +2,7 @@ import Component from '../Component';
import EachBlock from '../EachBlock';
import Element from '../Element';
import IfBlock from '../IfBlock';
import Slot from '../Slot';
import Text from '../Text';
import MustacheTag from '../MustacheTag';
import Window from '../Window';
@ -13,6 +14,7 @@ function getConstructor(type): typeof Node {
case 'EachBlock': return EachBlock;
case 'Element': return Element;
case 'IfBlock': return IfBlock;
case 'Slot': return Slot;
case 'Text': return Text;
case 'MustacheTag': return MustacheTag;
case 'Window': return Window;
@ -20,11 +22,11 @@ function getConstructor(type): typeof Node {
}
}
export default function mapChildren(compiler, parent, children: any[]) {
export default function mapChildren(compiler, parent, scope, children: any[]) {
let last = null;
return children.map(child => {
const constructor = getConstructor(child.type);
const node = new constructor(compiler, parent, child);
const node = new constructor(compiler, parent, scope, child);
if (last) last.next = node;
node.prev = last;

@ -50,21 +50,19 @@ export default function visitComponent(
});
function getAttributeValue(attribute) {
if (attribute.value === true) return `true`;
if (attribute.value.length === 0) return `''`;
if (attribute.isTrue) return `true`;
if (attribute.chunks.length === 0) return `''`;
if (attribute.value.length === 1) {
const chunk = attribute.value[0];
if (attribute.chunks.length === 1) {
const chunk = attribute.chunks[0];
if (chunk.type === 'Text') {
return stringify(chunk.data);
}
block.contextualise(chunk.expression);
const { snippet } = chunk.metadata;
return snippet;
return chunk.snippet;
}
return '`' + attribute.value.map(stringifyAttribute).join('') + '`';
return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`';
}
const props = usesSpread
@ -72,8 +70,7 @@ export default function visitComponent(
attributes
.map(attribute => {
if (attribute.type === 'Spread') {
block.contextualise(attribute.expression);
return attribute.metadata.snippet;
return attribute.expression.snippet;
} else {
return `{ ${attribute.name}: ${getAttributeValue(attribute)} }`;
}

@ -8,8 +8,7 @@ export default function visitEachBlock(
block: Block,
node: Node
) {
block.contextualise(node.expression);
const { snippet } = node.metadata;
const { snippet } = node.expression;
const open = `\${ ${node.else ? `${snippet}.length ? ` : ''}${snippet}.map(${node.index ? `(${node.context}, ${node.index})` : `(${node.context})`} => \``;
generator.append(open);

@ -145,12 +145,12 @@ function cjs(
helpers: { name: string, alias: string }[],
dependencies: Dependency[]
) {
const SHARED = '__shared';
const helperDeclarations = helpers && (
helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).join(', ')
);
const helperBlock = helpers && (
`var ${SHARED} = require(${JSON.stringify(sharedPath)});\n` +
helpers.map(helper => {
return `var ${helper.alias} = ${SHARED}.${helper.name};`;
}).join('\n')
`var { ${helperDeclarations} } = require(${JSON.stringify(sharedPath)});\n`
);
const requireBlock = dependencies.length > 0 && (

@ -113,7 +113,8 @@ export default function tag(parser: Parser) {
const type = metaTags.has(name)
? metaTags.get(name)
: /[A-Z]/.test(name[0]) ? 'Component' : 'Element';
: /[A-Z]/.test(name[0]) ? 'Component'
: name === 'slot' ? 'Slot' : 'Element';
const element: Node = {
start,

@ -1,6 +1,7 @@
import validateElement from './validateElement';
import validateWindow from './validateWindow';
import validateHead from './validateHead';
import validateSlot from './validateSlot';
import a11y from './a11y';
import fuzzymatch from '../utils/fuzzymatch'
import flattenReference from '../../utils/flattenReference';
@ -29,6 +30,10 @@ export default function validateHtml(validator: Validator, html: Node) {
validateHead(validator, node, refs, refCallees);
}
else if (node.type === 'Slot') {
validateSlot(validator, node);
}
else if (node.type === 'Component' || node.name === 'svelte:self' || node.name === 'svelte:component') {
validateElement(
validator,

@ -0,0 +1,56 @@
import * as namespaces from '../../utils/namespaces';
import validateEventHandler from './validateEventHandler';
import validate, { Validator } from '../index';
import { Node } from '../../interfaces';
export default function validateSlot(
validator: Validator,
node: Node
) {
node.attributes.forEach(attr => {
if (attr.type !== 'Attribute') {
validator.error(attr, {
code: `invalid-slot-directive`,
message: `<slot> cannot have directives`
});
}
if (attr.name !== 'name') {
validator.error(attr, {
code: `invalid-slot-attribute`,
message: `"name" is the only attribute permitted on <slot> elements`
});
}
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') {
validator.error(attr, {
code: `dynamic-slot-name`,
message: `<slot> name cannot be dynamic`
});
}
const slotName = attr.value[0].data;
if (slotName === 'default') {
validator.error(attr, {
code: `invalid-slot-name`,
message: `default is a reserved word — it cannot be used as a slot name`
});
}
// TODO should duplicate slots be disallowed? Feels like it's more likely to be a
// bug than anything. Perhaps it should be a warning
// if (validator.slots.has(slotName)) {
// validator.error(`duplicate '${slotName}' <slot> element`, nameAttribute.start);
// }
// validator.slots.add(slotName);
});
// if (node.attributes.length === 0) && validator.slots.has('default')) {
// validator.error(node, {
// code: `duplicate-slot`,
// message: `duplicate default <slot> element`
// });
// }
}

@ -7,7 +7,7 @@ export default {
},
html: `<svg><rect x="0" y="0" width="100" height="100"></rect></svg>`,
test ( assert, component, target ) {
const svg = target.querySelector( 'svg' );
const rect = target.querySelector( 'rect' );

Loading…
Cancel
Save