diff --git a/src/Stats.ts b/src/Stats.ts index 33f651b2fa..5236e6a85c 100644 --- a/src/Stats.ts +++ b/src/Stats.ts @@ -97,11 +97,18 @@ export default class Stats { }); return { - props: component.props.map(prop => prop.as), timings, warnings: this.warnings, - imports, - templateReferences: component && component.template_references + vars: component.vars.filter(variable => !variable.global && !variable.implicit && !variable.internal).map(variable => ({ + name: variable.name, + export_name: variable.export_name || null, + injected: variable.injected || false, + module: variable.module || false, + mutated: variable.mutated || false, + reassigned: variable.reassigned || false, + referenced: variable.referenced || false, + writable: variable.writable || false + })) }; } diff --git a/src/compile/Component.ts b/src/compile/Component.ts index c7ca6b7feb..42d3f7a449 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -11,11 +11,10 @@ import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import internal_exports from './internal-exports'; -import { Node, Ast, CompileOptions } from '../interfaces'; +import { Node, Ast, CompileOptions, Var } from '../interfaces'; import error from '../utils/error'; import getCodeFrame from '../utils/getCodeFrame'; import flattenReference from '../utils/flattenReference'; -import addToSet from '../utils/addToSet'; import isReference from 'is-reference'; import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; @@ -56,31 +55,22 @@ export default class Component { namespace: string; tag: string; - instance_script: Node; - module_script: Node; + vars: Var[] = []; + var_lookup: Map<string, Var> = new Map(); imports: Node[] = []; module_javascript: string; javascript: string; - declarations: string[] = []; - props: Array<{ name: string, as: string }> = []; - writable_declarations: Set<string> = new Set(); - initialised_declarations: Set<string> = new Set(); - imported_declarations: Set<string> = new Set(); - hoistable_names: Set<string> = new Set(); hoistable_nodes: Set<Node> = new Set(); node_for_declaration: Map<string, Node> = new Map(); - module_exports: Array<{ name: string, as: string }> = []; partly_hoisted: string[] = []; fully_hoisted: string[] = []; reactive_declarations: Array<{ assignees: Set<string>, dependencies: Set<string>, snippet: string }> = []; reactive_declaration_nodes: Set<Node> = new Set(); has_reactive_assignments = false; - mutable_props: Set<string> = new Set(); indirectDependencies: Map<string, Set<string>> = new Map(); - template_references: Set<string> = new Set(); file: string; locate: (c: number) => { line: number, column: number }; @@ -93,7 +83,6 @@ export default class Component { stylesheet: Stylesheet; - userVars: Set<string> = new Set(); aliases: Map<string, string> = new Map(); usedNames: Set<string> = new Set(); @@ -122,26 +111,6 @@ export default class Component { this.stylesheet = new Stylesheet(source, ast, options.filename, options.dev); this.stylesheet.validate(this); - const module_scripts = ast.js.filter(script => this.get_context(script) === 'module'); - const instance_scripts = ast.js.filter(script => this.get_context(script) === 'default'); - - if (module_scripts.length > 1) { - this.error(module_scripts[1], { - code: `invalid-script`, - message: `A component can only have one <script context="module"> element` - }); - } - - if (instance_scripts.length > 1) { - this.error(instance_scripts[1], { - code: `invalid-script`, - message: `A component can only have one instance-level <script> element` - }); - } - - this.module_script = module_scripts[0]; - this.instance_script = instance_scripts[0]; - this.meta = process_meta(this, this.ast.html.children); this.namespace = namespaces[this.meta.namespace] || this.meta.namespace; @@ -169,22 +138,43 @@ export default class Component { if (!options.customElement) this.stylesheet.reify(); this.stylesheet.warnOnUnusedSelectors(stats); + } + + add_var(variable: Var) { + // TODO remove this + if (this.var_lookup.has(variable.name)) { + throw new Error(`dupe: ${variable.name}`); + } + + this.vars.push(variable); + this.var_lookup.set(variable.name, variable); + } - if (!this.instance_script) { - const props = [...this.template_references]; - this.declarations.push(...props); - addToSet(this.mutable_props, this.template_references); - addToSet(this.writable_declarations, this.template_references); - addToSet(this.userVars, this.template_references); + add_reference(name: string) { + const variable = this.var_lookup.get(name); - this.props = props.map(name => ({ + if (variable) { + variable.referenced = true; + } else if (name[0] === '$') { + this.add_var({ name, - as: name - })); - } + injected: true, + referenced: true, + mutated: true, + writable: true + }); - // tell the root fragment scope about all of the mutable names we know from the script - this.mutable_props.forEach(name => this.fragment.scope.mutables.add(name)); + this.add_reference(name.slice(1)); + } else if (!this.ast.instance) { + this.add_var({ + name, + export_name: name, + implicit: true, + mutated: false, + referenced: true, + writable: true + }); + } } addSourcemapLocations(node: Node) { @@ -243,7 +233,10 @@ export default class Component { options.sveltePath, importedHelpers, this.imports, - this.module_exports, + this.vars.filter(variable => variable.module && variable.export_name).map(variable => ({ + name: variable.name, + as: variable.export_name + })), this.source ); @@ -313,7 +306,7 @@ export default class Component { for ( let i = 1; reservedNames.has(alias) || - this.userVars.has(alias) || + this.var_lookup.has(alias) || this.usedNames.has(alias); alias = `${name}_${i++}` ); @@ -329,7 +322,7 @@ export default class Component { } reservedNames.forEach(add); - this.userVars.forEach(add); + this.var_lookup.forEach((value, key) => add(key)); return (name: string) => { if (test) name = `${name}$`; @@ -396,7 +389,34 @@ export default class Component { }); } - extract_imports_and_exports(content, imports, exports) { + extract_imports(content, is_module: boolean) { + const { code } = this; + + content.body.forEach(node => { + if (node.type === 'ImportDeclaration') { + // imports need to be hoisted out of the IIFE + removeNode(code, content.start, content.end, content.body, node); + this.imports.push(node); + + node.specifiers.forEach((specifier: Node) => { + if (specifier.local.name[0] === '$') { + this.error(specifier.local, { + code: 'illegal-declaration', + message: `The $ prefix is reserved, and cannot be used for variable and import names` + }); + } + + this.add_var({ + name: specifier.local.name, + module: is_module, + hoistable: true + }); + }); + } + }); + } + + extract_exports(content, is_module: boolean) { const { code } = this; content.body.forEach(node => { @@ -412,44 +432,31 @@ export default class Component { if (node.declaration.type === 'VariableDeclaration') { node.declaration.declarations.forEach(declarator => { extractNames(declarator.id).forEach(name => { - exports.push({ name, as: name }); - this.mutable_props.add(name); + const variable = this.var_lookup.get(name); + variable.export_name = name; }); }); } else { const { name } = node.declaration.id; - exports.push({ name, as: name }); + + const variable = this.var_lookup.get(name); + variable.export_name = name; } code.remove(node.start, node.declaration.start); } else { removeNode(code, content.start, content.end, content.body, node); node.specifiers.forEach(specifier => { - exports.push({ - name: specifier.local.name, - as: specifier.exported.name - }); + const variable = this.var_lookup.get(specifier.local.name); + + if (variable) { + variable.export_name = specifier.exported.name; + } else { + // TODO what happens with `export { Math }` or some other global? + } }); } } - - // imports need to be hoisted out of the IIFE - else if (node.type === 'ImportDeclaration') { - removeNode(code, content.start, content.end, content.body, node); - imports.push(node); - - node.specifiers.forEach((specifier: Node) => { - if (specifier.local.name[0] === '$') { - this.error(specifier.local, { - code: 'illegal-declaration', - message: `The $ prefix is reserved, and cannot be used for variable and import names` - }); - } - - this.userVars.add(specifier.local.name); - this.imported_declarations.add(specifier.local.name); - }); - } }); } @@ -491,7 +498,7 @@ export default class Component { } walk_module_js() { - const script = this.module_script; + const script = this.ast.module; if (!script) return; this.addSourcemapLocations(script.content); @@ -506,15 +513,35 @@ export default class Component { message: `The $ prefix is reserved, and cannot be used for variable and import names` }); } + + if (!/Import/.test(node.type)) { + const kind = node.type === 'VariableDeclaration' + ? node.kind + : node.type === 'ClassDeclaration' + ? 'class' + : node.type === 'FunctionDeclaration' + ? 'function' + : null; + + // sanity check + if (!kind) throw new Error(`Unknown declaration type ${node.type}`); + + this.add_var({ + name, + module: true, + hoistable: true + }); + } }); - this.extract_imports_and_exports(script.content, this.imports, this.module_exports); + this.extract_imports(script.content, true); + this.extract_exports(script.content, true); remove_indentation(this.code, script.content); this.module_javascript = this.extract_javascript(script); } walk_instance_js_pre_template() { - const script = this.instance_script; + const script = this.ast.instance; if (!script) return; this.addSourcemapLocations(script.content); @@ -530,28 +557,56 @@ export default class Component { message: `The $ prefix is reserved, and cannot be used for variable and import names` }); } - }); - instance_scope.declarations.forEach((node, name) => { - this.userVars.add(name); - this.declarations.push(name); + if (!/Import/.test(node.type)) { + const kind = node.type === 'VariableDeclaration' + ? node.kind + : node.type === 'ClassDeclaration' + ? 'class' + : node.type === 'FunctionDeclaration' + ? 'function' + : null; + + // sanity check + if (!kind) throw new Error(`Unknown declaration type ${node.type}`); + + this.add_var({ + name, + initialised: instance_scope.initialised_declarations.has(name), + writable: kind === 'var' || kind === 'let' + }); + } this.node_for_declaration.set(name, node); }); - this.writable_declarations = instance_scope.writable_declarations; - this.initialised_declarations = instance_scope.initialised_declarations; - globals.forEach(name => { - this.userVars.add(name); + if (this.module_scope && this.module_scope.declarations.has(name)) return; + + if (name[0] === '$') { + this.add_var({ + name, + injected: true, + mutated: true, + writable: true + }); + + this.add_reference(name.slice(1)); + } else { + this.add_var({ + name, + global: true + }); + } }); + this.extract_imports(script.content, false); + this.extract_exports(script.content, false); this.track_mutations(); - this.extract_imports_and_exports(script.content, this.imports, this.props); } walk_instance_js_post_template() { - const script = this.instance_script; + const script = this.ast.instance; if (!script) return; this.hoist_instance_declarations(); @@ -563,28 +618,43 @@ export default class Component { // TODO merge this with other walks that are independent track_mutations() { const component = this; - let { instance_scope: scope, instance_scope_map: map } = this; + const { instance_scope, instance_scope_map: map } = this; + + let scope = instance_scope; - walk(this.instance_script.content, { + walk(this.ast.instance.content, { enter(node, parent) { - let names; if (map.has(node)) { scope = map.get(node); } + let names; + let deep = false; + if (node.type === 'AssignmentExpression') { - names = node.left.type === 'MemberExpression' - ? [getObject(node.left).name] - : extractNames(node.left); + deep = node.left.type === 'MemberExpression'; + + names = deep + ? [getObject(node.left).name] + : extractNames(node.left); } else if (node.type === 'UpdateExpression') { names = [getObject(node.argument).name]; } if (names) { names.forEach(name => { - if (scope.has(name)) component.mutable_props.add(name); + if (scope.findOwner(name) === instance_scope) { + const variable = component.var_lookup.get(name); + variable[deep ? 'mutated' : 'reassigned'] = true; + } }); } + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } } }) } @@ -595,7 +665,7 @@ export default class Component { const component = this; let { instance_scope: scope, instance_scope_map: map } = this; - walk(this.instance_script.content, { + walk(this.ast.instance.content, { enter(node, parent) { if (map.has(node)) { scope = map.get(node); @@ -607,9 +677,6 @@ export default class Component { if (name[0] === '$' && !scope.has(name)) { component.warn_if_undefined(object, null); - - // cheeky hack - component.template_references.add(name); } } }, @@ -627,17 +694,10 @@ export default class Component { const { code, instance_scope, instance_scope_map: map, meta } = this; let scope = instance_scope; - // TODO we will probably end up wanting to use this elsewhere - const exported = new Set(); - this.props.forEach(prop => { - exported.add(prop.name); - this.mutable_props.add(prop.name); - }); - const coalesced_declarations = []; let current_group; - walk(this.instance_script.content, { + walk(this.ast.instance.content, { enter(node, parent) { if (/Function/.test(node.type)) { current_group = null; @@ -650,14 +710,15 @@ export default class Component { if (node.type === 'VariableDeclaration') { if (node.kind === 'var' || scope === instance_scope) { - let has_meta_props = false; let has_exports = false; let has_only_exports = true; node.declarations.forEach(declarator => { extractNames(declarator.id).forEach(name => { + const variable = component.var_lookup.get(name); + if (name === meta.props_object) { - if (exported.has(name)) { + if (variable.export_name) { component.error(declarator, { code: 'exported-meta-props', message: `Cannot export props binding` @@ -676,11 +737,9 @@ export default class Component { } else { code.overwrite(declarator.id.end, declarator.end, ' = $$props'); } - - has_meta_props = true; } - if (exported.has(name)) { + if (variable.export_name) { has_exports = true; } else { has_only_exports = false; @@ -724,7 +783,6 @@ export default class Component { }); coalesced_declarations.forEach(group => { - const kind = group[0].kind; let c = 0; let combining = false; @@ -764,16 +822,16 @@ export default class Component { // reference instance variables other than other // hoistable functions. TODO others? - const { hoistable_names, hoistable_nodes, imported_declarations, instance_scope: scope } = this; - const template_scope = this.fragment.scope; + const { hoistable_nodes, var_lookup } = this; const top_level_function_declarations = new Map(); - this.instance_script.content.body.forEach(node => { + this.ast.instance.content.body.forEach(node => { if (node.type === 'VariableDeclaration') { - if (node.declarations.every(d => d.init && d.init.type === 'Literal' && !this.mutable_props.has(d.id.name) && !template_scope.containsMutable([d.id.name]))) { + if (node.declarations.every(d => d.init && d.init.type === 'Literal' && !this.var_lookup.get(d.id.name).reassigned)) { node.declarations.forEach(d => { - hoistable_names.add(d.id.name); + const variable = this.var_lookup.get(d.id.name); + variable.hoistable = true; }); hoistable_nodes.add(node); @@ -823,8 +881,9 @@ export default class Component { else if (owner === instance_scope) { if (name === fn_declaration.id.name) return; - if (hoistable_names.has(name)) return; - if (imported_declarations.has(name)) return; + + const variable = var_lookup.get(name); + if (variable.hoistable) return; if (top_level_function_declarations.has(name)) { const other_declaration = top_level_function_declarations.get(name); @@ -860,7 +919,8 @@ export default class Component { for (const [name, node] of top_level_function_declarations) { if (!checked.has(node) && is_hoistable(node)) { - hoistable_names.add(name); + const variable = this.var_lookup.get(name); + variable.hoistable = true; hoistable_nodes.add(node); remove_indentation(this.code, node); @@ -875,7 +935,7 @@ export default class Component { const unsorted_reactive_declarations = []; - this.instance_script.content.body.forEach(node => { + this.ast.instance.content.body.forEach(node => { if (node.type === 'LabeledStatement' && node.label.name === '$') { this.reactive_declaration_nodes.add(node); @@ -899,7 +959,7 @@ export default class Component { const object = getObject(node); const { name } = object; - if (name[0] === '$' || component.declarations.indexOf(name) !== -1) { + if (name[0] === '$' || component.var_lookup.has(name)) { dependencies.add(name); } @@ -982,11 +1042,12 @@ export default class Component { } qualify(name) { - if (this.hoistable_names.has(name)) return name; - if (this.imported_declarations.has(name)) return name; - if (this.declarations.indexOf(name) === -1) return name; + const variable = this.var_lookup.get(name); + + if (!variable) return name; + if (variable && variable.hoistable) return name; - this.template_references.add(name); // TODO we can probably remove most other occurrences of this + this.add_reference(name); // TODO we can probably remove most other occurrences of this return `ctx.${name}`; } @@ -998,7 +1059,7 @@ export default class Component { this.has_reactive_assignments = true; } - if (allow_implicit && !this.instance_script) return; + if (allow_implicit && !this.ast.instance && !this.ast.module) return; if (this.instance_scope && this.instance_scope.declarations.has(name)) return; if (this.module_scope && this.module_scope.declarations.has(name)) return; if (template_scope && template_scope.names.has(name)) return; @@ -1009,29 +1070,6 @@ export default class Component { message: `'${name}' is not defined` }); } - - get_context(script) { - const context = script.attributes.find(attribute => attribute.name === 'context'); - if (!context) return 'default'; - - if (context.value.length !== 1 || context.value[0].type !== 'Text') { - this.error(script, { - code: 'invalid-script', - message: `context attribute must be static` - }); - } - - const value = context.value[0].data; - - if (value !== 'module') { - this.error(context, { - code: `invalid-script`, - message: `If the context attribute is supplied, its value must be "module"` - }); - } - - return value; - } } function process_meta(component, nodes) { diff --git a/src/compile/css/Stylesheet.ts b/src/compile/css/Stylesheet.ts index 2cfb960862..0a36086765 100644 --- a/src/compile/css/Stylesheet.ts +++ b/src/compile/css/Stylesheet.ts @@ -265,15 +265,15 @@ export default class Stylesheet { this.nodesWithCssClass = new Set(); this.nodesWithRefCssClass = new Map(); - if (ast.css[0] && ast.css[0].children.length) { - this.id = `svelte-${hash(ast.css[0].content.styles)}`; + if (ast.css && ast.css.children.length) { + this.id = `svelte-${hash(ast.css.content.styles)}`; this.hasStyles = true; const stack: (Rule | Atrule)[] = []; let currentAtrule: Atrule = null; - walk(ast.css[0], { + walk(ast.css, { enter: (node: Node) => { if (node.type === 'Atrule') { const last = stack[stack.length - 1]; diff --git a/src/compile/nodes/Attribute.ts b/src/compile/nodes/Attribute.ts index 88aaadff26..3e1a08855c 100644 --- a/src/compile/nodes/Attribute.ts +++ b/src/compile/nodes/Attribute.ts @@ -34,7 +34,7 @@ export default class Attribute extends Node { this.isSynthetic = false; this.expression = new Expression(component, this, scope, info.expression); - this.dependencies = this.expression.dynamic_dependencies; + this.dependencies = this.expression.dependencies; this.chunks = null; this.isDynamic = true; // TODO not necessarily @@ -59,7 +59,7 @@ export default class Attribute extends Node { const expression = new Expression(component, this, scope, node.expression); - addToSet(this.dependencies, expression.dynamic_dependencies); + addToSet(this.dependencies, expression.dependencies); return expression; }); @@ -73,6 +73,19 @@ export default class Attribute extends Node { } } + get_dependencies() { + if (this.isSpread) return this.expression.dynamic_dependencies(); + + const dependencies = new Set(); + this.chunks.forEach(chunk => { + if (chunk.type === 'Expression') { + addToSet(dependencies, chunk.dynamic_dependencies()); + } + }); + + return [...dependencies]; + } + getValue() { if (this.isTrue) return true; if (this.chunks.length === 0) return `""`; diff --git a/src/compile/nodes/Binding.ts b/src/compile/nodes/Binding.ts index 2432478750..050b7f131c 100644 --- a/src/compile/nodes/Binding.ts +++ b/src/compile/nodes/Binding.ts @@ -2,6 +2,7 @@ import Node from './shared/Node'; import getObject from '../../utils/getObject'; import Expression from './shared/Expression'; import Component from '../Component'; +import TemplateScope from './shared/TemplateScope'; export default class Binding extends Node { name: string; @@ -10,7 +11,7 @@ export default class Binding extends Node { obj: string; prop: string; - constructor(component: Component, parent, scope, info) { + constructor(component: Component, parent, scope: TemplateScope, info) { super(component, parent, scope, info); if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { @@ -30,7 +31,21 @@ export default class Binding extends Node { this.isContextual = scope.names.has(name); // make sure we track this as a mutable ref - scope.setMutable(name); + if (this.isContextual) { + scope.dependenciesForName.get(name).forEach(name => { + const variable = component.var_lookup.get(name); + variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true; + }); + } else { + const variable = component.var_lookup.get(name); + + if (!variable) component.error(this.expression.node, { + code: 'binding-undeclared', + message: `${name} is not declared` + }); + + variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true; + } if (this.expression.node.type === 'MemberExpression') { prop = `[✂${this.expression.node.property.start}-${this.expression.node.property.end}✂]`; diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 362dbb784f..44e91b6408 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -92,7 +92,6 @@ export default class Element extends Node { constructor(component, parent, scope, info: any) { super(component, parent, scope, info); this.name = info.name; - this.scope = scope; const parentElement = parent.findNearest(/^Element/); this.namespace = this.name === 'svg' ? @@ -196,7 +195,7 @@ export default class Element extends Node { const dependencies = new Set([l.name]); l.names.forEach(name => { - this.scope.add(name, dependencies); + this.scope.add(name, dependencies, this); }); }); } else { diff --git a/src/compile/nodes/EventHandler.ts b/src/compile/nodes/EventHandler.ts index fe29c6d1fd..e426efa79c 100644 --- a/src/compile/nodes/EventHandler.ts +++ b/src/compile/nodes/EventHandler.ts @@ -41,7 +41,12 @@ export default class EventHandler extends Node { } } else { const name = component.getUniqueName(`${this.name}_handler`); - component.declarations.push(name); + + component.add_var({ + name, + internal: true, + referenced: true + }); component.partly_hoisted.push(deindent` function ${name}(event) { @@ -57,7 +62,7 @@ export default class EventHandler extends Node { render(block: Block) { if (this.expression) return this.expression.render(block); - this.component.template_references.add(this.handler_name); + // this.component.add_reference(this.handler_name); return `ctx.${this.handler_name}`; } } \ No newline at end of file diff --git a/src/compile/nodes/InlineComponent.ts b/src/compile/nodes/InlineComponent.ts index 4d3b7332a6..28c7b2594e 100644 --- a/src/compile/nodes/InlineComponent.ts +++ b/src/compile/nodes/InlineComponent.ts @@ -24,7 +24,7 @@ export default class InlineComponent extends Node { if (info.name !== 'svelte:component' && info.name !== 'svelte:self') { component.warn_if_undefined(info, scope); - component.template_references.add(info.name); + component.add_reference(info.name); } this.name = info.name; diff --git a/src/compile/nodes/shared/Expression.ts b/src/compile/nodes/shared/Expression.ts index 9ec0c92e84..ea2237543a 100644 --- a/src/compile/nodes/shared/Expression.ts +++ b/src/compile/nodes/shared/Expression.ts @@ -62,6 +62,7 @@ const precedence: Record<string, (node?: Node) => number> = { }; export default class Expression { + type = 'Expression'; component: Component; owner: Wrapper; node: any; @@ -69,7 +70,6 @@ export default class Expression { references: Set<string>; dependencies: Set<string> = new Set(); contextual_dependencies: Set<string> = new Set(); - dynamic_dependencies: Set<string> = new Set(); template_scope: TemplateScope; scope: Scope; @@ -95,7 +95,7 @@ export default class Expression { this.owner = owner; this.is_synthetic = owner.isSynthetic; - const { dependencies, contextual_dependencies, dynamic_dependencies } = this; + const { dependencies, contextual_dependencies } = this; let { map, scope } = createScopes(info); this.scope = scope; @@ -104,27 +104,6 @@ export default class Expression { const expression = this; let function_expression; - function add_dependency(name, deep = false) { - dependencies.add(name); - - if (!function_expression) { - // dynamic_dependencies is used to create `if (changed.foo || ...)` - // conditions — it doesn't apply if the dependency is inside a - // function, and it only applies if the dependency is writable - // or a sub-path of a non-writable - if (component.instance_script) { - const owner = template_scope.getOwner(name); - const is_let = owner && (owner.type === 'InlineComponent' || owner.type === 'Element'); - - if (is_let || component.writable_declarations.has(name) || name[0] === '$' || (component.userVars.has(name) && deep)) { - dynamic_dependencies.add(name); - } - } else { - dynamic_dependencies.add(name); - } - } - } - // discover dependencies, but don't change the code yet walk(info, { enter(node: any, parent: any, key: string) { @@ -143,18 +122,26 @@ export default class Expression { const { name, nodes } = flattenReference(node); if (scope.has(name)) return; - if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return; + if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return; - if (template_scope.names.has(name)) { + if (template_scope.is_let(name)) { + if (!function_expression) { + dependencies.add(name); + } + } else if (template_scope.names.has(name)) { expression.usesContext = true; contextual_dependencies.add(name); - template_scope.dependenciesForName.get(name).forEach(name => add_dependency(name, true)); + if (!function_expression) { + template_scope.dependenciesForName.get(name).forEach(name => dependencies.add(name)); + } } else { - add_dependency(name, nodes.length > 1); - component.template_references.add(name); + if (!function_expression) { + dependencies.add(name); + } + component.add_reference(name); component.warn_if_undefined(nodes[0], template_scope, true); } @@ -162,17 +149,36 @@ export default class Expression { } // track any assignments from template expressions as mutable + let names; + let deep = false; + if (function_expression) { if (node.type === 'AssignmentExpression') { - const names = node.left.type === 'MemberExpression' + deep = node.left.type === 'MemberExpression'; + names = deep ? [getObject(node.left).name] : extractNames(node.left); - names.forEach(name => template_scope.setMutable(name)); } else if (node.type === 'UpdateExpression') { const { name } = getObject(node.argument); - template_scope.setMutable(name); + names = [name]; } } + + if (names) { + names.forEach(name => { + if (template_scope.names.has(name)) { + template_scope.dependenciesForName.get(name).forEach(name => { + const variable = component.var_lookup.get(name); + if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; + }); + } else { + component.add_reference(name); + + const variable = component.var_lookup.get(name); + if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; + } + }); + } }, leave(node) { @@ -187,6 +193,18 @@ export default class Expression { }); } + dynamic_dependencies() { + return Array.from(this.dependencies).filter(name => { + if (this.template_scope.is_let(name)) return true; + + const variable = this.component.var_lookup.get(name); + if (!variable) return false; + + if (variable.mutated || variable.reassigned) return true; // dynamic internal state + if (!variable.module && variable.writable && variable.export_name) return true; // writable props + }); + } + getPrecedence() { return this.node.type in precedence ? precedence[this.node.type](this.node) : 0; } @@ -230,7 +248,7 @@ export default class Expression { const { name, nodes } = flattenReference(node); if (scope.has(name)) return; - if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return; + if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return; if (function_expression) { if (template_scope.names.has(name)) { @@ -241,7 +259,7 @@ export default class Expression { }); } else { dependencies.add(name); - component.template_references.add(name); + component.add_reference(name); } } else if (!is_synthetic && isContextual(component, template_scope, name)) { code.prependRight(node.start, key === 'key' && parent.shorthand @@ -276,7 +294,9 @@ export default class Expression { } else { names.forEach(name => { if (scope.declarations.has(name)) return; - if (component.imported_declarations.has(name)) return; + + const variable = component.var_lookup.get(name); + if (variable && variable.hoistable) return; pending_assignments.add(name); }); @@ -285,7 +305,9 @@ export default class Expression { const { name } = getObject(node.argument); if (scope.declarations.has(name)) return; - if (component.imported_declarations.has(name)) return; + + const variable = component.var_lookup.get(name); + if (variable && variable.hoistable) return; pending_assignments.add(name); } @@ -359,23 +381,38 @@ export default class Expression { // we can hoist this out of the component completely component.fully_hoisted.push(fn); code.overwrite(node.start, node.end, name); + + component.add_var({ + name, + internal: true, + hoistable: true, + referenced: true + }); } else if (contextual_dependencies.size === 0) { // function can be hoisted inside the component init component.partly_hoisted.push(fn); - component.declarations.push(name); - component.template_references.add(name); code.overwrite(node.start, node.end, `ctx.${name}`); + + component.add_var({ + name, + internal: true, + referenced: true + }); } else { // we need a combo block/init recipe component.partly_hoisted.push(fn); - component.declarations.push(name); - component.template_references.add(name); code.overwrite(node.start, node.end, name); + component.add_var({ + name, + internal: true, + referenced: true + }); + declarations.push(deindent` function ${name}(${original_params ? '...args' : ''}) { return ctx.${name}(ctx${original_params ? ', ...args' : ''}); @@ -445,10 +482,10 @@ function isContextual(component: Component, scope: TemplateScope, name: string) // if it's a name below root scope, it's contextual if (!scope.isTopLevel(name)) return true; + const variable = component.var_lookup.get(name); + // hoistables, module declarations, and imports are non-contextual - if (component.hoistable_names.has(name)) return false; - if (component.module_scope && component.module_scope.declarations.has(name)) return false; - if (component.imported_declarations.has(name)) return false; + if (!variable || variable.hoistable) return false; // assume contextual return true; diff --git a/src/compile/nodes/shared/TemplateScope.ts b/src/compile/nodes/shared/TemplateScope.ts index df48cc57f9..70522c88ed 100644 --- a/src/compile/nodes/shared/TemplateScope.ts +++ b/src/compile/nodes/shared/TemplateScope.ts @@ -9,7 +9,6 @@ type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Elem export default class TemplateScope { names: Set<string>; dependenciesForName: Map<string, Set<string>>; - mutables: Set<string> = new Set(); owners: Map<string, NodeWithScope> = new Map(); parent?: TemplateScope; @@ -31,29 +30,6 @@ export default class TemplateScope { return child; } - setMutable(name: string) { - if (this.names.has(name)) { - this.mutables.add(name); - if (this.parent && this.dependenciesForName.has(name)) this.dependenciesForName.get(name).forEach(dep => this.parent.setMutable(dep)); - } else if (this.parent) this.parent.setMutable(name); - else this.mutables.add(name); - } - - containsMutable(names: Iterable<string>) { - for (const name of names) { - const owner = this.getOwner(name); - const is_let = owner && (owner.type === 'InlineComponent' || owner.type === 'Element'); - if (is_let) return true; - - if (name[0] === '$') return true; - if (this.mutables.has(name)) return true; - else if (this.dependenciesForName.has(name) && this.containsMutable(this.dependenciesForName.get(name))) return true; - } - - if (this.parent) return this.parent.containsMutable(names); - else return false; - } - isTopLevel(name: string) { return !this.parent || !this.names.has(name) && this.parent.isTopLevel(name); } @@ -61,4 +37,9 @@ export default class TemplateScope { getOwner(name: string): NodeWithScope { return this.owners.get(name) || (this.parent && this.parent.getOwner(name)); } + + is_let(name: string) { + const owner = this.getOwner(name); + return owner && (owner.type === 'Element' || owner.type === 'InlineComponent'); + } } \ No newline at end of file diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index c7d79fb5d2..8567725763 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -70,9 +70,10 @@ export default function dom( options.css !== false ); - const props = component.props.filter(x => component.writable_declarations.has(x.name)); + const props = component.vars.filter(variable => !variable.module && variable.export_name); + const writable_props = props.filter(variable => variable.writable); - const set = (component.meta.props || props.length > 0 || renderer.slots.size > 0) + const set = (component.meta.props || writable_props.length > 0 || renderer.slots.size > 0) ? deindent` $$props => { ${component.meta.props && deindent` @@ -80,8 +81,8 @@ export default function dom( @assign(${component.meta.props}, $$props); $$invalidate('${component.meta.props_object}', ${component.meta.props_object}); `} - ${props.map(prop => - `if ('${prop.as}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.as});`)} + ${writable_props.map(prop => + `if ('${prop.export_name}' in $$props) $$invalidate('${prop.name}', ${prop.name} = $$props.${prop.export_name});`)} ${renderer.slots.size > 0 && `if ('$$scope' in $$props) $$invalidate('$$scope', $$scope = $$props.$$scope);`} } @@ -93,32 +94,34 @@ export default function dom( const not_equal = component.meta.immutable ? `@not_equal` : `@safe_not_equal`; let dev_props_check; - component.props.forEach(x => { - if (component.imported_declarations.has(x.name) || component.hoistable_names.has(x.name)) { + props.forEach(x => { + const variable = component.var_lookup.get(x.name); + + if (variable.hoistable) { body.push(deindent` - get ${x.as}() { + get ${x.export_name}() { return ${x.name}; } `); } else { body.push(deindent` - get ${x.as}() { + get ${x.export_name}() { return this.$$.ctx.${x.name}; } `); } - if (component.writable_declarations.has(x.as) && !renderer.readonly.has(x.as)) { + if (variable.writable && !renderer.readonly.has(x.export_name)) { body.push(deindent` - set ${x.as}(${x.name}) { + set ${x.export_name}(${x.name}) { this.$set({ ${x.name} }); @flush(); } `); } else if (component.options.dev) { body.push(deindent` - set ${x.as}(value) { - throw new Error("<${component.tag}>: Cannot set read-only property '${x.as}'"); + set ${x.export_name}(value) { + throw new Error("<${component.tag}>: Cannot set read-only property '${x.export_name}'"); } `); } @@ -127,30 +130,28 @@ export default function dom( if (component.options.dev) { // TODO check no uunexpected props were passed, as well as // checking that expected ones were passed - const expected = component.props - .map(x => x.name) - .filter(name => !component.initialised_declarations.has(name)); + const expected = props.filter(prop => !prop.initialised); if (expected.length) { dev_props_check = deindent` const { ctx } = this.$$; const props = ${options.customElement ? `this.attributes` : `options.props || {}`}; - ${expected.map(name => deindent` - if (ctx.${name} === undefined && !('${name}' in props)) { - console.warn("<${component.tag}> was created without expected prop '${name}'"); + ${expected.map(prop => deindent` + if (ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) { + console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); }`)} `; } } // instrument assignments - if (component.instance_script) { + if (component.ast.instance) { let scope = component.instance_scope; let map = component.instance_scope_map; let pending_assignments = new Set(); - walk(component.instance_script.content, { + walk(component.ast.instance.content, { enter: (node, parent) => { if (map.has(node)) { scope = map.get(node); @@ -177,9 +178,11 @@ export default function dom( code.overwrite(node.start, node.end, dirty.map(n => `$$invalidate('${n}', ${n})`).join('; ')); } else { names.forEach(name => { - if (component.imported_declarations.has(name)) return; if (scope.findOwner(name) !== component.instance_scope) return; + const variable = component.var_lookup.get(name); + if (variable && variable.hoistable) return; + pending_assignments.add(name); component.has_reactive_assignments = true; }); @@ -189,9 +192,11 @@ export default function dom( else if (node.type === 'UpdateExpression') { const { name } = getObject(node.argument); - if (component.imported_declarations.has(name)) return; if (scope.findOwner(name) !== component.instance_scope) return; + const variable = component.var_lookup.get(name); + if (variable && variable.hoistable) return; + pending_assignments.add(name); component.has_reactive_assignments = true; } @@ -238,7 +243,7 @@ export default function dom( } const args = ['$$self']; - if (component.props.length > 0 || component.has_reactive_assignments || renderer.slots.size > 0) { + if (props.length > 0 || component.has_reactive_assignments || renderer.slots.size > 0) { args.push('$$props', '$$invalidate'); } @@ -252,22 +257,19 @@ export default function dom( ${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')} `); - const filtered_declarations = component.declarations.filter(name => { - if (component.hoistable_names.has(name)) return false; - if (component.imported_declarations.has(name)) return false; - if (component.props.find(p => p.as === name)) return true; - return component.template_references.has(name); - }); + const filtered_declarations = component.vars.filter(variable => { + return (variable.referenced || variable.export_name) && !variable.hoistable; + }).map(variable => variable.name); + + const filtered_props = props.filter(prop => { + const variable = component.var_lookup.get(prop.name); - const filtered_props = component.props.filter(prop => { - if (component.hoistable_names.has(prop.name)) return false; - if (component.imported_declarations.has(prop.name)) return false; + if (variable.hoistable) return false; if (prop.name[0] === '$') return false; return true; }); - const reactive_stores = Array.from(component.template_references).filter(n => n[0] === '$'); - filtered_declarations.push(...reactive_stores); + const reactive_stores = component.vars.filter(variable => variable.name[0] === '$'); if (renderer.slots.size > 0) { const arr = Array.from(renderer.slots); @@ -283,7 +285,6 @@ export default function dom( filtered_props.length > 0 || component.partly_hoisted.length > 0 || filtered_declarations.length > 0 || - reactive_stores.length > 0 || component.reactive_declarations.length > 0 ); @@ -297,13 +298,13 @@ export default function dom( }); const user_code = component.javascript || ( - component.ast.js.length === 0 && filtered_props.length > 0 + !component.ast.instance && !component.ast.module && filtered_props.length > 0 ? `let { ${filtered_props.map(x => x.name).join(', ')} } = $$props;` : null ); const reactive_store_subscriptions = reactive_stores.length > 0 && reactive_stores - .map(name => deindent` + .map(({ name }) => deindent` let ${name}; ${component.options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`} $$self.$$.on_destroy.push(${name.slice(1)}.subscribe($$value => { ${name} = $$value; $$invalidate('${name}', ${name}); })); @@ -354,7 +355,7 @@ export default function dom( @insert(options.target, this, options.anchor); } - ${(component.props.length > 0 || component.meta.props) && deindent` + ${(props.length > 0 || component.meta.props) && deindent` if (options.props) { this.$set(options.props); @flush(); @@ -363,7 +364,7 @@ export default function dom( } static get observedAttributes() { - return ${JSON.stringify(component.props.map(x => x.as))}; + return ${JSON.stringify(props.map(x => x.export_name))}; } ${body.length > 0 && body.join('\n\n')} diff --git a/src/compile/render-dom/wrappers/AwaitBlock.ts b/src/compile/render-dom/wrappers/AwaitBlock.ts index a5926fa8d7..e896510fa8 100644 --- a/src/compile/render-dom/wrappers/AwaitBlock.ts +++ b/src/compile/render-dom/wrappers/AwaitBlock.ts @@ -67,7 +67,7 @@ export default class AwaitBlockWrapper extends Wrapper { this.cannotUseInnerHTML(); - block.addDependencies(this.node.expression.dynamic_dependencies); + block.addDependencies(this.node.expression.dependencies); let isDynamic = false; let hasIntros = false; @@ -181,9 +181,11 @@ export default class AwaitBlockWrapper extends Wrapper { } const conditions = []; - if (this.node.expression.dependencies.size > 0) { + const dependencies = this.node.expression.dynamic_dependencies(); + + if (dependencies.length > 0) { conditions.push( - `(${[...this.node.expression.dependencies].map(dep => `'${dep}' in changed`).join(' || ')})` + `(${dependencies.map(dep => `'${dep}' in changed`).join(' || ')})` ); } diff --git a/src/compile/render-dom/wrappers/DebugTag.ts b/src/compile/render-dom/wrappers/DebugTag.ts index cec40baaf7..294276a4ea 100644 --- a/src/compile/render-dom/wrappers/DebugTag.ts +++ b/src/compile/render-dom/wrappers/DebugTag.ts @@ -45,7 +45,7 @@ export default class DebugTagWrapper extends Wrapper { const dependencies = new Set(); this.node.expressions.forEach(expression => { - addToSet(dependencies, expression.dynamic_dependencies); + addToSet(dependencies, expression.dependencies); }); const condition = [...dependencies].map(d => `changed.${d}`).join(' || '); diff --git a/src/compile/render-dom/wrappers/EachBlock.ts b/src/compile/render-dom/wrappers/EachBlock.ts index f756a9ce6e..0fb1018ccf 100644 --- a/src/compile/render-dom/wrappers/EachBlock.ts +++ b/src/compile/render-dom/wrappers/EachBlock.ts @@ -74,8 +74,8 @@ export default class EachBlockWrapper extends Wrapper { super(renderer, block, parent, node); this.cannotUseInnerHTML(); - const { dynamic_dependencies } = node.expression; - block.addDependencies(dynamic_dependencies); + const { dependencies } = node.expression; + block.addDependencies(dependencies); this.block = block.child({ comment: createDebuggingComment(this.node, this.renderer.component), diff --git a/src/compile/render-dom/wrappers/Element/Attribute.ts b/src/compile/render-dom/wrappers/Element/Attribute.ts index dbb53b665f..ba4186f360 100644 --- a/src/compile/render-dom/wrappers/Element/Attribute.ts +++ b/src/compile/render-dom/wrappers/Element/Attribute.ts @@ -160,8 +160,8 @@ export default class AttributeWrapper { } // only add an update if mutations are involved (or it's a select?) - if (this.node.parent.scope.containsMutable(this.node.dependencies) || isSelectValueAttribute) { - const dependencies = Array.from(this.node.dependencies); + const dependencies = this.node.get_dependencies(); + if (dependencies.length > 0 || isSelectValueAttribute) { const changedCheck = ( (block.hasOutros ? `!#current || ` : '') + dependencies.map(dependency => `changed.${dependency}`).join(' || ') diff --git a/src/compile/render-dom/wrappers/Element/Binding.ts b/src/compile/render-dom/wrappers/Element/Binding.ts index 7604c16cc2..0dd41b6e62 100644 --- a/src/compile/render-dom/wrappers/Element/Binding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding.ts @@ -37,14 +37,14 @@ export default class BindingWrapper { this.node = node; this.parent = parent; - const { dynamic_dependencies } = this.node.expression; + const { dependencies } = this.node.expression; - block.addDependencies(dynamic_dependencies); + block.addDependencies(dependencies); // TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`? if (parent.node.name === 'select') { - parent.selectBindingDependencies = dynamic_dependencies; - dynamic_dependencies.forEach((prop: string) => { + parent.selectBindingDependencies = dependencies; + dependencies.forEach((prop: string) => { parent.renderer.component.indirectDependencies.set(prop, new Set()); }); } @@ -106,7 +106,7 @@ export default class BindingWrapper { let updateConditions: string[] = this.needsLock ? [`!${lock}`] : []; - const dependencyArray = [...this.node.expression.dynamic_dependencies] + const dependencyArray = [...this.node.expression.dependencies] if (dependencyArray.length === 1) { updateConditions.push(`changed.${dependencyArray[0]}`) diff --git a/src/compile/render-dom/wrappers/Element/StyleAttribute.ts b/src/compile/render-dom/wrappers/Element/StyleAttribute.ts index 7af2a8dc79..2ec4a1c98e 100644 --- a/src/compile/render-dom/wrappers/Element/StyleAttribute.ts +++ b/src/compile/render-dom/wrappers/Element/StyleAttribute.ts @@ -35,7 +35,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper { } else { const snippet = chunk.render(); - addToSet(propDependencies, chunk.dynamic_dependencies); + addToSet(propDependencies, chunk.dependencies); return chunk.getPrecedence() <= 13 ? `(${snippet})` : snippet; } diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 97d0a90459..77cb4bbd9f 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -132,7 +132,7 @@ export default class ElementWrapper extends Wrapper { const name = attribute.getStaticValue(); if (!(owner as InlineComponentWrapper).slots.has(name)) { - const child_block = block.child({ + const child_block = block.parent.child({ comment: createDebuggingComment(node, this.renderer.component), name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`) }); @@ -141,6 +141,7 @@ export default class ElementWrapper extends Wrapper { (owner as InlineComponentWrapper).slots.set(name, { block: child_block, + scope: this.node.scope, fn }); this.renderer.blocks.push(child_block); @@ -173,13 +174,13 @@ export default class ElementWrapper extends Wrapper { // add directive and handler dependencies [node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => { if (directive && directive.expression) { - block.addDependencies(directive.expression.dynamic_dependencies); + block.addDependencies(directive.expression.dependencies); } }); node.handlers.forEach(handler => { if (handler.expression) { - block.addDependencies(handler.expression.dynamic_dependencies); + block.addDependencies(handler.expression.dependencies); } }); @@ -404,8 +405,12 @@ export default class ElementWrapper extends Wrapper { groups.forEach(group => { const handler = renderer.component.getUniqueName(`${this.var}_${group.events.join('_')}_handler`); - renderer.component.declarations.push(handler); - renderer.component.template_references.add(handler); + + renderer.component.add_var({ + name: handler, + internal: true, + referenced: true + }); // TODO figure out how to handle locks const needsLock = group.bindings.some(binding => binding.needsLock); @@ -505,8 +510,12 @@ export default class ElementWrapper extends Wrapper { const this_binding = this.bindings.find(b => b.node.name === 'this'); if (this_binding) { const name = renderer.component.getUniqueName(`${this.var}_binding`); - renderer.component.declarations.push(name); - renderer.component.template_references.add(name); + + renderer.component.add_var({ + name, + internal: true, + referenced: true + }); const { handler, object } = this_binding; diff --git a/src/compile/render-dom/wrappers/IfBlock.ts b/src/compile/render-dom/wrappers/IfBlock.ts index c3a8157dbe..ce2d5fda0f 100644 --- a/src/compile/render-dom/wrappers/IfBlock.ts +++ b/src/compile/render-dom/wrappers/IfBlock.ts @@ -87,7 +87,7 @@ export default class IfBlockWrapper extends Wrapper { this.branches.push(branch); blocks.push(branch.block); - block.addDependencies(node.expression.dynamic_dependencies); + block.addDependencies(node.expression.dependencies); if (branch.block.dependencies.size > 0) { isDynamic = true; diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index 75bff825c6..f4dfe6f123 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -15,10 +15,11 @@ import createDebuggingComment from '../../../../utils/createDebuggingComment'; import sanitize from '../../../../utils/sanitize'; import { get_context_merger } from '../shared/get_context_merger'; import EachBlock from '../../../nodes/EachBlock'; +import TemplateScope from '../../../nodes/shared/TemplateScope'; export default class InlineComponentWrapper extends Wrapper { var: string; - slots: Map<string, { block: Block, fn?: string }> = new Map(); + slots: Map<string, { block: Block, scope: TemplateScope, fn?: string }> = new Map(); node: InlineComponent; fragment: FragmentWrapper; @@ -35,7 +36,7 @@ export default class InlineComponentWrapper extends Wrapper { this.cannotUseInnerHTML(); if (this.node.expression) { - block.addDependencies(this.node.expression.dynamic_dependencies); + block.addDependencies(this.node.expression.dependencies); } this.node.attributes.forEach(attr => { @@ -52,12 +53,12 @@ export default class InlineComponentWrapper extends Wrapper { (eachBlock as EachBlock).has_binding = true; } - block.addDependencies(binding.expression.dynamic_dependencies); + block.addDependencies(binding.expression.dependencies); }); this.node.handlers.forEach(handler => { if (handler.expression) { - block.addDependencies(handler.expression.dynamic_dependencies); + block.addDependencies(handler.expression.dependencies); } }); @@ -79,6 +80,7 @@ export default class InlineComponentWrapper extends Wrapper { this.slots.set('default', { block: default_slot, + scope: this.node.scope, fn }); this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, stripWhitespace, nextSibling); @@ -147,9 +149,14 @@ export default class InlineComponentWrapper extends Wrapper { const fragment_dependencies = new Set(); this.slots.forEach(slot => { slot.block.dependencies.forEach(name => { - if (renderer.component.mutable_props.has(name)) { - fragment_dependencies.add(name); - } + const is_let = slot.scope.is_let(name); + const variable = renderer.component.var_lookup.get(name); + + if (is_let) fragment_dependencies.add(name); + + if (!variable) return; + if (variable.mutated || variable.reassigned) fragment_dependencies.add(name); + if (!variable.module && variable.writable && variable.export_name) fragment_dependencies.add(name); }); }); @@ -233,8 +240,12 @@ export default class InlineComponentWrapper extends Wrapper { if (binding.name === 'this') { const fn = component.getUniqueName(`${this.var}_binding`); - component.declarations.push(fn); - component.template_references.add(fn); + + component.add_var({ + name: fn, + internal: true, + referenced: true + }); let lhs; let object; @@ -264,8 +275,12 @@ export default class InlineComponentWrapper extends Wrapper { } const name = component.getUniqueName(`${this.var}_${binding.name}_binding`); - component.declarations.push(name); - component.template_references.add(name); + + component.add_var({ + name, + internal: true, + referenced: true + }); const updating = block.getUniqueName(`updating_${binding.name}`); block.addVariable(updating); @@ -279,7 +294,7 @@ export default class InlineComponentWrapper extends Wrapper { ); updates.push(deindent` - if (!${updating} && ${[...binding.expression.dynamic_dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) { + if (!${updating} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) { ${name_changes}${quotePropIfNecessary(binding.name)} = ${snippet}; } `); diff --git a/src/compile/render-dom/wrappers/Title.ts b/src/compile/render-dom/wrappers/Title.ts index 733e4c845d..d82d950639 100644 --- a/src/compile/render-dom/wrappers/Title.ts +++ b/src/compile/render-dom/wrappers/Title.ts @@ -34,7 +34,7 @@ export default class TitleWrapper extends Wrapper { // single {tag} — may be a non-string const { expression } = this.node.children[0]; value = expression.render(block); - addToSet(allDependencies, expression.dynamic_dependencies); + addToSet(allDependencies, expression.dependencies); } else { // '{foo} {bar}' — treat as string concatenation value = @@ -46,7 +46,7 @@ export default class TitleWrapper extends Wrapper { } else { const snippet = chunk.expression.render(block); - chunk.expression.dynamic_dependencies.forEach(d => { + chunk.expression.dependencies.forEach(d => { allDependencies.add(d); }); diff --git a/src/compile/render-dom/wrappers/Window.ts b/src/compile/render-dom/wrappers/Window.ts index c986d3726f..18a15dcd77 100644 --- a/src/compile/render-dom/wrappers/Window.ts +++ b/src/compile/render-dom/wrappers/Window.ts @@ -118,16 +118,18 @@ export default class WindowWrapper extends Wrapper { `); } - component.declarations.push(handler_name); - component.template_references.add(handler_name); + component.add_var({ + name: handler_name, + internal: true, + referenced: true + }); + component.partly_hoisted.push(deindent` function ${handler_name}() { ${props.map(prop => `${prop.name} = window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)} } `); - - block.builders.init.addBlock(deindent` @add_render_callback(ctx.${handler_name}); `); diff --git a/src/compile/render-dom/wrappers/shared/Tag.ts b/src/compile/render-dom/wrappers/shared/Tag.ts index 475c0d8a70..8e72929acd 100644 --- a/src/compile/render-dom/wrappers/shared/Tag.ts +++ b/src/compile/render-dom/wrappers/shared/Tag.ts @@ -11,14 +11,14 @@ export default class Tag extends Wrapper { super(renderer, block, parent, node); this.cannotUseInnerHTML(); - block.addDependencies(node.expression.dynamic_dependencies); + block.addDependencies(node.expression.dependencies); } renameThisMethod( block: Block, update: ((value: string) => string) ) { - const dependencies = this.node.expression.dynamic_dependencies; + const dependencies = this.node.expression.dynamic_dependencies(); const snippet = this.node.expression.render(block); const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`); @@ -26,27 +26,22 @@ export default class Tag extends Wrapper { if (this.node.shouldCache) block.addVariable(value, snippet); - if (dependencies.size) { + if (dependencies.length > 0) { const changedCheck = ( (block.hasOutros ? `!#current || ` : '') + - [...dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ') + dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ') ); const updateCachedValue = `${value} !== (${value} = ${snippet})`; const condition =this.node.shouldCache - ? dependencies.size > 0 - ? `(${changedCheck}) && ${updateCachedValue}` - : updateCachedValue + ? `(${changedCheck}) && ${updateCachedValue}` : changedCheck; - // only update if there's a mutation involved - if (this.node.expression.template_scope.containsMutable(dependencies)) { - block.builders.update.addConditional( - condition, - update(content) - ); - } + block.builders.update.addConditional( + condition, + update(content) + ); } return { init: content }; diff --git a/src/compile/render-dom/wrappers/shared/addActions.ts b/src/compile/render-dom/wrappers/shared/addActions.ts index 48c2287f87..68f203e09a 100644 --- a/src/compile/render-dom/wrappers/shared/addActions.ts +++ b/src/compile/render-dom/wrappers/shared/addActions.ts @@ -1,4 +1,3 @@ -import Renderer from '../../Renderer'; import Block from '../../Block'; import Action from '../../../nodes/Action'; import Component from '../../../Component'; @@ -15,7 +14,7 @@ export default function addActions( if (expression) { snippet = expression.render(block); - dependencies = expression.dynamic_dependencies; + dependencies = expression.dynamic_dependencies(); } const name = block.getUniqueName( @@ -23,20 +22,17 @@ export default function addActions( ); block.addVariable(name); - const fn = component.imported_declarations.has(action.name) || component.hoistable_names.has(action.name) - ? action.name - : `ctx.${action.name}`; - component.template_references.add(action.name); + const fn = component.qualify(action.name); block.builders.mount.addLine( `${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};` ); - if (dependencies && dependencies.size > 0) { + if (dependencies && dependencies.length > 0) { let conditional = `typeof ${name}.update === 'function' && `; - const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || '); - conditional += dependencies.size > 1 ? `(${deps})` : deps; + const deps = dependencies.map(dependency => `changed.${dependency}`).join(' || '); + conditional += dependencies.length > 1 ? `(${deps})` : deps; block.builders.update.addConditional( conditional, diff --git a/src/compile/render-ssr/index.ts b/src/compile/render-ssr/index.ts index 1289b9ef88..351a36570a 100644 --- a/src/compile/render-ssr/index.ts +++ b/src/compile/render-ssr/index.ts @@ -24,17 +24,18 @@ export default function ssr( let user_code; + // TODO remove this, just use component.symbols everywhere + const props = component.vars.filter(variable => !variable.module && variable.export_name); + if (component.javascript) { component.rewrite_props(); user_code = component.javascript; - } else if (component.ast.js.length === 0 && component.props.length > 0) { - const props = component.props.map(prop => prop.as).filter(name => name[0] !== '$'); - - user_code = `let { ${props.join(', ')} } = $$props;` + } else if (!component.ast.instance && !component.ast.module && props.length > 0) { + user_code = `let { ${props.map(prop => prop.export_name).join(', ')} } = $$props;` } - const reactive_stores = Array.from(component.template_references).filter(n => n[0] === '$'); - const reactive_store_values = reactive_stores.map(name => { + const reactive_stores = component.vars.filter(variable => variable.name[0] === '$'); + const reactive_store_values = reactive_stores.map(({ name }) => { const assignment = `const ${name} = @get_store_value(${name.slice(1)});`; return component.options.dev @@ -44,8 +45,8 @@ export default function ssr( // TODO only do this for props with a default value const parent_bindings = component.javascript - ? component.props.map(prop => { - return `if ($$props.${prop.as} === void 0 && $$bindings.${prop.as} && ${prop.name} !== void 0) $$bindings.${prop.as}(${prop.name});`; + ? props.map(prop => { + return `if ($$props.${prop.export_name} === void 0 && $$bindings.${prop.export_name} && ${prop.name} !== void 0) $$bindings.${prop.export_name}(${prop.name});`; }) : []; diff --git a/src/interfaces.ts b/src/interfaces.ts index ab2fcb1944..157ad56eb7 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -21,7 +21,8 @@ export interface Parser { export interface Ast { html: Node; css: Node; - js: Node; + instance: Node; + module: Node; } export interface Warning { @@ -76,4 +77,22 @@ export interface CustomElementOptions { export interface AppendTarget { slots: Record<string, string>; slotStack: string[] +} + +export interface Var { + name: string; + export_name?: string; // the `bar` in `export { foo as bar }` + injected?: boolean; + module?: boolean; + mutated?: boolean; + reassigned?: boolean; + referenced?: boolean; + writable?: boolean; + + // used internally, but not exposed + global?: boolean; + implicit?: boolean; // logic-less template references + internal?: boolean; // event handlers, bindings + initialised?: boolean; + hoistable?: boolean; } \ No newline at end of file diff --git a/src/parse/acorn.ts b/src/parse/acorn.ts index 2f0cd2ec20..cfb3cdbfbd 100644 --- a/src/parse/acorn.ts +++ b/src/parse/acorn.ts @@ -3,13 +3,13 @@ import dynamicImport from 'acorn-dynamic-import'; const Parser = acorn.Parser.extend(dynamicImport); -export const parse = (source: string, options: any) => Parser.parse(source, { +export const parse = (source: string) => Parser.parse(source, { sourceType: 'module', ecmaVersion: 9, preserveParens: true }); -export const parseExpressionAt = (source: string, index: number, options: any) => Parser.parseExpressionAt(source, index, { +export const parseExpressionAt = (source: string, index: number) => Parser.parseExpressionAt(source, index, { ecmaVersion: 9, preserveParens: true }); \ No newline at end of file diff --git a/src/parse/index.ts b/src/parse/index.ts index bc37229ef9..4b1e0a225c 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -226,9 +226,27 @@ export default function parse( }, parser.css[1].start); } + const instance_scripts = parser.js.filter(script => script.context === 'default'); + const module_scripts = parser.js.filter(script => script.context === 'module'); + + if (instance_scripts.length > 1) { + parser.error({ + code: `invalid-script`, + message: `A component can only have one instance-level <script> element` + }, instance_scripts[1].start); + } + + if (module_scripts.length > 1) { + parser.error({ + code: `invalid-script`, + message: `A component can only have one <script context="module"> element` + }, module_scripts[1].start); + } + return { html: parser.html, - css: parser.css, - js: parser.js, + css: parser.css[0], + instance: instance_scripts[0], + module: module_scripts[0] }; } diff --git a/src/parse/read/script.ts b/src/parse/read/script.ts index 11337db513..172e1a832b 100644 --- a/src/parse/read/script.ts +++ b/src/parse/read/script.ts @@ -5,6 +5,29 @@ import { Node } from '../../interfaces'; const scriptClosingTag = '</script>'; +function get_context(parser: Parser, attributes: Node[], start: number) { + const context = attributes.find(attribute => attribute.name === 'context'); + if (!context) return 'default'; + + if (context.value.length !== 1 || context.value[0].type !== 'Text') { + parser.error({ + code: 'invalid-script', + message: `context attribute must be static` + }, start); + } + + const value = context.value[0].data; + + if (value !== 'module') { + parser.error({ + code: `invalid-script`, + message: `If the context attribute is supplied, its value must be "module"` + }, context.start); + } + + return value; +} + export default function readScript(parser: Parser, start: number, attributes: Node[]) { const scriptStart = parser.index; const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart); @@ -30,7 +53,7 @@ export default function readScript(parser: Parser, start: number, attributes: No return { start, end: parser.index, - attributes, + context: get_context(parser, attributes, start), content: ast, }; } diff --git a/src/utils/annotateWithScopes.ts b/src/utils/annotateWithScopes.ts index 96459e1ede..adf7619bed 100644 --- a/src/utils/annotateWithScopes.ts +++ b/src/utils/annotateWithScopes.ts @@ -52,6 +52,10 @@ export function createScopes(expression: Node) { }, }); + scope.declarations.forEach((node, name) => { + globals.delete(name); + }); + return { map, scope, globals }; } @@ -60,7 +64,6 @@ export class Scope { block: boolean; declarations: Map<string, Node> = new Map(); - writable_declarations: Set<string> = new Set(); initialised_declarations: Set<string> = new Set(); constructor(parent: Scope, block: boolean) { @@ -72,13 +75,11 @@ export class Scope { if (node.kind === 'var' && this.block && this.parent) { this.parent.addDeclaration(node); } else if (node.type === 'VariableDeclaration') { - const writable = node.kind !== 'const'; const initialised = !!node.init; node.declarations.forEach((declarator: Node) => { extractNames(declarator.id).forEach(name => { this.declarations.set(name, node); - if (writable) this.writable_declarations.add(name); if (initialised) this.initialised_declarations.add(name); }); }); diff --git a/test/parser/index.js b/test/parser/index.js index 8f20c77abd..900cc3ae05 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -31,7 +31,8 @@ describe('parse', () => { assert.deepEqual(ast.html, expectedOutput.html); assert.deepEqual(ast.css, expectedOutput.css); - assert.deepEqual(ast.js, expectedOutput.js); + assert.deepEqual(ast.instance, expectedOutput.instance); + assert.deepEqual(ast.module, expectedOutput.module); } catch (err) { if (err.name !== 'ParseError') throw err; if (!expectedError) throw err; diff --git a/test/parser/samples/action-with-call/output.json b/test/parser/samples/action-with-call/output.json index 521599544b..d6aaa72892 100644 --- a/test/parser/samples/action-with-call/output.json +++ b/test/parser/samples/action-with-call/output.json @@ -42,6 +42,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/action-with-identifier/output.json b/test/parser/samples/action-with-identifier/output.json index 800ab800be..d58b9097b7 100644 --- a/test/parser/samples/action-with-identifier/output.json +++ b/test/parser/samples/action-with-identifier/output.json @@ -28,6 +28,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/action-with-literal/output.json b/test/parser/samples/action-with-literal/output.json index 0705cd9e39..4a6f596d10 100644 --- a/test/parser/samples/action-with-literal/output.json +++ b/test/parser/samples/action-with-literal/output.json @@ -29,6 +29,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/action/output.json b/test/parser/samples/action/output.json index 68afd79d23..2f553b5efa 100644 --- a/test/parser/samples/action/output.json +++ b/test/parser/samples/action/output.json @@ -23,6 +23,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/animation/output.json b/test/parser/samples/animation/output.json index 585ab3ff1c..8332b3ad04 100644 --- a/test/parser/samples/animation/output.json +++ b/test/parser/samples/animation/output.json @@ -55,6 +55,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-containing-solidus/output.json b/test/parser/samples/attribute-containing-solidus/output.json index 920a9420c8..95372bd77d 100644 --- a/test/parser/samples/attribute-containing-solidus/output.json +++ b/test/parser/samples/attribute-containing-solidus/output.json @@ -36,6 +36,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic-boolean/output.json b/test/parser/samples/attribute-dynamic-boolean/output.json index e50640f279..81a19f49b9 100644 --- a/test/parser/samples/attribute-dynamic-boolean/output.json +++ b/test/parser/samples/attribute-dynamic-boolean/output.json @@ -34,6 +34,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic-reserved/output.json b/test/parser/samples/attribute-dynamic-reserved/output.json index 042712b872..3a830d448f 100644 --- a/test/parser/samples/attribute-dynamic-reserved/output.json +++ b/test/parser/samples/attribute-dynamic-reserved/output.json @@ -34,6 +34,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic/output.json b/test/parser/samples/attribute-dynamic/output.json index f3099f6e62..50d8ec60a5 100644 --- a/test/parser/samples/attribute-dynamic/output.json +++ b/test/parser/samples/attribute-dynamic/output.json @@ -58,6 +58,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-escaped/output.json b/test/parser/samples/attribute-escaped/output.json index 979b54d713..6a4143e674 100644 --- a/test/parser/samples/attribute-escaped/output.json +++ b/test/parser/samples/attribute-escaped/output.json @@ -29,6 +29,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-multiple/output.json b/test/parser/samples/attribute-multiple/output.json index 5864363cdf..668409c0ef 100644 --- a/test/parser/samples/attribute-multiple/output.json +++ b/test/parser/samples/attribute-multiple/output.json @@ -43,6 +43,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-shorthand/output.json b/test/parser/samples/attribute-shorthand/output.json index 69f9329ed1..08ddf5eda9 100644 --- a/test/parser/samples/attribute-shorthand/output.json +++ b/test/parser/samples/attribute-shorthand/output.json @@ -34,6 +34,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-static-boolean/output.json b/test/parser/samples/attribute-static-boolean/output.json index 79ea9e9d11..f63b5734e0 100644 --- a/test/parser/samples/attribute-static-boolean/output.json +++ b/test/parser/samples/attribute-static-boolean/output.json @@ -22,6 +22,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-static/output.json b/test/parser/samples/attribute-static/output.json index cb0d7684da..3185e48736 100644 --- a/test/parser/samples/attribute-static/output.json +++ b/test/parser/samples/attribute-static/output.json @@ -29,6 +29,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/attribute-unquoted/output.json b/test/parser/samples/attribute-unquoted/output.json index 940ac5b01d..c7556f2eba 100644 --- a/test/parser/samples/attribute-unquoted/output.json +++ b/test/parser/samples/attribute-unquoted/output.json @@ -29,6 +29,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/await-then-catch/output.json b/test/parser/samples/await-then-catch/output.json index 1ab557f99d..21fc13eff9 100644 --- a/test/parser/samples/await-then-catch/output.json +++ b/test/parser/samples/await-then-catch/output.json @@ -155,6 +155,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/binding-shorthand/output.json b/test/parser/samples/binding-shorthand/output.json index c80fc5f7f2..3be1db50b4 100644 --- a/test/parser/samples/binding-shorthand/output.json +++ b/test/parser/samples/binding-shorthand/output.json @@ -28,6 +28,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/binding/output.json b/test/parser/samples/binding/output.json index 031d291fa7..5dc173c2a2 100644 --- a/test/parser/samples/binding/output.json +++ b/test/parser/samples/binding/output.json @@ -28,6 +28,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/comment/output.json b/test/parser/samples/comment/output.json index 10d85bf422..89295c188a 100644 --- a/test/parser/samples/comment/output.json +++ b/test/parser/samples/comment/output.json @@ -12,6 +12,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/component-dynamic/output.json b/test/parser/samples/component-dynamic/output.json index 9b08732c38..c2e4e3ee79 100644 --- a/test/parser/samples/component-dynamic/output.json +++ b/test/parser/samples/component-dynamic/output.json @@ -37,6 +37,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/convert-entities-in-element/output.json b/test/parser/samples/convert-entities-in-element/output.json index f481345a02..fb0f5b288e 100644 --- a/test/parser/samples/convert-entities-in-element/output.json +++ b/test/parser/samples/convert-entities-in-element/output.json @@ -21,6 +21,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/convert-entities/output.json b/test/parser/samples/convert-entities/output.json index b3e66a9007..ca1c1356f8 100644 --- a/test/parser/samples/convert-entities/output.json +++ b/test/parser/samples/convert-entities/output.json @@ -12,6 +12,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/css/output.json b/test/parser/samples/css/output.json index e91c31a71a..3127e01c71 100644 --- a/test/parser/samples/css/output.json +++ b/test/parser/samples/css/output.json @@ -27,71 +27,70 @@ } ] }, - "css": [ - { - "start": 16, - "end": 56, - "attributes": [], - "children": [ - { - "type": "Rule", - "selector": { - "type": "SelectorList", - "children": [ - { - "type": "Selector", + "css": { + "start": 16, + "end": 56, + "attributes": [], + "children": [ + { + "type": "Rule", + "selector": { + "type": "SelectorList", + "children": [ + { + "type": "Selector", + "children": [ + { + "type": "TypeSelector", + "name": "div", + "start": 25, + "end": 28 + } + ], + "start": 25, + "end": 28 + } + ], + "start": 25, + "end": 28 + }, + "block": { + "type": "Block", + "children": [ + { + "type": "Declaration", + "important": false, + "property": "color", + "value": { + "type": "Value", "children": [ { - "type": "TypeSelector", - "name": "div", - "start": 25, - "end": 28 + "type": "Identifier", + "name": "red", + "start": 40, + "end": 43 } ], - "start": 25, - "end": 28 - } - ], - "start": 25, - "end": 28 - }, - "block": { - "type": "Block", - "children": [ - { - "type": "Declaration", - "important": false, - "property": "color", - "value": { - "type": "Value", - "children": [ - { - "type": "Identifier", - "name": "red", - "start": 40, - "end": 43 - } - ], - "start": 39, - "end": 43 - }, - "start": 33, + "start": 39, "end": 43 - } - ], - "start": 29, - "end": 47 - }, - "start": 25, + }, + "start": 33, + "end": 43 + } + ], + "start": 29, "end": 47 - } - ], - "content": { - "start": 23, - "end": 48, - "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n" + }, + "start": 25, + "end": 47 } + ], + "content": { + "start": 23, + "end": 48, + "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n" } - ], - "js": [] + }, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/dynamic-import/output.json b/test/parser/samples/dynamic-import/output.json index 55c1bdf756..c0d4a45e0d 100644 --- a/test/parser/samples/dynamic-import/output.json +++ b/test/parser/samples/dynamic-import/output.json @@ -5,201 +5,199 @@ "type": "Fragment", "children": [] }, - "css": [], - "js": [ - { - "start": 0, - "end": 146, - "attributes": [], - "content": { - "type": "Program", - "start": 8, - "end": 137, - "body": [ - { - "type": "ImportDeclaration", - "start": 10, - "end": 43, - "specifiers": [ - { - "type": "ImportSpecifier", + "css": null, + "instance": { + "start": 0, + "end": 146, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 137, + "body": [ + { + "type": "ImportDeclaration", + "start": 10, + "end": 43, + "specifiers": [ + { + "type": "ImportSpecifier", + "start": 19, + "end": 26, + "imported": { + "type": "Identifier", "start": 19, "end": 26, - "imported": { - "type": "Identifier", - "start": 19, - "end": 26, - "name": "onMount" - }, - "local": { - "type": "Identifier", - "start": 19, - "end": 26, - "name": "onMount" - } + "name": "onMount" + }, + "local": { + "type": "Identifier", + "start": 19, + "end": 26, + "name": "onMount" } - ], - "source": { - "type": "Literal", - "start": 34, - "end": 42, - "value": "svelte", - "raw": "'svelte'" } - }, - { - "type": "ExpressionStatement", + ], + "source": { + "type": "Literal", + "start": 34, + "end": 42, + "value": "svelte", + "raw": "'svelte'" + } + }, + { + "type": "ExpressionStatement", + "start": 46, + "end": 136, + "expression": { + "type": "CallExpression", "start": 46, - "end": 136, - "expression": { - "type": "CallExpression", + "end": 135, + "callee": { + "type": "Identifier", "start": 46, - "end": 135, - "callee": { - "type": "Identifier", - "start": 46, - "end": 53, - "name": "onMount" - }, - "arguments": [ - { - "type": "ArrowFunctionExpression", - "start": 54, + "end": 53, + "name": "onMount" + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 54, + "end": 134, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 60, "end": 134, - "id": null, - "generator": false, - "expression": false, - "async": false, - "params": [], - "body": { - "type": "BlockStatement", - "start": 60, - "end": 134, - "body": [ - { - "type": "ExpressionStatement", + "body": [ + { + "type": "ExpressionStatement", + "start": 64, + "end": 131, + "expression": { + "type": "CallExpression", "start": 64, - "end": 131, - "expression": { - "type": "CallExpression", + "end": 130, + "callee": { + "type": "MemberExpression", "start": 64, - "end": 130, - "callee": { - "type": "MemberExpression", + "end": 87, + "object": { + "type": "CallExpression", "start": 64, - "end": 87, - "object": { - "type": "CallExpression", + "end": 82, + "callee": { + "type": "Import", "start": 64, - "end": 82, - "callee": { - "type": "Import", - "start": 64, - "end": 70 - }, - "arguments": [ - { - "type": "Literal", - "start": 71, - "end": 81, - "value": "./foo.js", - "raw": "'./foo.js'" - } - ] - }, - "property": { - "type": "Identifier", - "start": 83, - "end": 87, - "name": "then" + "end": 70 }, - "computed": false + "arguments": [ + { + "type": "Literal", + "start": 71, + "end": 81, + "value": "./foo.js", + "raw": "'./foo.js'" + } + ] + }, + "property": { + "type": "Identifier", + "start": 83, + "end": 87, + "name": "then" }, - "arguments": [ - { - "type": "ArrowFunctionExpression", - "start": 88, + "computed": false + }, + "arguments": [ + { + "type": "ArrowFunctionExpression", + "start": 88, + "end": 129, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 88, + "end": 91, + "name": "foo" + } + ], + "body": { + "type": "BlockStatement", + "start": 95, "end": 129, - "id": null, - "generator": false, - "expression": false, - "async": false, - "params": [ + "body": [ { - "type": "Identifier", - "start": 88, - "end": 91, - "name": "foo" - } - ], - "body": { - "type": "BlockStatement", - "start": 95, - "end": 129, - "body": [ - { - "type": "ExpressionStatement", + "type": "ExpressionStatement", + "start": 100, + "end": 125, + "expression": { + "type": "CallExpression", "start": 100, - "end": 125, - "expression": { - "type": "CallExpression", + "end": 124, + "callee": { + "type": "MemberExpression", "start": 100, - "end": 124, - "callee": { - "type": "MemberExpression", + "end": 111, + "object": { + "type": "Identifier", "start": 100, + "end": 107, + "name": "console" + }, + "property": { + "type": "Identifier", + "start": 108, "end": 111, + "name": "log" + }, + "computed": false + }, + "arguments": [ + { + "type": "MemberExpression", + "start": 112, + "end": 123, "object": { "type": "Identifier", - "start": 100, - "end": 107, - "name": "console" + "start": 112, + "end": 115, + "name": "foo" }, "property": { "type": "Identifier", - "start": 108, - "end": 111, - "name": "log" + "start": 116, + "end": 123, + "name": "default" }, "computed": false - }, - "arguments": [ - { - "type": "MemberExpression", - "start": 112, - "end": 123, - "object": { - "type": "Identifier", - "start": 112, - "end": 115, - "name": "foo" - }, - "property": { - "type": "Identifier", - "start": 116, - "end": 123, - "name": "default" - }, - "computed": false - } - ] - } + } + ] } - ] - } + } + ] } - ] - } + } + ] } - ] - } + } + ] } - ] - } + } + ] } - ], - "sourceType": "module" - } + } + ], + "sourceType": "module" } - ] + } } \ No newline at end of file diff --git a/test/parser/samples/each-block-destructured/output.json b/test/parser/samples/each-block-destructured/output.json index fdca5f82a2..b9efc18906 100644 --- a/test/parser/samples/each-block-destructured/output.json +++ b/test/parser/samples/each-block-destructured/output.json @@ -75,6 +75,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/each-block-else/output.json b/test/parser/samples/each-block-else/output.json index be0ac6d550..aefc8c2f39 100644 --- a/test/parser/samples/each-block-else/output.json +++ b/test/parser/samples/each-block-else/output.json @@ -67,6 +67,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/each-block-indexed/output.json b/test/parser/samples/each-block-indexed/output.json index bda5f86a89..7518652468 100644 --- a/test/parser/samples/each-block-indexed/output.json +++ b/test/parser/samples/each-block-indexed/output.json @@ -63,6 +63,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/each-block-keyed/output.json b/test/parser/samples/each-block-keyed/output.json index a00694fc4e..fe893bcdb9 100644 --- a/test/parser/samples/each-block-keyed/output.json +++ b/test/parser/samples/each-block-keyed/output.json @@ -63,6 +63,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/each-block/output.json b/test/parser/samples/each-block/output.json index a24c32e9ad..c16a71ad5d 100644 --- a/test/parser/samples/each-block/output.json +++ b/test/parser/samples/each-block/output.json @@ -45,6 +45,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/element-with-mustache/output.json b/test/parser/samples/element-with-mustache/output.json index a56cd27967..c8a386d681 100644 --- a/test/parser/samples/element-with-mustache/output.json +++ b/test/parser/samples/element-with-mustache/output.json @@ -38,6 +38,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/element-with-text/output.json b/test/parser/samples/element-with-text/output.json index 1ae33df94f..70f6163c93 100644 --- a/test/parser/samples/element-with-text/output.json +++ b/test/parser/samples/element-with-text/output.json @@ -21,6 +21,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/elements/output.json b/test/parser/samples/elements/output.json index 219799df3e..d216f7f5d8 100644 --- a/test/parser/samples/elements/output.json +++ b/test/parser/samples/elements/output.json @@ -22,6 +22,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/event-handler/output.json b/test/parser/samples/event-handler/output.json index 740e43e48d..b1fe89fc2a 100644 --- a/test/parser/samples/event-handler/output.json +++ b/test/parser/samples/event-handler/output.json @@ -98,6 +98,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/if-block-else/output.json b/test/parser/samples/if-block-else/output.json index 8e96c34efc..dedf2797c4 100644 --- a/test/parser/samples/if-block-else/output.json +++ b/test/parser/samples/if-block-else/output.json @@ -56,6 +56,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/if-block-elseif/output.json b/test/parser/samples/if-block-elseif/output.json index 257eb1e7d2..426e1f7fbc 100644 --- a/test/parser/samples/if-block-elseif/output.json +++ b/test/parser/samples/if-block-elseif/output.json @@ -96,6 +96,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/if-block/output.json b/test/parser/samples/if-block/output.json index ba734c0e56..d560824766 100644 --- a/test/parser/samples/if-block/output.json +++ b/test/parser/samples/if-block/output.json @@ -25,6 +25,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/implicitly-closed-li/output.json b/test/parser/samples/implicitly-closed-li/output.json index 69def7a9a2..caf69d7109 100644 --- a/test/parser/samples/implicitly-closed-li/output.json +++ b/test/parser/samples/implicitly-closed-li/output.json @@ -66,6 +66,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/nbsp/output.json b/test/parser/samples/nbsp/output.json index 7373bc9673..4fa318ce48 100644 --- a/test/parser/samples/nbsp/output.json +++ b/test/parser/samples/nbsp/output.json @@ -21,6 +21,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/raw-mustaches/output.json b/test/parser/samples/raw-mustaches/output.json index 41dab885e0..1d92b21c85 100644 --- a/test/parser/samples/raw-mustaches/output.json +++ b/test/parser/samples/raw-mustaches/output.json @@ -55,6 +55,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/refs/output.json b/test/parser/samples/refs/output.json index 3cd6c4d9fa..2cf5054304 100644 --- a/test/parser/samples/refs/output.json +++ b/test/parser/samples/refs/output.json @@ -28,6 +28,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/script-comment-only/output.json b/test/parser/samples/script-comment-only/output.json index b04d38633d..95ca769b20 100644 --- a/test/parser/samples/script-comment-only/output.json +++ b/test/parser/samples/script-comment-only/output.json @@ -20,19 +20,16 @@ } ] }, - "css": [], - "js": [ - { - "start": 0, - "end": 43, - "attributes": [], - "content": { - "type": "Program", - "start": 8, - "end": 34, - "body": [], - "sourceType": "module" - } + "instance": { + "start": 0, + "end": 43, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 34, + "body": [], + "sourceType": "module" } - ] + } } \ No newline at end of file diff --git a/test/parser/samples/script-comment-trailing-multiline/output.json b/test/parser/samples/script-comment-trailing-multiline/output.json index a32ff799f7..d4a45911a1 100644 --- a/test/parser/samples/script-comment-trailing-multiline/output.json +++ b/test/parser/samples/script-comment-trailing-multiline/output.json @@ -44,46 +44,43 @@ } ] }, - "css": [], - "js": [ - { - "start": 0, - "end": 77, - "attributes": [], - "content": { - "type": "Program", - "start": 8, - "end": 68, - "body": [ - { - "type": "VariableDeclaration", - "start": 10, - "end": 29, - "declarations": [ - { - "type": "VariableDeclarator", + "instance": { + "start": 0, + "end": 77, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 68, + "body": [ + { + "type": "VariableDeclaration", + "start": 10, + "end": 29, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 14, + "end": 28, + "id": { + "type": "Identifier", "start": 14, + "end": 18, + "name": "name" + }, + "init": { + "type": "Literal", + "start": 21, "end": 28, - "id": { - "type": "Identifier", - "start": 14, - "end": 18, - "name": "name" - }, - "init": { - "type": "Literal", - "start": 21, - "end": 28, - "value": "world", - "raw": "'world'" - } + "value": "world", + "raw": "'world'" } - ], - "kind": "let" - } - ], - "sourceType": "module" - } + } + ], + "kind": "let" + } + ], + "sourceType": "module" } - ] + } } \ No newline at end of file diff --git a/test/parser/samples/script-comment-trailing/output.json b/test/parser/samples/script-comment-trailing/output.json index 78012d4438..92d431b558 100644 --- a/test/parser/samples/script-comment-trailing/output.json +++ b/test/parser/samples/script-comment-trailing/output.json @@ -44,46 +44,43 @@ } ] }, - "css": [], - "js": [ - { - "start": 0, - "end": 66, - "attributes": [], - "content": { - "type": "Program", - "start": 8, - "end": 57, - "body": [ - { - "type": "VariableDeclaration", - "start": 10, - "end": 29, - "declarations": [ - { - "type": "VariableDeclarator", + "instance": { + "start": 0, + "end": 66, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 57, + "body": [ + { + "type": "VariableDeclaration", + "start": 10, + "end": 29, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 14, + "end": 28, + "id": { + "type": "Identifier", "start": 14, + "end": 18, + "name": "name" + }, + "init": { + "type": "Literal", + "start": 21, "end": 28, - "id": { - "type": "Identifier", - "start": 14, - "end": 18, - "name": "name" - }, - "init": { - "type": "Literal", - "start": 21, - "end": 28, - "value": "world", - "raw": "'world'" - } + "value": "world", + "raw": "'world'" } - ], - "kind": "let" - } - ], - "sourceType": "module" - } + } + ], + "kind": "let" + } + ], + "sourceType": "module" } - ] + } } \ No newline at end of file diff --git a/test/parser/samples/script/output.json b/test/parser/samples/script/output.json index bf89a0ff02..95966f5dc6 100644 --- a/test/parser/samples/script/output.json +++ b/test/parser/samples/script/output.json @@ -44,46 +44,43 @@ } ] }, - "css": [], - "js": [ - { - "start": 0, - "end": 39, - "attributes": [], - "content": { - "type": "Program", - "start": 8, - "end": 30, - "body": [ - { - "type": "VariableDeclaration", - "start": 10, - "end": 29, - "declarations": [ - { - "type": "VariableDeclarator", + "instance": { + "start": 0, + "end": 39, + "context": "default", + "content": { + "type": "Program", + "start": 8, + "end": 30, + "body": [ + { + "type": "VariableDeclaration", + "start": 10, + "end": 29, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 14, + "end": 28, + "id": { + "type": "Identifier", "start": 14, + "end": 18, + "name": "name" + }, + "init": { + "type": "Literal", + "start": 21, "end": 28, - "id": { - "type": "Identifier", - "start": 14, - "end": 18, - "name": "name" - }, - "init": { - "type": "Literal", - "start": 21, - "end": 28, - "value": "world", - "raw": "'world'" - } + "value": "world", + "raw": "'world'" } - ], - "kind": "let" - } - ], - "sourceType": "module" - } + } + ], + "kind": "let" + } + ], + "sourceType": "module" } - ] + } } \ No newline at end of file diff --git a/test/parser/samples/self-closing-element/output.json b/test/parser/samples/self-closing-element/output.json index f7475b41d7..47eea2f333 100644 --- a/test/parser/samples/self-closing-element/output.json +++ b/test/parser/samples/self-closing-element/output.json @@ -14,6 +14,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/self-reference/output.json b/test/parser/samples/self-reference/output.json index ff111e17ba..de5112a087 100644 --- a/test/parser/samples/self-reference/output.json +++ b/test/parser/samples/self-reference/output.json @@ -73,6 +73,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/space-between-mustaches/output.json b/test/parser/samples/space-between-mustaches/output.json index d66735213d..c89409daeb 100644 --- a/test/parser/samples/space-between-mustaches/output.json +++ b/test/parser/samples/space-between-mustaches/output.json @@ -72,6 +72,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/spread/output.json b/test/parser/samples/spread/output.json index a632dc89ab..3986da3578 100644 --- a/test/parser/samples/spread/output.json +++ b/test/parser/samples/spread/output.json @@ -26,6 +26,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/textarea-children/output.json b/test/parser/samples/textarea-children/output.json index 08f42919ed..3b403458fc 100644 --- a/test/parser/samples/textarea-children/output.json +++ b/test/parser/samples/textarea-children/output.json @@ -44,6 +44,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/transition-intro-no-params/output.json b/test/parser/samples/transition-intro-no-params/output.json index d53b4d9d88..edb15457e6 100644 --- a/test/parser/samples/transition-intro-no-params/output.json +++ b/test/parser/samples/transition-intro-no-params/output.json @@ -32,6 +32,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/transition-intro/output.json b/test/parser/samples/transition-intro/output.json index 6405d0bad5..5583dd89bc 100644 --- a/test/parser/samples/transition-intro/output.json +++ b/test/parser/samples/transition-intro/output.json @@ -60,6 +60,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/unusual-identifier/output.json b/test/parser/samples/unusual-identifier/output.json index fe9b175128..c0d4ecc3ff 100644 --- a/test/parser/samples/unusual-identifier/output.json +++ b/test/parser/samples/unusual-identifier/output.json @@ -45,6 +45,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/whitespace-leading-trailing/output.json b/test/parser/samples/whitespace-leading-trailing/output.json index 7dbf259721..f164af01ba 100644 --- a/test/parser/samples/whitespace-leading-trailing/output.json +++ b/test/parser/samples/whitespace-leading-trailing/output.json @@ -27,6 +27,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/whitespace-normal/output.json b/test/parser/samples/whitespace-normal/output.json index c150b53f04..e4ce956331 100644 --- a/test/parser/samples/whitespace-normal/output.json +++ b/test/parser/samples/whitespace-normal/output.json @@ -62,6 +62,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/parser/samples/yield/output.json b/test/parser/samples/yield/output.json index b16ab436f6..16ea79d8e8 100644 --- a/test/parser/samples/yield/output.json +++ b/test/parser/samples/yield/output.json @@ -17,6 +17,7 @@ } ] }, - "css": [], - "js": [] + "css": null, + "instance": null, + "module": null } \ No newline at end of file diff --git a/test/stats/samples/implicit-action/_config.js b/test/stats/samples/implicit-action/_config.js new file mode 100644 index 0000000000..71c255d54b --- /dev/null +++ b/test/stats/samples/implicit-action/_config.js @@ -0,0 +1,5 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, []); + }, +}; diff --git a/test/stats/samples/implicit-action/input.html b/test/stats/samples/implicit-action/input.html new file mode 100644 index 0000000000..466495d255 --- /dev/null +++ b/test/stats/samples/implicit-action/input.html @@ -0,0 +1 @@ +<div use:foo/> \ No newline at end of file diff --git a/test/stats/samples/implicit/_config.js b/test/stats/samples/implicit/_config.js new file mode 100644 index 0000000000..71c255d54b --- /dev/null +++ b/test/stats/samples/implicit/_config.js @@ -0,0 +1,5 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, []); + }, +}; diff --git a/test/stats/samples/implicit/input.html b/test/stats/samples/implicit/input.html new file mode 100644 index 0000000000..e076ac55f8 --- /dev/null +++ b/test/stats/samples/implicit/input.html @@ -0,0 +1 @@ +{foo} \ No newline at end of file diff --git a/test/stats/samples/imports/_config.js b/test/stats/samples/imports/_config.js index f7c15183dd..287c5837ad 100644 --- a/test/stats/samples/imports/_config.js +++ b/test/stats/samples/imports/_config.js @@ -1,17 +1,35 @@ export default { test(assert, stats) { - assert.deepEqual(stats.imports, [ + assert.deepEqual(stats.vars, [ { - source: 'x', - specifiers: [{ name: 'default', as: 'x' }] + name: 'x', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: false, + writable: false }, { - source: 'y', - specifiers: [{ name: 'y', as: 'y' }] + name: 'y', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: false, + writable: false }, { - source: 'z', - specifiers: [{ name: '*', as: 'z' }] + name: 'z', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: false, + writable: false } ]); } diff --git a/test/stats/samples/mutated-vs-reassigned-bindings/_config.js b/test/stats/samples/mutated-vs-reassigned-bindings/_config.js new file mode 100644 index 0000000000..a1d027cbb5 --- /dev/null +++ b/test/stats/samples/mutated-vs-reassigned-bindings/_config.js @@ -0,0 +1,26 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, [ + { + name: 'count', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: true, + referenced: true, + writable: true + }, + { + name: 'user', + export_name: null, + injected: false, + module: false, + mutated: true, + reassigned: false, + referenced: true, + writable: false + } + ]); + } +}; \ No newline at end of file diff --git a/test/stats/samples/mutated-vs-reassigned-bindings/input.html b/test/stats/samples/mutated-vs-reassigned-bindings/input.html new file mode 100644 index 0000000000..c1186ad3a7 --- /dev/null +++ b/test/stats/samples/mutated-vs-reassigned-bindings/input.html @@ -0,0 +1,7 @@ +<script> + let count; + const user = { name: 'world' }; +</script> + +<input bind:value={user.name}> +<input type=number bind:value={count}> \ No newline at end of file diff --git a/test/stats/samples/mutated-vs-reassigned/_config.js b/test/stats/samples/mutated-vs-reassigned/_config.js new file mode 100644 index 0000000000..a1d027cbb5 --- /dev/null +++ b/test/stats/samples/mutated-vs-reassigned/_config.js @@ -0,0 +1,26 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, [ + { + name: 'count', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: true, + referenced: true, + writable: true + }, + { + name: 'user', + export_name: null, + injected: false, + module: false, + mutated: true, + reassigned: false, + referenced: true, + writable: false + } + ]); + } +}; \ No newline at end of file diff --git a/test/stats/samples/mutated-vs-reassigned/input.html b/test/stats/samples/mutated-vs-reassigned/input.html new file mode 100644 index 0000000000..6a5e604c26 --- /dev/null +++ b/test/stats/samples/mutated-vs-reassigned/input.html @@ -0,0 +1,7 @@ +<script> + let count; + const user = { name: 'world' }; +</script> + +<input on:input="{e => user.name = e.value}"> +<input type=number on:input="{e => count = +e.value}"> \ No newline at end of file diff --git a/test/stats/samples/props/_config.js b/test/stats/samples/props/_config.js index 50c01e2ffb..cbec831f61 100644 --- a/test/stats/samples/props/_config.js +++ b/test/stats/samples/props/_config.js @@ -1,5 +1,46 @@ export default { test(assert, stats) { - assert.deepEqual(stats.props.sort(), ['cats', 'name']); + assert.deepEqual(stats.vars, [ + { + name: 'name', + export_name: 'name', + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: true, + writable: true + }, + { + name: 'cats', + export_name: 'cats', + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: true, + writable: true + }, + { + name: 'foo', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: false, + writable: true + }, + { + name: 'bar', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: true, + referenced: true, + writable: true + } + ]); } }; \ No newline at end of file diff --git a/test/stats/samples/store-referenced/_config.js b/test/stats/samples/store-referenced/_config.js new file mode 100644 index 0000000000..ecb958df1e --- /dev/null +++ b/test/stats/samples/store-referenced/_config.js @@ -0,0 +1,26 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, [ + { + name: 'foo', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: true, + writable: true + }, + { + name: '$foo', + export_name: null, + injected: true, + module: false, + mutated: true, + reassigned: false, + referenced: true, + writable: true + } + ]); + } +}; diff --git a/test/stats/samples/store-referenced/input.html b/test/stats/samples/store-referenced/input.html new file mode 100644 index 0000000000..0222372f4d --- /dev/null +++ b/test/stats/samples/store-referenced/input.html @@ -0,0 +1,5 @@ +<script> + let foo; +</script> + +{$foo} \ No newline at end of file diff --git a/test/stats/samples/store-unreferenced/_config.js b/test/stats/samples/store-unreferenced/_config.js new file mode 100644 index 0000000000..9ea5784f92 --- /dev/null +++ b/test/stats/samples/store-unreferenced/_config.js @@ -0,0 +1,26 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, [ + { + name: 'foo', + export_name: null, + injected: false, + module: false, + mutated: false, + reassigned: false, + referenced: true, + writable: true + }, + { + name: '$foo', + export_name: null, + injected: true, + module: false, + mutated: true, + reassigned: false, + referenced: false, + writable: true + } + ]); + } +}; diff --git a/test/stats/samples/store-unreferenced/input.html b/test/stats/samples/store-unreferenced/input.html new file mode 100644 index 0000000000..3ad525047d --- /dev/null +++ b/test/stats/samples/store-unreferenced/input.html @@ -0,0 +1,4 @@ +<script> + let foo; + console.log($foo); +</script> \ No newline at end of file diff --git a/test/stats/samples/template-references/_config.js b/test/stats/samples/template-references/_config.js index 0bc844928f..71c255d54b 100644 --- a/test/stats/samples/template-references/_config.js +++ b/test/stats/samples/template-references/_config.js @@ -1,8 +1,5 @@ export default { test(assert, stats) { - assert.equal(stats.templateReferences.size, 3); - assert.ok(stats.templateReferences.has('foo')); - assert.ok(stats.templateReferences.has('Bar')); - assert.ok(stats.templateReferences.has('baz')); + assert.deepEqual(stats.vars, []); }, }; diff --git a/test/stats/samples/undeclared/_config.js b/test/stats/samples/undeclared/_config.js new file mode 100644 index 0000000000..71c255d54b --- /dev/null +++ b/test/stats/samples/undeclared/_config.js @@ -0,0 +1,5 @@ +export default { + test(assert, stats) { + assert.deepEqual(stats.vars, []); + }, +}; diff --git a/test/stats/samples/undeclared/input.html b/test/stats/samples/undeclared/input.html new file mode 100644 index 0000000000..0178466fe3 --- /dev/null +++ b/test/stats/samples/undeclared/input.html @@ -0,0 +1,3 @@ +<script></script> + +{foo} \ No newline at end of file diff --git a/test/validator/index.js b/test/validator/index.js index 9ca3fc57d1..517427e817 100644 --- a/test/validator/index.js +++ b/test/validator/index.js @@ -64,11 +64,16 @@ describe("validate", () => { throw new Error(`Expected an error: ${expected.message}`); } - assert.equal(error.code, expected.code); - assert.equal(error.message, expected.message); - assert.deepEqual(error.start, expected.start); - assert.deepEqual(error.end, expected.end); - assert.equal(error.pos, expected.pos); + try { + assert.equal(error.code, expected.code); + assert.equal(error.message, expected.message); + assert.deepEqual(error.start, expected.start); + assert.deepEqual(error.end, expected.end); + assert.equal(error.pos, expected.pos); + } catch (e) { + console.error(error) + throw e; + } } }); }); @@ -102,7 +107,7 @@ describe("validate", () => { name: "_", generate: false }); - + assert.deepEqual(stats.warnings, []); }); }); diff --git a/test/validator/samples/binding-invalid-value/errors.json b/test/validator/samples/binding-invalid-value/errors.json new file mode 100644 index 0000000000..07eb7b0f1c --- /dev/null +++ b/test/validator/samples/binding-invalid-value/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "binding-undeclared", + "message": "foo is not declared", + "pos": 37, + "start": { + "line": 2, + "column": 19, + "character": 37 + }, + "end": { + "line": 2, + "column": 22, + "character": 40 + } +}] \ No newline at end of file diff --git a/test/validator/samples/binding-invalid-value/input.html b/test/validator/samples/binding-invalid-value/input.html new file mode 100644 index 0000000000..9eeeba5fed --- /dev/null +++ b/test/validator/samples/binding-invalid-value/input.html @@ -0,0 +1,2 @@ +<script></script> +<input bind:value={foo}> \ No newline at end of file diff --git a/test/validator/samples/multiple-script-default-context/errors.json b/test/validator/samples/multiple-script-default-context/errors.json index 6e9a53d691..6f0637b51b 100644 --- a/test/validator/samples/multiple-script-default-context/errors.json +++ b/test/validator/samples/multiple-script-default-context/errors.json @@ -8,8 +8,8 @@ "character": 30 }, "end": { - "line": 7, - "column": 9, - "character": 58 + "line": 5, + "column": 0, + "character": 30 } }] \ No newline at end of file diff --git a/test/validator/samples/multiple-script-module-context/errors.json b/test/validator/samples/multiple-script-module-context/errors.json index ef8dc15273..7b86c61926 100644 --- a/test/validator/samples/multiple-script-module-context/errors.json +++ b/test/validator/samples/multiple-script-module-context/errors.json @@ -8,8 +8,8 @@ "character": 47 }, "end": { - "line": 7, - "column": 9, - "character": 92 + "line": 5, + "column": 0, + "character": 47 } }] \ No newline at end of file diff --git a/test/validator/samples/script-invalid-context/errors.json b/test/validator/samples/script-invalid-context/errors.json index 9c276d932d..07ad0f1270 100644 --- a/test/validator/samples/script-invalid-context/errors.json +++ b/test/validator/samples/script-invalid-context/errors.json @@ -9,7 +9,7 @@ }, "end": { "line": 1, - "column": 22, - "character": 22 + "column": 8, + "character": 8 } }] \ No newline at end of file