clean up nodes

pull/6898/head
Yuichiro Yamashita 5 years ago
parent 7b5247d7a4
commit 185332e409

@ -208,7 +208,7 @@ export default {
}, },
invalid_attribute_value: (name: string) => ({ invalid_attribute_value: (name: string) => ({
code: `invalid-${name}-value`, code: `invalid-${name}-value`,
message: `${name} attribute must be true or false` message: `${name} attribute must be true or false`
}), }),
invalid_options_attribute_unknown: { invalid_options_attribute_unknown: {
code: 'invalid-options-attribute', code: 'invalid-options-attribute',

@ -5,7 +5,6 @@ import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import Element from './Element'; import Element from './Element';
import EachBlock from './EachBlock'; import EachBlock from './EachBlock';
import DynamicElement from './DynamicElement';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
export default class Animation extends Node { export default class Animation extends Node {
@ -13,7 +12,7 @@ export default class Animation extends Node {
name: string; name: string;
expression: Expression; expression: Expression;
constructor(component: Component, parent: Element | DynamicElement, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);

@ -10,7 +10,6 @@ import Element from './Element';
import InlineComponent from './InlineComponent'; import InlineComponent from './InlineComponent';
import Window from './Window'; import Window from './Window';
import { clone } from '../../utils/clone'; import { clone } from '../../utils/clone';
import DynamicElement from './DynamicElement';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
// TODO this should live in a specific binding // TODO this should live in a specific binding
@ -33,7 +32,7 @@ export default class Binding extends Node {
is_contextual: boolean; is_contextual: boolean;
is_readonly: boolean; is_readonly: boolean;
constructor(component: Component, parent: Element | InlineComponent | Window | DynamicElement, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Element | InlineComponent | Window, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {

@ -1,151 +0,0 @@
import Node from './shared/Node';
import Attribute from './Attribute';
import Binding from './Binding';
import EventHandler from './EventHandler';
import Let from './Let';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
import Expression from './shared/Expression';
import Component from '../Component';
import map_children from './shared/map_children';
import Class from './Class';
import Transition from './Transition';
import Animation from './Animation';
import Action from './Action';
import { string_literal } from '../utils/stringify';
import { Literal } from 'estree';
import Text from './Text';
export default class DynamicElement extends Node {
type: 'DynamicElement';
name: string;
tag: Expression;
attributes: Attribute[] = [];
actions: Action[] = [];
bindings: Binding[] = [];
classes: Class[] = [];
handlers: EventHandler[] = [];
lets: Let[] = [];
intro?: Transition = null;
outro?: Transition = null;
animation?: Animation = null;
children: INode[];
scope: TemplateScope;
needs_manual_style_scoping: boolean;
constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info);
this.name = info.name;
if (typeof info.tag === 'string') {
this.tag = new Expression(component, this, scope, string_literal(info.tag) as Literal);
} else {
this.tag = new Expression(component, this, scope, info.tag);
}
info.attributes.forEach((node) => {
switch (node.type) {
case 'Action':
this.actions.push(new Action(component, this, scope, node));
break;
case 'Attribute':
case 'Spread':
this.attributes.push(new Attribute(component, this, scope, node));
break;
case 'Binding':
this.bindings.push(new Binding(component, this, scope, node));
break;
case 'Class':
this.classes.push(new Class(component, this, scope, node));
break;
case 'EventHandler':
this.handlers.push(new EventHandler(component, this, scope, node));
break;
case 'Let': {
const l = new Let(component, this, scope, node);
this.lets.push(l);
const dependencies = new Set([l.name.name]);
l.names.forEach((name) => {
scope.add(name, dependencies, this);
});
break;
}
case 'Transition': {
const transition = new Transition(component, this, scope, node);
if (node.intro) this.intro = transition;
if (node.outro) this.outro = transition;
break;
}
case 'Animation':
this.animation = new Animation(component, this, scope, node);
break;
default:
throw new Error(`Not implemented: ${node.type}`);
}
});
this.scope = scope;
this.children = map_children(component, this, this.scope, info.children);
this.validate();
// TODO create BaseElement class or an interface which both DynamicElement and Element use
// to resolve the hacky cast
component.apply_stylesheet(this as any);
}
validate() {
this.bindings.forEach(binding => {
if (binding.name !== 'this') {
this.component.error(binding, {
code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding. svelte:element only supports bind:this`
});
}
});
}
add_css_class() {
if (this.attributes.some(attr => attr.is_spread)) {
this.needs_manual_style_scoping = true;
return;
}
const { id } = this.component.stylesheet;
const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(class_attribute.chunks[0] as Text).data += ` ${id}`;
} else {
(class_attribute.chunks as Node[]).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${id}`,
synthetic: true
} as any)
);
}
} else {
this.attributes.push(
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: id, synthetic: true }]
} as any)
);
}
}
}

@ -13,6 +13,8 @@ import map_children from './shared/map_children';
import { dimensions } from '../../utils/patterns'; import { dimensions } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import { string_literal } from '../utils/stringify';
import { Literal } from 'estree';
import Let from './Let'; import Let from './Let';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';
@ -132,12 +134,25 @@ export default class Element extends Node {
children: INode[]; children: INode[];
namespace: string; namespace: string;
needs_manual_style_scoping: boolean; needs_manual_style_scoping: boolean;
dynamic_tag?: Expression; // If tag is <svelte:element>, it will be set.
dynamic_tag_expr?: Expression = null;
get is_dynamic_tag(): boolean {
return this.name === 'svelte:element';
}
constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { constructor(component: Component, parent: Node, scope: TemplateScope, info: any) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.name = info.name; this.name = info.name;
if (this.name === 'svelte:element') {
if (typeof info.tag === 'string') {
this.dynamic_tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal);
} else {
this.dynamic_tag_expr = new Expression(component, this, scope, info.tag);
}
}
this.namespace = get_namespace(parent as Element, this, component.namespace); this.namespace = get_namespace(parent as Element, this, component.namespace);
if (this.namespace !== namespaces.foreign) { if (this.namespace !== namespaces.foreign) {

@ -4,7 +4,6 @@ import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces'; import { TemplateNode } from '../../interfaces';
import Element from './Element'; import Element from './Element';
import DynamicElement from './DynamicElement';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
export default class Transition extends Node { export default class Transition extends Node {
@ -14,7 +13,7 @@ export default class Transition extends Node {
expression: Expression; expression: Expression;
is_local: boolean; is_local: boolean;
constructor(component: Component, parent: Element | DynamicElement, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
component.warn_if_undefined(info.name, info, scope); component.warn_if_undefined(info.name, info, scope);

@ -32,7 +32,6 @@ import ThenBlock from './ThenBlock';
import Title from './Title'; import Title from './Title';
import Transition from './Transition'; import Transition from './Transition';
import Window from './Window'; import Window from './Window';
import DynamicElement from './DynamicElement';
// note: to write less types each of types in union below should have type defined as literal // note: to write less types each of types in union below should have type defined as literal
// https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
@ -46,7 +45,6 @@ export type INode = Action
| Class | Class
| Comment | Comment
| DebugTag | DebugTag
| DynamicElement
| EachBlock | EachBlock
| Element | Element
| ElseBlock | ElseBlock

@ -1,7 +1,6 @@
import AwaitBlock from '../AwaitBlock'; import AwaitBlock from '../AwaitBlock';
import Body from '../Body'; import Body from '../Body';
import Comment from '../Comment'; import Comment from '../Comment';
import DynamicElement from '../DynamicElement';
import EachBlock from '../EachBlock'; import EachBlock from '../EachBlock';
import Element from '../Element'; import Element from '../Element';
import Head from '../Head'; import Head from '../Head';
@ -26,7 +25,7 @@ function get_constructor(type) {
case 'AwaitBlock': return AwaitBlock; case 'AwaitBlock': return AwaitBlock;
case 'Body': return Body; case 'Body': return Body;
case 'Comment': return Comment; case 'Comment': return Comment;
case 'DynamicElement' : return DynamicElement; case 'DynamicElement' : return Element;
case 'EachBlock': return EachBlock; case 'EachBlock': return EachBlock;
case 'Element': return Element; case 'Element': return Element;
case 'Head': return Head; case 'Head': return Head;

@ -3,13 +3,12 @@ import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import DynamicElement from '../../nodes/DynamicElement';
import ElementWrapper from './Element/index'; import ElementWrapper from './Element/index';
import create_debugging_comment from './shared/create_debugging_comment'; import create_debugging_comment from './shared/create_debugging_comment';
import Element from '../../nodes/Element'; import Element from '../../nodes/Element';
export default class DynamicElementWrapper extends Wrapper { export default class DynamicElementWrapper extends Wrapper {
node: DynamicElement; node: Element;
elementWrapper: ElementWrapper; elementWrapper: ElementWrapper;
block: Block; block: Block;
dependencies: string[]; dependencies: string[];
@ -19,14 +18,14 @@ export default class DynamicElementWrapper extends Wrapper {
renderer: Renderer, renderer: Renderer,
block: Block, block: Block,
parent: Wrapper, parent: Wrapper,
node: DynamicElement, node: Element,
strip_whitespace: boolean, strip_whitespace: boolean,
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.not_static_content(); this.not_static_content();
this.dependencies = node.tag.dynamic_dependencies(); this.dependencies = node.dynamic_tag_expr.dynamic_dependencies();
if (this.dependencies.length) { if (this.dependencies.length) {
block = block.child({ block = block.child({
@ -37,8 +36,6 @@ export default class DynamicElementWrapper extends Wrapper {
renderer.blocks.push(block); renderer.blocks.push(block);
} }
(node as unknown as Element).dynamic_tag = node.tag;
this.block = block; this.block = block;
this.elementWrapper = new ElementWrapper( this.elementWrapper = new ElementWrapper(
renderer, renderer,
@ -83,7 +80,7 @@ export default class DynamicElementWrapper extends Wrapper {
const dynamic = this.block.has_update_method; const dynamic = this.block.has_update_method;
const previous_tag = block.get_unique_name('previous_tag'); const previous_tag = block.get_unique_name('previous_tag');
const snippet = this.node.tag.manipulate(block); const snippet = this.node.dynamic_tag_expr.manipulate(block);
block.add_variable(previous_tag, snippet); block.add_variable(previous_tag, snippet);
const not_equal = this.renderer.component.component_options.immutable const not_equal = this.renderer.component.component_options.immutable
@ -128,7 +125,7 @@ export default class DynamicElementWrapper extends Wrapper {
if (dynamic) { if (dynamic) {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
${body} ${body}
} else { } else {
${this.var}.p(#ctx, #dirty); ${this.var}.p(#ctx, #dirty);
} }

@ -211,8 +211,8 @@ export default class ElementWrapper extends Wrapper {
} }
}); });
if (node.dynamic_tag) { if (node.is_dynamic_tag) {
block.add_dependencies(node.dynamic_tag.dependencies); block.add_dependencies(node.dynamic_tag_expr.dependencies);
} }
if (this.parent) { if (this.parent) {
@ -248,11 +248,11 @@ export default class ElementWrapper extends Wrapper {
b`${node} = ${render_statement};` b`${node} = ${render_statement};`
); );
if (this.node.dynamic_tag && this.renderer.options.dev) { if (this.node.is_dynamic_tag && this.renderer.options.dev) {
block.chunks.create.push(b`@validate_dynamic_element(${this.node.dynamic_tag.manipulate(block)});`); block.chunks.create.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`);
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
block.chunks.claim.push(b`@validate_dynamic_element(${this.node.dynamic_tag.manipulate(block)});`); block.chunks.claim.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`);
} }
} }
@ -354,8 +354,8 @@ export default class ElementWrapper extends Wrapper {
this.add_classes(block); this.add_classes(block);
this.add_manual_style_scoping(block); this.add_manual_style_scoping(block);
if (this.node.dynamic_tag) { if (this.node.is_dynamic_tag) {
const dependencies = this.node.dynamic_tag.dynamic_dependencies(); const dependencies = this.node.dynamic_tag_expr.dynamic_dependencies();
if (dependencies.length) { if (dependencies.length) {
const condition = block.renderer.dirty( const condition = block.renderer.dirty(
dependencies dependencies
@ -405,7 +405,7 @@ export default class ElementWrapper extends Wrapper {
return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`;
} }
const reference = this.node.dynamic_tag ? this.node.dynamic_tag.manipulate(block) : `"${name}"`; const reference = this.node.is_dynamic_tag ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`;
return x`@element(${reference})`; return x`@element(${reference})`;
} }
@ -417,7 +417,7 @@ export default class ElementWrapper extends Wrapper {
const name = this.node.namespace const name = this.node.namespace
? this.node.name ? this.node.name
: this.node.name.toUpperCase(); : this.node.name.toUpperCase();
const reference = this.node.dynamic_tag ? this.node.dynamic_tag.manipulate(block) : `"${name}"`; const reference = this.node.is_dynamic_tag ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`;
if (this.node.namespace === namespaces.svg) { if (this.node.namespace === namespaces.svg) {
return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`;

@ -5,7 +5,6 @@ import {
import { get_slot_scope } from './shared/get_slot_scope'; import { get_slot_scope } from './shared/get_slot_scope';
import { boolean_attributes } from './shared/boolean_attributes'; import { boolean_attributes } from './shared/boolean_attributes';
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import DynamicElement from '../../nodes/DynamicElement';
import ElementHandler from './Element'; import ElementHandler from './Element';
import { x } from 'code-red'; import { x } from 'code-red';
import Expression from '../../nodes/shared/Expression'; import Expression from '../../nodes/shared/Expression';
@ -14,17 +13,16 @@ import Element from '../../nodes/Element';
import { Expression as ESExpression } from 'estree'; import { Expression as ESExpression } from 'estree';
export default function ( export default function (
node: DynamicElement, node: Element,
renderer: Renderer, renderer: Renderer,
options: RenderOptions & { options: RenderOptions & {
slot_scopes: Map<any, any>; slot_scopes: Map<any, any>;
} }
) { ) {
const dependencies = node.tag.dynamic_dependencies(); const dependencies = node.dynamic_tag_expr.dynamic_dependencies();
if (dependencies.length === 0) { if (dependencies.length === 0) {
((node as unknown) as Element).dynamic_tag = node.tag; ElementHandler(node, renderer, options);
ElementHandler((node as unknown) as Element, renderer, options);
} else { } else {
const children = remove_whitespace_children(node.children, node.next); const children = remove_whitespace_children(node.children, node.next);
@ -43,7 +41,7 @@ export default function (
} }
renderer.add_string('<'); renderer.add_string('<');
renderer.add_expression(node.tag.node as ESExpression); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression);
const class_expression_list = node.classes.map((class_directive) => { const class_expression_list = node.classes.map((class_directive) => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
@ -184,13 +182,13 @@ export default function (
} }
renderer.add_string('</'); renderer.add_string('</');
renderer.add_expression(node.tag.node as ESExpression); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression);
renderer.add_string('>'); renderer.add_string('>');
} else if (slot && nearest_inline_component) { } else if (slot && nearest_inline_component) {
renderer.render(children, options); renderer.render(children, options);
renderer.add_string('</'); renderer.add_string('</');
renderer.add_expression(node.tag.node as ESExpression); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression);
renderer.add_string('>'); renderer.add_string('>');
const lets = node.lets; const lets = node.lets;
@ -208,7 +206,7 @@ export default function (
renderer.render(children, options); renderer.render(children, options);
renderer.add_string('</'); renderer.add_string('</');
renderer.add_expression(node.tag.node as ESExpression); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression);
renderer.add_string('>'); renderer.add_string('>');
} }
} }

@ -23,9 +23,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
node.attributes.some((attribute) => attribute.name === 'contenteditable') node.attributes.some((attribute) => attribute.name === 'contenteditable')
); );
if (node.dynamic_tag) { if (node.is_dynamic_tag) {
renderer.add_string('<'); renderer.add_string('<');
renderer.add_expression(node.dynamic_tag.node as ESExpression); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression);
} else { } else {
renderer.add_string(`<${node.name}`); renderer.add_string(`<${node.name}`);
} }
@ -158,9 +158,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
} }
if (!is_void(node.name)) { if (!is_void(node.name)) {
if (node.dynamic_tag) { if (node.is_dynamic_tag) {
renderer.add_string('</'); renderer.add_string('</');
renderer.add_expression(node.dynamic_tag.node as ESExpression); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression);
renderer.add_string('>'); renderer.add_string('>');
} else { } else {
renderer.add_string(`</${node.name}>`); renderer.add_string(`</${node.name}>`);
@ -170,9 +170,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
renderer.render(children, options); renderer.render(children, options);
if (!is_void(node.name)) { if (!is_void(node.name)) {
if (node.dynamic_tag) { if (node.is_dynamic_tag) {
renderer.add_string('</'); renderer.add_string('</');
renderer.add_expression(node.dynamic_tag.node as ESExpression); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression);
renderer.add_string('>'); renderer.add_string('>');
} else { } else {
renderer.add_string(`</${node.name}>`); renderer.add_string(`</${node.name}>`);

@ -1,3 +1,3 @@
export default { export default {
error: "'value' is not a valid binding. svelte:element only supports bind:this" error: "'value' is not a valid binding on <svelte:element> elements"
}; };

Loading…
Cancel
Save