run prettier on src, update tests

pull/618/head
Rich Harris 8 years ago
parent 399ee4595a
commit 84595fb381

@ -42,7 +42,12 @@ export default class Generator {
aliases: Map<string, string>; aliases: Map<string, string>;
usedNames: Set<string>; usedNames: Set<string>;
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) { constructor(
parsed: Parsed,
source: string,
name: string,
options: CompileOptions
) {
this.parsed = parsed; this.parsed = parsed;
this.source = source; this.source = source;
this.name = name; this.name = name;
@ -91,7 +96,12 @@ export default class Generator {
return this.aliases.get(name); return this.aliases.get(name);
} }
contextualise ( block: DomBlock | SsrBlock, expression: Node, context: string, isEventHandler: boolean ) { contextualise(
block: DomBlock | SsrBlock,
expression: Node,
context: string,
isEventHandler: boolean
) {
this.addSourcemapLocations(expression); this.addSourcemapLocations(expression);
const usedContexts: string[] = []; const usedContexts: string[] = [];
@ -114,37 +124,36 @@ export default class Generator {
} }
if (node.type === 'ThisExpression') { if (node.type === 'ThisExpression') {
if ( lexicalDepth === 0 && context ) code.overwrite( node.start, node.end, context, { storeName: true, contentOnly: false } ); if (lexicalDepth === 0 && context)
} code.overwrite(node.start, node.end, context, {
storeName: true,
else if ( isReference( node, parent ) ) { contentOnly: false
});
} else if (isReference(node, parent)) {
const { name } = flattenReference(node); const { name } = flattenReference(node);
if (scope.has(name)) return; if (scope.has(name)) return;
if (name === 'event' && isEventHandler) { if (name === 'event' && isEventHandler) {
// noop // noop
} } else if (contexts.has(name)) {
else if ( contexts.has( name ) ) {
const contextName = contexts.get(name); const contextName = contexts.get(name);
if (contextName !== name) { if (contextName !== name) {
// this is true for 'reserved' names like `state` and `component` // this is true for 'reserved' names like `state` and `component`
code.overwrite( node.start, node.start + name.length, contextName, { storeName: true, contentOnly: false } ); code.overwrite(
node.start,
node.start + name.length,
contextName,
{ storeName: true, contentOnly: false }
);
} }
if (!~usedContexts.indexOf(name)) usedContexts.push(name); if (!~usedContexts.indexOf(name)) usedContexts.push(name);
} } else if (helpers.has(name)) {
else if ( helpers.has( name ) ) {
code.prependRight(node.start, `${self.alias('template')}.helpers.`); code.prependRight(node.start, `${self.alias('template')}.helpers.`);
} } else if (indexes.has(name)) {
else if ( indexes.has( name ) ) {
const context = indexes.get(name); const context = indexes.get(name);
if (!~usedContexts.indexOf(context)) usedContexts.push(context); if (!~usedContexts.indexOf(context)) usedContexts.push(context);
} } else {
else {
// handle shorthand properties // handle shorthand properties
if (parent && parent.type === 'Property' && parent.shorthand) { if (parent && parent.type === 'Property' && parent.shorthand) {
if (key === 'key') { if (key === 'key') {
@ -155,7 +164,10 @@ export default class Generator {
if (globalWhitelist.has(name)) { if (globalWhitelist.has(name)) {
code.prependRight(node.start, `( '${name}' in state ? state.`); code.prependRight(node.start, `( '${name}' in state ? state.`);
code.appendLeft( node.object ? node.object.end : node.end, ` : ${name} )` ); code.appendLeft(
node.object ? node.object.end : node.end,
` : ${name} )`
);
} else { } else {
code.prependRight(node.start, `state.`); code.prependRight(node.start, `state.`);
} }
@ -180,7 +192,11 @@ export default class Generator {
}; };
} }
findDependencies ( contextDependencies: Map<string, string[]>, indexes: Map<string, string>, expression: Node ) { findDependencies(
contextDependencies: Map<string, string[]>,
indexes: Map<string, string>,
expression: Node
) {
if (expression._dependencies) return expression._dependencies; if (expression._dependencies) return expression._dependencies;
let scope = annotateWithScopes(expression); let scope = annotateWithScopes(expression);
@ -229,23 +245,40 @@ export default class Generator {
this.imports.forEach((declaration, i) => { this.imports.forEach((declaration, i) => {
if (format === 'es') { if (format === 'es') {
statements.push( this.source.slice( declaration.start, declaration.end ) ); statements.push(
this.source.slice(declaration.start, declaration.end)
);
return; return;
} }
const defaultImport = declaration.specifiers.find( ( x: Node ) => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' ); const defaultImport = declaration.specifiers.find(
const namespaceImport = declaration.specifiers.find( ( x: Node ) => x.type === 'ImportNamespaceSpecifier' ); (x: Node) =>
const namedImports = declaration.specifiers.filter( ( x: Node ) => x.type === 'ImportSpecifier' && x.imported.name !== 'default' ); x.type === 'ImportDefaultSpecifier' ||
(x.type === 'ImportSpecifier' && x.imported.name === 'default')
const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`; );
const namespaceImport = declaration.specifiers.find(
(x: Node) => x.type === 'ImportNamespaceSpecifier'
);
const namedImports = declaration.specifiers.filter(
(x: Node) =>
x.type === 'ImportSpecifier' && x.imported.name !== 'default'
);
const name = defaultImport || namespaceImport
? (defaultImport || namespaceImport).local.name
: `__import${i}`;
declaration.name = name; // hacky but makes life a bit easier later declaration.name = name; // hacky but makes life a bit easier later
namedImports.forEach((specifier: Node) => { namedImports.forEach((specifier: Node) => {
statements.push( `var ${specifier.local.name} = ${name}.${specifier.imported.name}` ); statements.push(
`var ${specifier.local.name} = ${name}.${specifier.imported.name}`
);
}); });
if (defaultImport) { if (defaultImport) {
statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` ); statements.push(
`${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};`
);
} }
}); });
@ -298,7 +331,10 @@ export default class Generator {
return { return {
code: compiled.toString(), code: compiled.toString(),
map: compiled.generateMap({ includeContent: true, file: options.outputFilename }), map: compiled.generateMap({
includeContent: true,
file: options.outputFilename
}),
css: this.css css: this.css
}; };
} }
@ -306,7 +342,13 @@ export default class Generator {
getUniqueName(name: string) { getUniqueName(name: string) {
if (test) name = `${name}$`; if (test) name = `${name}$`;
let alias = name; let alias = name;
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ); alias = `${name}_${i++}` ); for (
let i = 1;
reservedNames.has(alias) ||
this.importedNames.has(alias) ||
this.usedNames.has(alias);
alias = `${name}_${i++}`
);
this.usedNames.add(alias); this.usedNames.add(alias);
return alias; return alias;
} }
@ -316,7 +358,14 @@ export default class Generator {
return name => { return name => {
if (test) name = `${name}$`; if (test) name = `${name}$`;
let alias = name; let alias = name;
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ) || localUsedNames.has( alias ); alias = `${name}_${i++}` ); for (
let i = 1;
reservedNames.has(alias) ||
this.importedNames.has(alias) ||
this.usedNames.has(alias) ||
localUsedNames.has(alias);
alias = `${name}_${i++}`
);
localUsedNames.add(alias); localUsedNames.add(alias);
return alias; return alias;
}; };
@ -350,7 +399,9 @@ export default class Generator {
} }
} }
const defaultExport = body.find( ( node: Node ) => node.type === 'ExportDefaultDeclaration' ); const defaultExport = body.find(
(node: Node) => node.type === 'ExportDefaultDeclaration'
);
if (defaultExport) { if (defaultExport) {
defaultExport.declaration.properties.forEach((prop: Node) => { defaultExport.declaration.properties.forEach((prop: Node) => {
@ -373,7 +424,10 @@ export default class Generator {
const key = prop.key.name; const key = prop.key.name;
const value = prop.value; const value = prop.value;
const deps = value.params.map( ( param: Node ) => param.type === 'AssignmentPattern' ? param.left.name : param.name ); const deps = value.params.map(
(param: Node) =>
param.type === 'AssignmentPattern' ? param.left.name : param.name
);
dependencies.set(key, deps); dependencies.set(key, deps);
}); });
@ -391,7 +445,9 @@ export default class Generator {
computations.push({ key, deps }); computations.push({ key, deps });
} }
templateProperties.computed.value.properties.forEach( ( prop: Node ) => visit( prop.key.name ) ); templateProperties.computed.value.properties.forEach((prop: Node) =>
visit(prop.key.name)
);
} }
if (templateProperties.namespace) { if (templateProperties.namespace) {
@ -403,19 +459,28 @@ export default class Generator {
if (templateProperties.components) { if (templateProperties.components) {
let hasNonImportedComponent = false; let hasNonImportedComponent = false;
templateProperties.components.value.properties.forEach( ( property: Node ) => { templateProperties.components.value.properties.forEach(
(property: Node) => {
const key = property.key.name; const key = property.key.name;
const value = source.slice( property.value.start, property.value.end ); const value = source.slice(
property.value.start,
property.value.end
);
if (this.importedNames.has(value)) { if (this.importedNames.has(value)) {
this.importedComponents.set(key, value); this.importedComponents.set(key, value);
} else { } else {
hasNonImportedComponent = true; hasNonImportedComponent = true;
} }
}); }
);
if (hasNonImportedComponent) { if (hasNonImportedComponent) {
// remove the specific components that were imported, as we'll refer to them directly // remove the specific components that were imported, as we'll refer to them directly
Array.from(this.importedComponents.keys()).forEach(key => { Array.from(this.importedComponents.keys()).forEach(key => {
removeObjectKey( this.code, templateProperties.components.value, key ); removeObjectKey(
this.code,
templateProperties.components.value,
key
);
}); });
} else { } else {
// remove the entire components portion of the export // remove the entire components portion of the export
@ -426,22 +491,48 @@ export default class Generator {
// Remove these after version 2 // Remove these after version 2
if (templateProperties.onrender) { if (templateProperties.onrender) {
const { key } = templateProperties.onrender; const { key } = templateProperties.onrender;
this.code.overwrite( key.start, key.end, 'oncreate', { storeName: true, contentOnly: false } ); this.code.overwrite(key.start, key.end, 'oncreate', {
storeName: true,
contentOnly: false
});
templateProperties.oncreate = templateProperties.onrender; templateProperties.oncreate = templateProperties.onrender;
} }
if (templateProperties.onteardown) { if (templateProperties.onteardown) {
const { key } = templateProperties.onteardown; const { key } = templateProperties.onteardown;
this.code.overwrite( key.start, key.end, 'ondestroy', { storeName: true, contentOnly: false } ); this.code.overwrite(key.start, key.end, 'ondestroy', {
storeName: true,
contentOnly: false
});
templateProperties.ondestroy = templateProperties.onteardown; templateProperties.ondestroy = templateProperties.onteardown;
} }
// in an SSR context, we don't need to include events, methods, oncreate or ondestroy // in an SSR context, we don't need to include events, methods, oncreate or ondestroy
if (ssr) { if (ssr) {
if ( templateProperties.oncreate ) removeNode( this.code, defaultExport.declaration, templateProperties.oncreate ); if (templateProperties.oncreate)
if ( templateProperties.ondestroy ) removeNode( this.code, defaultExport.declaration, templateProperties.ondestroy ); removeNode(
if ( templateProperties.methods ) removeNode( this.code, defaultExport.declaration, templateProperties.methods ); this.code,
if ( templateProperties.events ) removeNode( this.code, defaultExport.declaration, templateProperties.events ); defaultExport.declaration,
templateProperties.oncreate
);
if (templateProperties.ondestroy)
removeNode(
this.code,
defaultExport.declaration,
templateProperties.ondestroy
);
if (templateProperties.methods)
removeNode(
this.code,
defaultExport.declaration,
templateProperties.methods
);
if (templateProperties.events)
removeNode(
this.code,
defaultExport.declaration,
templateProperties.events
);
} }
// now that we've analysed the default export, we can determine whether or not we need to keep it // now that we've analysed the default export, we can determine whether or not we need to keep it
@ -456,30 +547,47 @@ export default class Generator {
const finalNode = body[body.length - 1]; const finalNode = body[body.length - 1];
if (defaultExport === finalNode) { if (defaultExport === finalNode) {
// export is last property, we can just return it // export is last property, we can just return it
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `return ` ); this.code.overwrite(
defaultExport.start,
defaultExport.declaration.start,
`return `
);
} else { } else {
const { declarations } = annotateWithScopes(js); const { declarations } = annotateWithScopes(js);
let template = 'template'; let template = 'template';
for ( let i = 1; declarations.has( template ); template = `template_${i++}` ); for (
let i = 1;
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var ${template} = ` ); declarations.has(template);
template = `template_${i++}`
);
this.code.overwrite(
defaultExport.start,
defaultExport.declaration.start,
`var ${template} = `
);
let i = defaultExport.start; let i = defaultExport.start;
while (/\s/.test(source[i - 1])) i--; while (/\s/.test(source[i - 1])) i--;
const indentation = source.slice(i, defaultExport.start); const indentation = source.slice(i, defaultExport.start);
this.code.appendLeft( finalNode.end, `\n\n${indentation}return ${template};` ); this.code.appendLeft(
finalNode.end,
`\n\n${indentation}return ${template};`
);
} }
} }
// user code gets wrapped in an IIFE // user code gets wrapped in an IIFE
if (js.content.body.length) { if (js.content.body.length) {
const prefix = hasDefaultExport ? `var ${this.alias( 'template' )} = (function () {` : `(function () {`; const prefix = hasDefaultExport
this.code.prependRight( js.content.start, prefix ).appendLeft( js.content.end, '}());' ); ? `var ${this.alias('template')} = (function () {`
} : `(function () {`;
this.code
.prependRight(js.content.start, prefix)
.appendLeft(js.content.end, '}());');
} else {
// if there's no need to include user code, remove it altogether // if there's no need to include user code, remove it altogether
else {
this.code.remove(js.content.start, js.content.end); this.code.remove(js.content.start, js.content.end);
hasJs = false; hasJs = false;
} }

@ -48,7 +48,7 @@ export default class Block {
unmount: CodeBuilder; unmount: CodeBuilder;
detachRaw: CodeBuilder; detachRaw: CodeBuilder;
destroy: CodeBuilder; destroy: CodeBuilder;
} };
hasIntroMethod: boolean; hasIntroMethod: boolean;
hasOutroMethod: boolean; hasOutroMethod: boolean;
@ -117,26 +117,37 @@ export default class Block {
}); });
} }
addElement ( name: string, renderStatement: string, parentNode: string, needsIdentifier = false ) { addElement(
name: string,
renderStatement: string,
parentNode: string,
needsIdentifier = false
) {
const isToplevel = !parentNode; const isToplevel = !parentNode;
if (needsIdentifier || isToplevel) { if (needsIdentifier || isToplevel) {
this.builders.create.addLine( this.builders.create.addLine(`var ${name} = ${renderStatement};`);
`var ${name} = ${renderStatement};`
);
this.mount(name, parentNode); this.mount(name, parentNode);
} else { } else {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${parentNode} );` ); this.builders.create.addLine(
`${this.generator.helper(
'appendNode'
)}( ${renderStatement}, ${parentNode} );`
);
} }
if (isToplevel) { if (isToplevel) {
this.builders.unmount.addLine( `${this.generator.helper( 'detachNode' )}( ${name} );` ); this.builders.unmount.addLine(
`${this.generator.helper('detachNode')}( ${name} );`
);
} }
} }
addVariable(name: string, init?: string) { addVariable(name: string, init?: string) {
if (this.variables.has(name) && this.variables.get(name) !== init) { if (this.variables.has(name) && this.variables.get(name) !== init) {
throw new Error( `Variable '${name}' already initialised with a different value` ); throw new Error(
`Variable '${name}' already initialised with a different value`
);
} }
this.variables.set(name, init); this.variables.set(name, init);
@ -155,18 +166,32 @@ export default class Block {
} }
contextualise(expression: Node, context?: string, isEventHandler?: boolean) { contextualise(expression: Node, context?: string, isEventHandler?: boolean) {
return this.generator.contextualise( this, expression, context, isEventHandler ); return this.generator.contextualise(
this,
expression,
context,
isEventHandler
);
} }
findDependencies(expression) { findDependencies(expression) {
return this.generator.findDependencies( this.contextDependencies, this.indexes, expression ); return this.generator.findDependencies(
this.contextDependencies,
this.indexes,
expression
);
} }
mount(name: string, parentNode: string) { mount(name: string, parentNode: string) {
if (parentNode) { if (parentNode) {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${parentNode} );` ); this.builders.create.addLine(
`${this.generator.helper('appendNode')}( ${name}, ${parentNode} );`
);
} else { } else {
this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, ${this.target}, anchor );` ); this.builders.mount.addLine(
`${this.generator.helper('insertNode')}( ${name}, ${this
.target}, anchor );`
);
} }
} }
@ -302,7 +327,10 @@ export default class Block {
} }
return deindent` return deindent`
function ${this.name} ( ${this.params.join( ', ' )}, ${this.component}${this.key ? `, ${localKey}` : ''} ) { function ${this.name} ( ${this.params.join(', ')}, ${this.component}${this
.key
? `, ${localKey}`
: ''} ) {
${this.builders.create} ${this.builders.create}
return { return {

@ -13,7 +13,7 @@ import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces'; import { Parsed, CompileOptions, Node } from '../../interfaces';
export class DomGenerator extends Generator { export class DomGenerator extends Generator {
blocks: Block[] blocks: Block[];
uses: Set<string>; uses: Set<string>;
readonly: Set<string>; readonly: Set<string>;
metaBindings: string[]; metaBindings: string[];
@ -22,7 +22,12 @@ export class DomGenerator extends Generator {
hasOutroTransitions: boolean; hasOutroTransitions: boolean;
hasComplexBindings: boolean; hasComplexBindings: boolean;
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) { constructor(
parsed: Parsed,
source: string,
name: string,
options: CompileOptions
) {
super(parsed, source, name, options); super(parsed, source, name, options);
this.blocks = []; this.blocks = [];
this.uses = new Set(); this.uses = new Set();
@ -44,13 +49,22 @@ export class DomGenerator extends Generator {
} }
} }
export default function dom ( parsed: Parsed, source: string, options: CompileOptions ) { export default function dom(
parsed: Parsed,
source: string,
options: CompileOptions
) {
const format = options.format || 'es'; const format = options.format || 'es';
const name = options.name || 'SvelteComponent'; const name = options.name || 'SvelteComponent';
const generator = new DomGenerator(parsed, source, name, options); const generator = new DomGenerator(parsed, source, name, options);
const { computations, hasJs, templateProperties, namespace } = generator.parseJs(); const {
computations,
hasJs,
templateProperties,
namespace
} = generator.parseJs();
const { block, state } = preprocess(generator, namespace, parsed.html); const { block, state } = preprocess(generator, namespace, parsed.html);
@ -70,47 +84,71 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
computations.forEach(({ key, deps }) => { computations.forEach(({ key, deps }) => {
if (generator.readonly.has(key)) { if (generator.readonly.has(key)) {
// <:Window> bindings // <:Window> bindings
throw new Error( `Cannot have a computed value '${key}' that clashes with a read-only property` ); throw new Error(
`Cannot have a computed value '${key}' that clashes with a read-only property`
);
} }
generator.readonly.add(key); generator.readonly.add(key);
const condition = `isInitial || ${deps.map( dep => `( '${dep}' in newState && ${differs}( state.${dep}, oldState.${dep} ) )` ).join( ' || ' )}`; const condition = `isInitial || ${deps
const statement = `state.${key} = newState.${key} = ${generator.alias( 'template' )}.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );`; .map(
dep =>
`( '${dep}' in newState && ${differs}( state.${dep}, oldState.${dep} ) )`
)
.join(' || ')}`;
const statement = `state.${key} = newState.${key} = ${generator.alias(
'template'
)}.computed.${key}( ${deps.map(dep => `state.${dep}`).join(', ')} );`;
builder.addConditionalLine(condition, statement); builder.addConditionalLine(condition, statement);
}); });
builders.main.addBlock(deindent` builders.main.addBlock(deindent`
function ${generator.alias( 'recompute' )} ( state, newState, oldState, isInitial ) { function ${generator.alias(
'recompute'
)} ( state, newState, oldState, isInitial ) {
${builder} ${builder}
} }
`); `);
} }
builders._set.addBlock(deindent` builders._set.addBlock(deindent`
${options.dev && deindent` ${options.dev &&
deindent`
if ( typeof newState !== 'object' ) { if ( typeof newState !== 'object' ) {
throw new Error( 'Component .set was called without an object of data key-values to update.' ); throw new Error( 'Component .set was called without an object of data key-values to update.' );
} }
${Array.from( generator.readonly ).map( prop => ${Array.from(generator.readonly).map(
prop =>
`if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );` `if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );`
)} )}
`} `}
var oldState = this._state; var oldState = this._state;
this._state = ${generator.helper('assign')}( {}, oldState, newState ); this._state = ${generator.helper('assign')}( {}, oldState, newState );
${computations.length && `${generator.alias( 'recompute' )}( this._state, newState, oldState, false )`} ${computations.length &&
${generator.helper( 'dispatchObservers' )}( this, this._observers.pre, newState, oldState ); `${generator.alias(
'recompute'
)}( this._state, newState, oldState, false )`}
${generator.helper(
'dispatchObservers'
)}( this, this._observers.pre, newState, oldState );
${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`} ${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`}
${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState ); ${generator.helper(
${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`} 'dispatchObservers'
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`} )}( this, this._observers.post, newState, oldState );
${generator.hasComplexBindings &&
`while ( this._bindings.length ) this._bindings.pop()();`}
${(generator.hasComponents || generator.hasIntroTransitions) &&
`this._flush();`}
`); `);
if (hasJs) { if (hasJs) {
builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); builders.main.addBlock(
`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`
);
} }
if (generator.css && options.css !== false) { if (generator.css && options.css !== false) {
@ -128,29 +166,50 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
builders.main.addBlock(block.render()); builders.main.addBlock(block.render());
}); });
const sharedPath = options.shared === true ? 'svelte/shared.js' : options.shared; const sharedPath = options.shared === true
? 'svelte/shared.js'
const prototypeBase = `${name}.prototype` + ( templateProperties.methods ? `, ${generator.alias( 'template' )}.methods` : '' ); : options.shared;
const proto = sharedPath ? `${generator.helper( 'proto' )} ` : deindent`
const prototypeBase =
`${name}.prototype` +
(templateProperties.methods
? `, ${generator.alias('template')}.methods`
: '');
const proto = sharedPath
? `${generator.helper('proto')} `
: deindent`
{ {
${ ${['get', 'fire', 'observe', 'on', 'set', '_flush']
[ 'get', 'fire', 'observe', 'on', 'set', '_flush' ]
.map(n => `${n}: ${generator.helper(n)}`) .map(n => `${n}: ${generator.helper(n)}`)
.join( ',\n' ) .join(',\n')}
}
}`; }`;
// TODO deprecate component.teardown() // TODO deprecate component.teardown()
builders.main.addBlock(deindent` builders.main.addBlock(deindent`
function ${name} ( options ) { function ${name} ( options ) {
options = options || {}; options = options || {};
${options.dev && `if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`} ${options.dev &&
`if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`}
${generator.usesRefs && `this.refs = {};`} ${generator.usesRefs && `this.refs = {};`}
this._state = ${templateProperties.data ? `${generator.helper( 'assign' )}( ${generator.alias( 'template' )}.data(), options.data )` : `options.data || {}`}; this._state = ${templateProperties.data
? `${generator.helper('assign')}( ${generator.alias(
'template'
)}.data(), options.data )`
: `options.data || {}`};
${generator.metaBindings} ${generator.metaBindings}
${computations.length && `${generator.alias( 'recompute' )}( this._state, this._state, {}, true );`} ${computations.length &&
${options.dev && Array.from( generator.expectedProperties ).map( prop => `if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`)} `${generator.alias(
${generator.bindingGroups.length && `this._bindingGroups = [ ${Array( generator.bindingGroups.length ).fill( '[]' ).join( ', ' )} ];`} 'recompute'
)}( this._state, this._state, {}, true );`}
${options.dev &&
Array.from(generator.expectedProperties).map(
prop =>
`if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`
)}
${generator.bindingGroups.length &&
`this._bindingGroups = [ ${Array(generator.bindingGroups.length)
.fill('[]')
.join(', ')} ];`}
this._observers = { this._observers = {
pre: Object.create( null ), pre: Object.create( null ),
@ -163,18 +222,30 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
this._yield = options._yield; this._yield = options._yield;
this._torndown = false; this._torndown = false;
${parsed.css && options.css !== false && `if ( !document.getElementById( ${JSON.stringify( generator.cssId + '-style' )} ) ) ${generator.alias( 'add_css' )}();`} ${parsed.css &&
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._renderHooks = [];`} options.css !== false &&
`if ( !document.getElementById( ${JSON.stringify(
generator.cssId + '-style'
)} ) ) ${generator.alias('add_css')}();`}
${(generator.hasComponents || generator.hasIntroTransitions) &&
`this._renderHooks = [];`}
${generator.hasComplexBindings && `this._bindings = [];`} ${generator.hasComplexBindings && `this._bindings = [];`}
this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this ); this._fragment = ${generator.alias(
'create_main_fragment'
)}( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null ); if ( options.target ) this._fragment.mount( options.target, null );
${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`} ${generator.hasComplexBindings &&
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`} `while ( this._bindings.length ) this._bindings.pop()();`}
${(generator.hasComponents || generator.hasIntroTransitions) &&
`this._flush();`}
${templateProperties.oncreate && deindent` ${templateProperties.oncreate &&
deindent`
if ( options._root ) { if ( options._root ) {
options._root._renderHooks.push( ${generator.alias( 'template' )}.oncreate.bind( this ) ); options._root._renderHooks.push( ${generator.alias(
'template'
)}.oncreate.bind( this ) );
} else { } else {
${generator.alias('template')}.oncreate.call( this ); ${generator.alias('template')}.oncreate.call( this );
} }
@ -189,7 +260,8 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
${name}.prototype.teardown = ${name}.prototype.destroy = function destroy ( detach ) { ${name}.prototype.teardown = ${name}.prototype.destroy = function destroy ( detach ) {
this.fire( 'destroy' ); this.fire( 'destroy' );
${templateProperties.ondestroy && `${generator.alias( 'template' )}.ondestroy.call( this );`} ${templateProperties.ondestroy &&
`${generator.alias('template')}.ondestroy.call( this );`}
if ( detach !== false ) this._fragment.unmount(); if ( detach !== false ) this._fragment.unmount();
this._fragment.destroy(); this._fragment.destroy();
@ -202,11 +274,15 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
if (sharedPath) { if (sharedPath) {
if (format !== 'es') { if (format !== 'es') {
throw new Error( `Components with shared helpers must be compiled to ES2015 modules (format: 'es')` ); throw new Error(
`Components with shared helpers must be compiled to ES2015 modules (format: 'es')`
);
} }
const names = Array.from(generator.uses).sort().map(name => { const names = Array.from(generator.uses).sort().map(name => {
return name !== generator.alias( name ) ? `${name} as ${generator.alias( name )}` : name; return name !== generator.alias(name)
? `${name} as ${generator.alias(name)}`
: name;
}); });
builders.main.addLineAtStart( builders.main.addLineAtStart(
@ -224,14 +300,19 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
enter(node, parent) { enter(node, parent) {
if (node._scope) scope = node._scope; if (node._scope) scope = node._scope;
if ( node.type === 'Identifier' && isReference( node, parent ) && !scope.has( node.name ) ) { if (
node.type === 'Identifier' &&
isReference(node, parent) &&
!scope.has(node.name)
) {
if (node.name in shared) { if (node.name in shared) {
// this helper function depends on another one // this helper function depends on another one
const dependency = node.name; const dependency = node.name;
generator.uses.add(dependency); generator.uses.add(dependency);
const alias = generator.alias(dependency); const alias = generator.alias(dependency);
if ( alias !== node.name ) code.overwrite( node.start, node.end, alias ); if (alias !== node.name)
code.overwrite(node.start, node.end, alias);
} }
} }
}, },
@ -241,20 +322,27 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
} }
}); });
if ( key === 'transitionManager' ) { // special case if (key === 'transitionManager') {
// special case
const global = `_svelteTransitionManager`; const global = `_svelteTransitionManager`;
builders.main.addBlock( builders.main.addBlock(
`var ${generator.alias( 'transitionManager' )} = window.${global} || ( window.${global} = ${code});` `var ${generator.alias(
'transitionManager'
)} = window.${global} || ( window.${global} = ${code});`
); );
} else { } else {
const alias = generator.alias(expression.id.name); const alias = generator.alias(expression.id.name);
if ( alias !== expression.id.name ) code.overwrite( expression.id.start, expression.id.end, alias ); if (alias !== expression.id.name)
code.overwrite(expression.id.start, expression.id.end, alias);
builders.main.addBlock(code.toString()); builders.main.addBlock(code.toString());
} }
}); });
} }
return generator.generate( builders.main.toString(), options, { name, format } ); return generator.generate(builders.main.toString(), options, {
name,
format
});
} }

@ -2,7 +2,7 @@ export interface State {
name: string; name: string;
namespace: string; namespace: string;
parentNode: string; parentNode: string;
isTopLevel: boolean isTopLevel: boolean;
parentNodeName?: string; parentNodeName?: string;
basename?: string; basename?: string;
inEachBlock?: boolean; inEachBlock?: boolean;

@ -6,7 +6,9 @@ import { Node } from '../../interfaces';
import { State } from './interfaces'; import { State } from './interfaces';
function isElseIf(node: Node) { function isElseIf(node: Node) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
} }
function getChildState(parent: State, child = {}) { function getChildState(parent: State, child = {}) {
@ -28,7 +30,12 @@ const elementsWithoutText = new Set([
]); ]);
const preprocessors = { const preprocessors = {
MustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { MustacheTag: (
generator: DomGenerator,
block: Block,
state: State,
node: Node
) => {
const dependencies = block.findDependencies(node.expression); const dependencies = block.findDependencies(node.expression);
block.addDependencies(dependencies); block.addDependencies(dependencies);
@ -37,7 +44,12 @@ const preprocessors = {
}); });
}, },
RawMustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { RawMustacheTag: (
generator: DomGenerator,
block: Block,
state: State,
node: Node
) => {
const dependencies = block.findDependencies(node.expression); const dependencies = block.findDependencies(node.expression);
block.addDependencies(dependencies); block.addDependencies(dependencies);
@ -59,7 +71,12 @@ const preprocessors = {
node._state.name = block.getUniqueName(`text`); node._state.name = block.getUniqueName(`text`);
}, },
IfBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { IfBlock: (
generator: DomGenerator,
block: Block,
state: State,
node: Node
) => {
const blocks: Block[] = []; const blocks: Block[] = [];
let dynamic = false; let dynamic = false;
let hasIntros = false; let hasIntros = false;
@ -96,7 +113,12 @@ const preprocessors = {
node.else._state = getChildState(state); node.else._state = getChildState(state);
blocks.push(node.else._block); blocks.push(node.else._block);
preprocessChildren( generator, node.else._block, node.else._state, node.else ); preprocessChildren(
generator,
node.else._block,
node.else._state,
node.else
);
if (node.else._block.dependencies.size > 0) { if (node.else._block.dependencies.size > 0) {
dynamic = true; dynamic = true;
@ -116,12 +138,18 @@ const preprocessors = {
generator.blocks.push(...blocks); generator.blocks.push(...blocks);
}, },
EachBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { EachBlock: (
generator: DomGenerator,
block: Block,
state: State,
node: Node
) => {
const dependencies = block.findDependencies(node.expression); const dependencies = block.findDependencies(node.expression);
block.addDependencies(dependencies); block.addDependencies(dependencies);
const indexNames = new Map(block.indexNames); const indexNames = new Map(block.indexNames);
const indexName = node.index || block.getUniqueName( `${node.context}_index` ); const indexName =
node.index || block.getUniqueName(`${node.context}_index`);
indexNames.set(node.context, indexName); indexNames.set(node.context, indexName);
const listNames = new Map(block.listNames); const listNames = new Map(block.listNames);
@ -173,25 +201,40 @@ const preprocessors = {
node.else._state = getChildState(state); node.else._state = getChildState(state);
generator.blocks.push(node.else._block); generator.blocks.push(node.else._block);
preprocessChildren( generator, node.else._block, node.else._state, node.else ); preprocessChildren(
generator,
node.else._block,
node.else._state,
node.else
);
node.else._block.hasUpdateMethod = node.else._block.dependencies.size > 0; node.else._block.hasUpdateMethod = node.else._block.dependencies.size > 0;
} }
}, },
Element: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { Element: (
const isComponent = generator.components.has( node.name ) || node.name === ':Self'; generator: DomGenerator,
block: Block,
state: State,
node: Node
) => {
const isComponent =
generator.components.has(node.name) || node.name === ':Self';
if (isComponent) { if (isComponent) {
node._state = getChildState(state); node._state = getChildState(state);
} else { } else {
const name = block.getUniqueName( node.name.replace( /[^a-zA-Z0-9_$]/g, '_' ) ); const name = block.getUniqueName(
node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
);
node._state = getChildState(state, { node._state = getChildState(state, {
isTopLevel: false, isTopLevel: false,
name, name,
parentNode: name, parentNode: name,
parentNodeName: node.name, parentNodeName: node.name,
namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace, namespace: node.name === 'svg'
? 'http://www.w3.org/2000/svg'
: state.namespace,
allUsedContexts: [] allUsedContexts: []
}); });
} }
@ -204,15 +247,12 @@ const preprocessors = {
block.addDependencies(dependencies); block.addDependencies(dependencies);
} }
}); });
} } else if (attribute.type === 'Binding') {
else if ( attribute.type === 'Binding' ) {
const dependencies = block.findDependencies(attribute.value); const dependencies = block.findDependencies(attribute.value);
block.addDependencies(dependencies); block.addDependencies(dependencies);
} } else if (attribute.type === 'Transition') {
if (attribute.intro)
else if ( attribute.type === 'Transition' ) { generator.hasIntroTransitions = block.hasIntroMethod = true;
if ( attribute.intro ) generator.hasIntroTransitions = block.hasIntroMethod = true;
if (attribute.outro) { if (attribute.outro) {
generator.hasOutroTransitions = block.hasOutroMethod = true; generator.hasOutroTransitions = block.hasOutroMethod = true;
block.outros += 1; block.outros += 1;
@ -222,7 +262,9 @@ const preprocessors = {
if (node.children.length) { if (node.children.length) {
if (isComponent) { if (isComponent) {
const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() ); const name = block.getUniqueName(
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
);
node._block = block.child({ node._block = block.child({
name: generator.getUniqueName(`create_${name}_yield_fragment`) name: generator.getUniqueName(`create_${name}_yield_fragment`)
@ -232,16 +274,20 @@ const preprocessors = {
preprocessChildren(generator, node._block, node._state, node); preprocessChildren(generator, node._block, node._state, node);
block.addDependencies(node._block.dependencies); block.addDependencies(node._block.dependencies);
node._block.hasUpdateMethod = node._block.dependencies.size > 0; node._block.hasUpdateMethod = node._block.dependencies.size > 0;
} } else {
else {
preprocessChildren(generator, block, node._state, node); preprocessChildren(generator, block, node._state, node);
} }
} }
} }
}; };
function preprocessChildren ( generator: DomGenerator, block: Block, state: State, node: Node, isTopLevel: boolean = false ) { function preprocessChildren(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
isTopLevel: boolean = false
) {
// glue text nodes together // glue text nodes together
const cleaned: Node[] = []; const cleaned: Node[] = [];
let lastChild: Node; let lastChild: Node;
@ -295,7 +341,11 @@ function preprocessChildren ( generator: DomGenerator, block: Block, state: Stat
node.children = cleaned; node.children = cleaned;
} }
export default function preprocess ( generator: DomGenerator, namespace: string, node: Node ) { export default function preprocess(
generator: DomGenerator,
namespace: string,
node: Node
) {
const block = new Block({ const block = new Block({
generator, generator,
name: generator.alias('create_main_fragment'), name: generator.alias('create_main_fragment'),

@ -3,7 +3,12 @@ import { DomGenerator } from './index';
import Block from './Block'; import Block from './Block';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
export default function visit ( generator: DomGenerator, block: Block, state, node: Node ) { export default function visit(
generator: DomGenerator,
block: Block,
state,
node: Node
) {
const visitor = visitors[node.type]; const visitor = visitors[node.type];
visitor(generator, block, state, node); visitor(generator, block, state, node);
} }

@ -3,35 +3,38 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function visitAttribute ( generator: DomGenerator, block: Block, state: State, node: Node, attribute, local ) { export default function visitAttribute(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute,
local
) {
if (attribute.value === true) { if (attribute.value === true) {
// attributes without values, e.g. <textarea readonly> // attributes without values, e.g. <textarea readonly>
local.staticAttributes.push({ local.staticAttributes.push({
name: attribute.name, name: attribute.name,
value: true value: true
}); });
} } else if (attribute.value.length === 0) {
else if ( attribute.value.length === 0 ) {
local.staticAttributes.push({ local.staticAttributes.push({
name: attribute.name, name: attribute.name,
value: `''` value: `''`
}); });
} } else if (attribute.value.length === 1) {
else if ( attribute.value.length === 1 ) {
const value = attribute.value[0]; const value = attribute.value[0];
if (value.type === 'Text') { if (value.type === 'Text') {
// static attributes // static attributes
const result = isNaN( value.data ) ? JSON.stringify( value.data ) : value.data; const result = isNaN(value.data)
? JSON.stringify(value.data)
: value.data;
local.staticAttributes.push({ local.staticAttributes.push({
name: attribute.name, name: attribute.name,
value: result value: result
}); });
} } else {
else {
// simple dynamic attributes // simple dynamic attributes
const { dependencies, snippet } = block.contextualise(value.expression); const { dependencies, snippet } = block.contextualise(value.expression);
@ -42,26 +45,29 @@ export default function visitAttribute ( generator: DomGenerator, block: Block,
dependencies dependencies
}); });
} }
} } else {
else {
// complex dynamic attributes // complex dynamic attributes
const allDependencies = []; const allDependencies = [];
const value = ( attribute.value[0].type === 'Text' ? '' : `"" + ` ) + ( const value =
attribute.value.map( chunk => { (attribute.value[0].type === 'Text' ? '' : `"" + `) +
attribute.value
.map(chunk => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return JSON.stringify(chunk.data); return JSON.stringify(chunk.data);
} else { } else {
const { dependencies, snippet } = block.contextualise( chunk.expression ); const { dependencies, snippet } = block.contextualise(
chunk.expression
);
dependencies.forEach(dependency => { dependencies.forEach(dependency => {
if ( !~allDependencies.indexOf( dependency ) ) allDependencies.push( dependency ); if (!~allDependencies.indexOf(dependency))
allDependencies.push(dependency);
}); });
return `( ${snippet} )`; return `( ${snippet} )`;
} }
}).join( ' + ' ) })
); .join(' + ');
local.dynamicAttributes.push({ local.dynamicAttributes.push({
name: attribute.name, name: attribute.name,

@ -6,14 +6,27 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function visitBinding ( generator: DomGenerator, block: Block, state: State, node: Node, attribute, local ) { export default function visitBinding(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute,
local
) {
const { name } = flattenReference(attribute.value); const { name } = flattenReference(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise( attribute.value ); const { snippet, contexts, dependencies } = block.contextualise(
attribute.value
);
if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' ); if (dependencies.length > 1)
throw new Error(
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
);
contexts.forEach(context => { contexts.forEach(context => {
if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context ); if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
}); });
const contextual = block.contexts.has(name); const contextual = block.contexts.has(name);
@ -25,7 +38,8 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
obj = block.listNames.get(name); obj = block.listNames.get(name);
prop = block.indexNames.get(name); prop = block.indexNames.get(name);
} else if (attribute.value.type === 'MemberExpression') { } else if (attribute.value.type === 'MemberExpression') {
prop = `'[✂${attribute.value.property.start}-${attribute.value.property.end}✂]'`; prop = `'[✂${attribute.value.property.start}-${attribute.value.property
.end}]'`;
obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`; obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`;
} else { } else {
obj = 'state'; obj = 'state';
@ -39,7 +53,15 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
prop prop
}); });
const setter = getSetter({ block, name, snippet, context: '_context', attribute, dependencies, value: 'value' }); const setter = getSetter({
block,
name,
snippet,
context: '_context',
attribute,
dependencies,
value: 'value'
});
generator.hasComplexBindings = true; generator.hasComplexBindings = true;
@ -54,12 +76,16 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
${updating} = true; ${updating} = true;
${setter} ${setter}
${updating} = false; ${updating} = false;
}, { init: ${generator.helper( 'differs' )}( ${local.name}.get( '${attribute.name}' ), ${snippet} ) }); }, { init: ${generator.helper(
'differs'
)}( ${local.name}.get( '${attribute.name}' ), ${snippet} ) });
}); });
`); `);
local.update.addBlock(deindent` local.update.addBlock(deindent`
if ( !${updating} && ${dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) { if ( !${updating} && ${dependencies
.map(dependency => `'${dependency}' in changed`)
.join('||')} ) {
${updating} = true; ${updating} = true;
${local.name}._set({ ${attribute.name}: ${snippet} }); ${local.name}._set({ ${attribute.name}: ${snippet} });
${updating} = false; ${updating} = false;

@ -36,9 +36,16 @@ const visitors = {
Ref: visitRef Ref: visitRef
}; };
export default function visitComponent ( generator: DomGenerator, block: Block, state: State, node: Node ) { export default function visitComponent(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const hasChildren = node.children.length > 0; const hasChildren = node.children.length > 0;
const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() ); const name = block.getUniqueName(
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
);
const childState = node._state; const childState = node._state;
@ -63,27 +70,38 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
node.attributes node.attributes
.sort((a, b) => order[a.type] - order[b.type]) .sort((a, b) => order[a.type] - order[b.type])
.forEach(attribute => { .forEach(attribute => {
visitors[ attribute.type ]( generator, block, childState, node, attribute, local ); visitors[attribute.type](
generator,
block,
childState,
node,
attribute,
local
);
}); });
if (local.allUsedContexts.length) { if (local.allUsedContexts.length) {
const initialProps = local.allUsedContexts.map( contextName => { const initialProps = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `state: state`; if (contextName === 'state') return `state: state`;
const listName = block.listNames.get(contextName); const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName); const indexName = block.indexNames.get(contextName);
return `${listName}: ${listName},\n${indexName}: ${indexName}`; return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' ); })
.join(',\n');
const updates = local.allUsedContexts.map( contextName => { const updates = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `${name}._context.state = state;`; if (contextName === 'state') return `${name}._context.state = state;`;
const listName = block.listNames.get(contextName); const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName); const indexName = block.indexNames.get(contextName);
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
}).join( '\n' ); })
.join('\n');
local.create.addBlock(deindent` local.create.addBlock(deindent`
${name}._context = { ${name}._context = {
@ -121,16 +139,18 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
); );
} }
block.builders.destroy.addLine( block.builders.destroy.addLine(`${yieldFragment}.destroy();`);
`${yieldFragment}.destroy();`
);
componentInitProperties.push(`_yield: ${yieldFragment}`); componentInitProperties.push(`_yield: ${yieldFragment}`);
} }
const statements: string[] = []; const statements: string[] = [];
if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) { if (
local.staticAttributes.length ||
local.dynamicAttributes.length ||
local.bindings.length
) {
const initialProps = local.staticAttributes const initialProps = local.staticAttributes
.concat(local.dynamicAttributes) .concat(local.dynamicAttributes)
.map(attribute => `${attribute.name}: ${attribute.value}`); .map(attribute => `${attribute.name}: ${attribute.value}`);
@ -143,7 +163,9 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
statements.push(`var ${initialData} = ${initialPropString};`); statements.push(`var ${initialData} = ${initialPropString};`);
local.bindings.forEach(binding => { local.bindings.forEach(binding => {
statements.push( `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` ); statements.push(
`if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};`
);
}); });
componentInitProperties.push(`data: ${initialData}`); componentInitProperties.push(`data: ${initialData}`);
@ -152,7 +174,10 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
} }
} }
const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`; const expression = node.name === ':Self'
? generator.name
: generator.importedComponents.get(node.name) ||
`${generator.alias('template')}.components.${node.name}`;
local.create.addBlockAtStart(deindent` local.create.addBlockAtStart(deindent`
${statements.join('\n')} ${statements.join('\n')}
@ -162,14 +187,20 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
`); `);
if (isTopLevel) { if (isTopLevel) {
block.builders.mount.addLine( `${name}._fragment.mount( ${block.target}, anchor );` ); block.builders.mount.addLine(
`${name}._fragment.mount( ${block.target}, anchor );`
);
} }
if (local.dynamicAttributes.length) { if (local.dynamicAttributes.length) {
const updates = local.dynamicAttributes.map(attribute => { const updates = local.dynamicAttributes.map(attribute => {
if (attribute.dependencies.length) { if (attribute.dependencies.length) {
return deindent` return deindent`
if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value}; if ( ${attribute.dependencies
.map(dependency => `'${dependency}' in changed`)
.join(
'||'
)} ) ${name}_changes.${attribute.name} = ${attribute.value};
`; `;
} }
@ -187,7 +218,8 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
`); `);
} }
if ( isTopLevel ) block.builders.unmount.addLine( `${name}._fragment.unmount();` ); if (isTopLevel)
block.builders.unmount.addLine(`${name}._fragment.unmount();`);
block.builders.destroy.addLine(`${name}.destroy( false );`); block.builders.destroy.addLine(`${name}.destroy( false );`);
block.builders.create.addBlock(local.create); block.builders.create.addBlock(local.create);

@ -4,10 +4,20 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function visitEventHandler ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node, local ) { export default function visitEventHandler(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
local
) {
// TODO verify that it's a valid callee (i.e. built-in or declared method) // TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations(attribute.expression); generator.addSourcemapLocations(attribute.expression);
generator.code.prependRight( attribute.expression.start, `${block.component}.` ); generator.code.prependRight(
attribute.expression.start,
`${block.component}.`
);
const usedContexts: string[] = []; const usedContexts: string[] = [];
attribute.expression.arguments.forEach((arg: Node) => { attribute.expression.arguments.forEach((arg: Node) => {
@ -15,7 +25,8 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
contexts.forEach(context => { contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context); if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context ); if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
}); });
}); });
@ -29,7 +40,9 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`; return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`;
}); });
const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`; const handlerBody =
(declarations.length ? declarations.join('\n') + '\n\n' : '') +
`[✂${attribute.expression.start}-${attribute.expression.end}✂];`;
local.create.addBlock(deindent` local.create.addBlock(deindent`
${local.name}.on( '${attribute.name}', function ( event ) { ${local.name}.on( '${attribute.name}', function ( event ) {

@ -4,7 +4,14 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function visitRef ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node, local ) { export default function visitRef(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
local
) {
generator.usesRefs = true; generator.usesRefs = true;
local.create.addLine( local.create.addLine(

@ -5,17 +5,33 @@ import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import { State } from '../interfaces'; import { State } from '../interfaces';
export default function visitEachBlock ( generator: DomGenerator, block: Block, state: State, node: Node ) { export default function visitEachBlock(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const each_block = generator.getUniqueName(`each_block`); const each_block = generator.getUniqueName(`each_block`);
const create_each_block = node._block.name; const create_each_block = node._block.name;
const each_block_value = node._block.listName; const each_block_value = node._block.listName;
const iterations = block.getUniqueName(`${each_block}_iterations`); const iterations = block.getUniqueName(`${each_block}_iterations`);
const i = block.alias(`i`); const i = block.alias(`i`);
const params = block.params.join(', '); const params = block.params.join(', ');
const anchor = node.needsAnchor ? block.getUniqueName( `${each_block}_anchor` ) : ( node.next && node.next._state.name ) || 'null'; const anchor = node.needsAnchor
? block.getUniqueName(`${each_block}_anchor`)
: (node.next && node.next._state.name) || 'null';
const mountOrIntro = node._block.hasIntroMethod ? 'intro' : 'mount'; const mountOrIntro = node._block.hasIntroMethod ? 'intro' : 'mount';
const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro }; const vars = {
each_block,
create_each_block,
each_block_value,
iterations,
i,
params,
anchor,
mountOrIntro
};
const { snippet } = block.contextualise(node.expression); const { snippet } = block.contextualise(node.expression);
@ -30,7 +46,12 @@ export default function visitEachBlock ( generator: DomGenerator, block: Block,
const isToplevel = !state.parentNode; const isToplevel = !state.parentNode;
if (node.needsAnchor) { if (node.needsAnchor) {
block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true ); block.addElement(
anchor,
`${generator.helper('createComment')}()`,
state.parentNode,
true
);
} else if (node.next) { } else if (node.next) {
node.next.usedAsAnchor = true; node.next.usedAsAnchor = true;
} }
@ -43,14 +64,18 @@ export default function visitEachBlock ( generator: DomGenerator, block: Block,
// TODO neaten this up... will end up with an empty line in the block // TODO neaten this up... will end up with an empty line in the block
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
if ( !${each_block_value}.length ) { if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); ${each_block_else} = ${node.else._block
${!isToplevel ? `${each_block_else}.${mountOrIntro}( ${state.parentNode}, null );` : ''} .name}( ${params}, ${block.component} );
${!isToplevel
? `${each_block_else}.${mountOrIntro}( ${state.parentNode}, null );`
: ''}
} }
`); `);
block.builders.mount.addBlock(deindent` block.builders.mount.addBlock(deindent`
if ( ${each_block_else} ) { if ( ${each_block_else} ) {
${each_block_else}.${mountOrIntro}( ${state.parentNode || block.target}, null ); ${each_block_else}.${mountOrIntro}( ${state.parentNode ||
block.target}, null );
} }
`); `);
@ -61,7 +86,8 @@ export default function visitEachBlock ( generator: DomGenerator, block: Block,
if ( !${each_block_value}.length && ${each_block_else} ) { if ( !${each_block_value}.length && ${each_block_else} ) {
${each_block_else}.update( changed, ${params} ); ${each_block_else}.update( changed, ${params} );
} else if ( !${each_block_value}.length ) { } else if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); ${each_block_else} = ${node.else._block
.name}( ${params}, ${block.component} );
${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} ); ${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} );
} else if ( ${each_block_else} ) { } else if ( ${each_block_else} ) {
${each_block_else}.unmount(); ${each_block_else}.unmount();
@ -78,7 +104,8 @@ export default function visitEachBlock ( generator: DomGenerator, block: Block,
${each_block_else} = null; ${each_block_else} = null;
} }
} else if ( !${each_block_else} ) { } else if ( !${each_block_else} ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); ${each_block_else} = ${node.else._block
.name}( ${params}, ${block.component} );
${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} ); ${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} );
} }
`); `);
@ -104,7 +131,22 @@ export default function visitEachBlock ( generator: DomGenerator, block: Block,
} }
} }
function keyed ( generator: DomGenerator, block: Block, state: State, node: Node, snippet, { each_block, create_each_block, each_block_value, i, params, anchor, mountOrIntro } ) { function keyed(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
snippet,
{
each_block,
create_each_block,
each_block_value,
i,
params,
anchor,
mountOrIntro
}
) {
const key = block.getUniqueName('key'); const key = block.getUniqueName('key');
const lookup = block.getUniqueName(`${each_block}_lookup`); const lookup = block.getUniqueName(`${each_block}_lookup`);
const iteration = block.getUniqueName(`${each_block}_iteration`); const iteration = block.getUniqueName(`${each_block}_iteration`);
@ -112,11 +154,17 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
const last = block.getUniqueName(`${each_block}_last`); const last = block.getUniqueName(`${each_block}_last`);
const expected = block.getUniqueName(`${each_block}_expected`); const expected = block.getUniqueName(`${each_block}_expected`);
if ( node.children[0] && node.children[0].type === 'Element' ) { // TODO or text/tag/raw if (node.children[0] && node.children[0].type === 'Element') {
// TODO or text/tag/raw
node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing
} else { } else {
node._block.first = node._block.getUniqueName('first'); node._block.first = node._block.getUniqueName('first');
node._block.addElement( node._block.first, `${generator.helper( 'createComment' )}()`, null, true ); node._block.addElement(
node._block.first,
`${generator.helper('createComment')}()`,
null,
true
);
} }
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
@ -128,7 +176,8 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key}; var ${key} = ${each_block_value}[${i}].${node.key};
var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${state.parentNode && `${iteration}.${mountOrIntro}( ${state.parentNode}, null );`} ${state.parentNode &&
`${iteration}.${mountOrIntro}( ${state.parentNode}, null );`}
if ( ${last} ) ${last}.next = ${iteration}; if ( ${last} ) ${last}.next = ${iteration};
${iteration}.last = ${last}; ${iteration}.last = ${last};
@ -213,7 +262,8 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
var ${key} = ${each_block_value}[${i}].${node.key}; var ${key} = ${each_block_value}[${i}].${node.key};
var ${iteration} = ${lookup}[${key}]; var ${iteration} = ${lookup}[${key}];
${dynamic && `if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`} ${dynamic &&
`if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`}
if ( ${expected} ) { if ( ${expected} ) {
if ( ${key} === ${expected}.key ) { if ( ${key} === ${expected}.key ) {
@ -256,7 +306,8 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
if ( ${last} ) ${last}.next = ${iteration}; if ( ${last} ) ${last}.next = ${iteration};
${iteration}.last = ${last}; ${iteration}.last = ${last};
${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`} ${node._block.hasIntroMethod &&
`${iteration}.intro( ${parentNode}, ${anchor} );`}
${last} = ${iteration}; ${last} = ${iteration};
} }
@ -286,13 +337,29 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
`); `);
} }
function unkeyed ( generator: DomGenerator, block: Block, state: State, node: Node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) { function unkeyed(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
snippet,
{
create_each_block,
each_block_value,
iterations,
i,
params,
anchor,
mountOrIntro
}
) {
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
var ${iterations} = []; var ${iterations} = [];
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) { for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`} ${state.parentNode &&
`${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`}
} }
`); `);
@ -318,25 +385,25 @@ function unkeyed ( generator: DomGenerator, block: Block, state: State, node: No
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
if (condition !== '') { if (condition !== '') {
const forLoopBody = node._block.hasUpdateMethod ? const forLoopBody = node._block.hasUpdateMethod
node._block.hasIntroMethod ? ? node._block.hasIntroMethod
deindent` ? deindent`
if ( ${iterations}[${i}] ) { if ( ${iterations}[${i}] ) {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} ); ${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else { } else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
} }
${iterations}[${i}].intro( ${parentNode}, ${anchor} ); ${iterations}[${i}].intro( ${parentNode}, ${anchor} );
` : `
deindent` : deindent`
if ( ${iterations}[${i}] ) { if ( ${iterations}[${i}] ) {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} ); ${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else { } else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].mount( ${parentNode}, ${anchor} ); ${iterations}[${i}].mount( ${parentNode}, ${anchor} );
} }
` : `
deindent` : deindent`
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} ); ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} ); ${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} );
`; `;
@ -344,8 +411,8 @@ function unkeyed ( generator: DomGenerator, block: Block, state: State, node: No
const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`; const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`;
const outro = block.getUniqueName('outro'); const outro = block.getUniqueName('outro');
const destroy = node._block.hasOutroMethod ? const destroy = node._block.hasOutroMethod
deindent` ? deindent`
function ${outro} ( i ) { function ${outro} ( i ) {
if ( ${iterations}[i] ) { if ( ${iterations}[i] ) {
${iterations}[i].outro( function () { ${iterations}[i].outro( function () {
@ -357,8 +424,8 @@ function unkeyed ( generator: DomGenerator, block: Block, state: State, node: No
} }
for ( ; ${i} < ${iterations}.length; ${i} += 1 ) ${outro}( ${i} ); for ( ; ${i} < ${iterations}.length; ${i} += 1 ) ${outro}( ${i} );
` : `
deindent` : deindent`
for ( ; ${i} < ${iterations}.length; ${i} += 1 ) { for ( ; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].unmount(); ${iterations}[${i}].unmount();
${iterations}[${i}].destroy(); ${iterations}[${i}].destroy();

@ -6,25 +6,39 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function visitAttribute ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) { export default function visitAttribute(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node
) {
const name = attribute.name; const name = attribute.name;
let metadata = state.namespace ? null : attributeLookup[name]; let metadata = state.namespace ? null : attributeLookup[name];
if ( metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf( node.name ) ) metadata = null; if (metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf(node.name))
metadata = null;
const isIndirectlyBoundValue = name === 'value' && ( const isIndirectlyBoundValue =
node.name === 'option' || // TODO check it's actually bound name === 'value' &&
node.name === 'input' && /^(checkbox|radio)$/.test( getStaticAttributeValue( node, 'type' ) ) (node.name === 'option' || // TODO check it's actually bound
); (node.name === 'input' &&
/^(checkbox|radio)$/.test(getStaticAttributeValue(node, 'type'))));
const propertyName = isIndirectlyBoundValue ? '__value' : metadata && metadata.propertyName; const propertyName = isIndirectlyBoundValue
? '__value'
: metadata && metadata.propertyName;
// xlink is a special case... we could maybe extend this to generic // xlink is a special case... we could maybe extend this to generic
// namespaced attributes but I'm not sure that's applicable in // namespaced attributes but I'm not sure that's applicable in
// HTML5? // HTML5?
const method = name.slice( 0, 6 ) === 'xlink:' ? 'setXlinkAttribute' : 'setAttribute'; const method = name.slice(0, 6) === 'xlink:'
? 'setXlinkAttribute'
: 'setAttribute';
const isDynamic = attribute.value !== true && attribute.value.length > 1 || ( attribute.value.length === 1 && attribute.value[0].type !== 'Text' ); const isDynamic =
(attribute.value !== true && attribute.value.length > 1) ||
(attribute.value.length === 1 && attribute.value[0].type !== 'Text');
if (isDynamic) { if (isDynamic) {
let value; let value;
@ -35,35 +49,42 @@ export default function visitAttribute ( generator: DomGenerator, block: Block,
value = snippet; value = snippet;
} else { } else {
// '{{foo}} {{bar}}' — treat as string concatenation // '{{foo}} {{bar}}' — treat as string concatenation
value = ( attribute.value[0].type === 'Text' ? '' : `"" + ` ) + ( value =
attribute.value.map( ( chunk: Node ) => { (attribute.value[0].type === 'Text' ? '' : `"" + `) +
attribute.value
.map((chunk: Node) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return JSON.stringify(chunk.data); return JSON.stringify(chunk.data);
} else { } else {
const { snippet } = block.contextualise(chunk.expression); const { snippet } = block.contextualise(chunk.expression);
return `( ${snippet} )`; return `( ${snippet} )`;
} }
}).join( ' + ' ) })
); .join(' + ');
} }
const last = block.getUniqueName( `${state.parentNode}_${name.replace( /[^a-zA-Z_$]/g, '_')}_value` ); const last = block.getUniqueName(
`${state.parentNode}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
block.addVariable(last); block.addVariable(last);
const isSelectValueAttribute = name === 'value' && state.parentNodeName === 'select'; const isSelectValueAttribute =
name === 'value' && state.parentNodeName === 'select';
let updater; let updater;
if (isSelectValueAttribute) { if (isSelectValueAttribute) {
// annoying special case // annoying special case
const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue const isMultipleSelect =
node.name === 'select' &&
node.attributes.find(attr => attr.name.toLowerCase() === 'multiple'); // TODO use getStaticAttributeValue
const i = block.getUniqueName('i'); const i = block.getUniqueName('i');
const option = block.getUniqueName('option'); const option = block.getUniqueName('option');
const ifStatement = isMultipleSelect ? const ifStatement = isMultipleSelect
deindent` ? deindent`
${option}.selected = ~${last}.indexOf( ${option}.__value );` : ${option}.selected = ~${last}.indexOf( ${option}.__value );`
deindent` : deindent`
if ( ${option}.__value === ${last} ) { if ( ${option}.__value === ${last} ) {
${option}.selected = true; ${option}.selected = true;
break; break;
@ -82,11 +103,19 @@ export default function visitAttribute ( generator: DomGenerator, block: Block,
${updater} ${updater}
`); `);
} else if (propertyName) { } else if (propertyName) {
block.builders.create.addLine( `${state.parentNode}.${propertyName} = ${last} = ${value};` ); block.builders.create.addLine(
`${state.parentNode}.${propertyName} = ${last} = ${value};`
);
updater = `${state.parentNode}.${propertyName} = ${last};`; updater = `${state.parentNode}.${propertyName} = ${last};`;
} else { } else {
block.builders.create.addLine( `${generator.helper( method )}( ${state.parentNode}, '${name}', ${last} = ${value} );` ); block.builders.create.addLine(
updater = `${generator.helper( method )}( ${state.parentNode}, '${name}', ${last} );`; `${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${last} = ${value} );`
);
updater = `${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${last} );`;
} }
block.builders.update.addBlock(deindent` block.builders.update.addBlock(deindent`
@ -94,17 +123,18 @@ export default function visitAttribute ( generator: DomGenerator, block: Block,
${updater} ${updater}
} }
`); `);
} } else {
const value = attribute.value === true
else { ? 'true'
const value = attribute.value === true ? 'true' : : attribute.value.length === 0
attribute.value.length === 0 ? `''` : ? `''`
JSON.stringify( attribute.value[0].data ); : JSON.stringify(attribute.value[0].data);
const statement = propertyName ? const statement = propertyName
`${state.parentNode}.${propertyName} = ${value};` : ? `${state.parentNode}.${propertyName} = ${value};`
`${generator.helper( method )}( ${state.parentNode}, '${name}', ${value} );`; : `${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${value} );`;
block.builders.create.addLine(statement); block.builders.create.addLine(statement);

@ -13,25 +13,62 @@ function getObject ( node ) {
return node; return node;
} }
export default function visitBinding ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) { export default function visitBinding(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node
) {
const { name } = getObject(attribute.value); const { name } = getObject(attribute.value);
const { snippet, contexts } = block.contextualise(attribute.value); const { snippet, contexts } = block.contextualise(attribute.value);
const dependencies = block.contextDependencies.has( name ) ? block.contextDependencies.get( name ) : [ name ]; const dependencies = block.contextDependencies.has(name)
? block.contextDependencies.get(name)
: [name];
if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' ); if (dependencies.length > 1)
throw new Error(
'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!'
);
contexts.forEach(context => { contexts.forEach(context => {
if ( !~state.allUsedContexts.indexOf( context ) ) state.allUsedContexts.push( context ); if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
}); });
const eventName = getBindingEventName(node, attribute); const eventName = getBindingEventName(node, attribute);
const handler = block.getUniqueName( `${state.parentNode}_${eventName}_handler` ); const handler = block.getUniqueName(
const isMultipleSelect = node.name === 'select' && node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue `${state.parentNode}_${eventName}_handler`
);
const isMultipleSelect =
node.name === 'select' &&
node.attributes.find(
(attr: Node) => attr.name.toLowerCase() === 'multiple'
); // TODO use getStaticAttributeValue
const type = getStaticAttributeValue(node, 'type'); const type = getStaticAttributeValue(node, 'type');
const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, attribute.value ) : null; const bindingGroup = attribute.name === 'group'
const value = getBindingValue( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type ); ? getBindingGroup(generator, attribute.value)
: null;
const value = getBindingValue(
generator,
block,
state,
node,
attribute,
isMultipleSelect,
bindingGroup,
type
);
let setter = getSetter({ block, name, snippet, context: '_svelte', attribute, dependencies, value }); let setter = getSetter({
block,
name,
snippet,
context: '_svelte',
attribute,
dependencies,
value
});
let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`; let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`;
const lock = block.alias(`${state.parentNode}_updating`); const lock = block.alias(`${state.parentNode}_updating`);
let updateCondition = `!${lock}`; let updateCondition = `!${lock}`;
@ -48,10 +85,10 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
const i = block.alias('i'); const i = block.alias('i');
const option = block.getUniqueName('option'); const option = block.getUniqueName('option');
const ifStatement = isMultipleSelect ? const ifStatement = isMultipleSelect
deindent` ? deindent`
${option}.selected = ~${value}.indexOf( ${option}.__value );` : ${option}.selected = ~${value}.indexOf( ${option}.__value );`
deindent` : deindent`
if ( ${option}.__value === ${value} ) { if ( ${option}.__value === ${value} ) {
${option}.selected = true; ${option}.selected = true;
break; break;
@ -65,10 +102,8 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
${ifStatement} ${ifStatement}
} }
`; `;
} } else if (attribute.name === 'group') {
// <input type='checkbox|radio' bind:group='selected'> special case // <input type='checkbox|radio' bind:group='selected'> special case
else if ( attribute.name === 'group' ) {
if (type === 'radio') { if (type === 'radio') {
setter = deindent` setter = deindent`
if ( !${state.parentNode}.checked ) return; if ( !${state.parentNode}.checked ) return;
@ -76,9 +111,9 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
`; `;
} }
const condition = type === 'checkbox' ? const condition = type === 'checkbox'
`~${snippet}.indexOf( ${state.parentNode}.__value )` : ? `~${snippet}.indexOf( ${state.parentNode}.__value )`
`${state.parentNode}.__value === ${snippet}`; : `${state.parentNode}.__value === ${snippet}`;
block.builders.create.addLine( block.builders.create.addLine(
`${block.component}._bindingGroups[${bindingGroup}].push( ${state.parentNode} );` `${block.component}._bindingGroups[${bindingGroup}].push( ${state.parentNode} );`
@ -89,11 +124,11 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
); );
updateElement = `${state.parentNode}.checked = ${condition};`; updateElement = `${state.parentNode}.checked = ${condition};`;
} } else if (node.name === 'audio' || node.name === 'video') {
else if ( node.name === 'audio' || node.name === 'video' ) {
generator.hasComplexBindings = true; generator.hasComplexBindings = true;
block.builders.create.addBlock( `${block.component}._bindings.push( ${handler} );` ); block.builders.create.addBlock(
`${block.component}._bindings.push( ${handler} );`
);
if (attribute.name === 'currentTime') { if (attribute.name === 'currentTime') {
const frame = block.getUniqueName(`${state.parentNode}_animationframe`); const frame = block.getUniqueName(`${state.parentNode}_animationframe`);
@ -105,13 +140,9 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
`; `;
updateCondition += ` && !isNaN( ${snippet} )`; updateCondition += ` && !isNaN( ${snippet} )`;
} } else if (attribute.name === 'duration') {
else if ( attribute.name === 'duration' ) {
updateCondition = null; updateCondition = null;
} } else if (attribute.name === 'paused') {
else if ( attribute.name === 'paused' ) {
// this is necessary to prevent the audio restarting by itself // this is necessary to prevent the audio restarting by itself
const last = block.getUniqueName(`${state.parentNode}_paused_value`); const last = block.getUniqueName(`${state.parentNode}_paused_value`);
block.addVariable(last, 'true'); block.addVariable(last, 'true');
@ -128,10 +159,13 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
${lock} = false; ${lock} = false;
} }
${generator.helper( 'addEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} ); ${generator.helper(
'addEventListener'
)}( ${state.parentNode}, '${eventName}', ${handler} );
`); `);
if ( node.name !== 'audio' && node.name !== 'video' ) node.initialUpdate = updateElement; if (node.name !== 'audio' && node.name !== 'video')
node.initialUpdate = updateElement;
if (updateCondition !== null) { if (updateCondition !== null) {
// audio/video duration is read-only, it never updates // audio/video duration is read-only, it never updates
@ -143,18 +177,30 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
} }
block.builders.destroy.addLine(deindent` block.builders.destroy.addLine(deindent`
${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} ); ${generator.helper(
'removeEventListener'
)}( ${state.parentNode}, '${eventName}', ${handler} );
`); `);
if (attribute.name === 'paused') { if (attribute.name === 'paused') {
block.builders.create.addLine( `${generator.helper( 'addEventListener' )}( ${state.parentNode}, 'play', ${handler} );` ); block.builders.create.addLine(
block.builders.destroy.addLine( `${generator.helper( 'removeEventListener' )}( ${state.parentNode}, 'play', ${handler} );` ); `${generator.helper(
'addEventListener'
)}( ${state.parentNode}, 'play', ${handler} );`
);
block.builders.destroy.addLine(
`${generator.helper(
'removeEventListener'
)}( ${state.parentNode}, 'play', ${handler} );`
);
} }
} }
function getBindingEventName(node: Node, attribute: Node) { function getBindingEventName(node: Node, attribute: Node) {
if (node.name === 'input') { if (node.name === 'input') {
const typeAttribute = node.attributes.find( ( attr: Node ) => attr.type === 'Attribute' && attr.name === 'type' ); const typeAttribute = node.attributes.find(
(attr: Node) => attr.type === 'Attribute' && attr.name === 'type'
);
const type = typeAttribute ? typeAttribute.value[0].data : 'text'; // TODO in validation, should throw if type attribute is not static const type = typeAttribute ? typeAttribute.value[0].data : 'text'; // TODO in validation, should throw if type attribute is not static
return type === 'checkbox' || type === 'radio' ? 'change' : 'input'; return type === 'checkbox' || type === 'radio' ? 'change' : 'input';
@ -168,7 +214,16 @@ function getBindingEventName ( node: Node, attribute: Node ) {
return 'change'; return 'change';
} }
function getBindingValue ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node, isMultipleSelect: boolean, bindingGroup: number, type: string ) { function getBindingValue(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
isMultipleSelect: boolean,
bindingGroup: number,
type: string
) {
// <select multiple bind:value='selected> // <select multiple bind:value='selected>
if (isMultipleSelect) { if (isMultipleSelect) {
return `[].map.call( ${state.parentNode}.querySelectorAll(':checked'), function ( option ) { return option.__value; })`; return `[].map.call( ${state.parentNode}.querySelectorAll(':checked'), function ( option ) { return option.__value; })`;
@ -182,7 +237,9 @@ function getBindingValue ( generator: DomGenerator, block: Block, state: State,
// <input type='checkbox' bind:group='foo'> // <input type='checkbox' bind:group='foo'>
if (attribute.name === 'group') { if (attribute.name === 'group') {
if (type === 'checkbox') { if (type === 'checkbox') {
return `${generator.helper( 'getBindingGroupValue' )}( ${block.component}._bindingGroups[${bindingGroup}] )`; return `${generator.helper(
'getBindingGroupValue'
)}( ${block.component}._bindingGroups[${bindingGroup}] )`;
} }
return `${state.parentNode}.__value`; return `${state.parentNode}.__value`;
@ -190,7 +247,9 @@ function getBindingValue ( generator: DomGenerator, block: Block, state: State,
// <input type='range|number' bind:value> // <input type='range|number' bind:value>
if (type === 'range' || type === 'number') { if (type === 'range' || type === 'number') {
return `${generator.helper( 'toNumber' )}( ${state.parentNode}.${attribute.name} )`; return `${generator.helper(
'toNumber'
)}( ${state.parentNode}.${attribute.name} )`;
} }
// everything else // everything else

@ -30,7 +30,12 @@ const visitors = {
Ref: visitRef Ref: visitRef
}; };
export default function visitElement ( generator: DomGenerator, block: Block, state: State, node: Node ) { export default function visitElement(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
if (node.name in meta) { if (node.name in meta) {
return meta[node.name](generator, block, node); return meta[node.name](generator, block, node);
} }
@ -42,12 +47,22 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
const childState = node._state; const childState = node._state;
const name = childState.parentNode; const name = childState.parentNode;
block.builders.create.addLine( `var ${name} = ${getRenderStatement( generator, childState.namespace, node.name )};` ); block.builders.create.addLine(
`var ${name} = ${getRenderStatement(
generator,
childState.namespace,
node.name
)};`
);
block.mount(name, state.parentNode); block.mount(name, state.parentNode);
// add CSS encapsulation attribute // add CSS encapsulation attribute
if (generator.cssId && (!generator.cascade || state.isTopLevel)) { if (generator.cssId && (!generator.cascade || state.isTopLevel)) {
block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` ); block.builders.create.addLine(
`${generator.helper(
'setAttribute'
)}( ${name}, '${generator.cssId}', '' );`
);
} }
function visitAttributesAndAddProps() { function visitAttributesAndAddProps() {
@ -66,7 +81,8 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
visitors[attribute.type](generator, block, childState, node, attribute); visitors[attribute.type](generator, block, childState, node, attribute);
}); });
if ( intro || outro ) addTransitions( generator, block, childState, node, intro, outro ); if (intro || outro)
addTransitions(generator, block, childState, node, intro, outro);
if (childState.allUsedContexts.length || childState.usesComponent) { if (childState.allUsedContexts.length || childState.usesComponent) {
const initialProps: string[] = []; const initialProps: string[] = [];
@ -82,8 +98,12 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
const listName = block.listNames.get(contextName); const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName); const indexName = block.indexNames.get(contextName);
initialProps.push( `${listName}: ${listName},\n${indexName}: ${indexName}` ); initialProps.push(
updates.push( `${name}._svelte.${listName} = ${listName};\n${name}._svelte.${indexName} = ${indexName};` ); `${listName}: ${listName},\n${indexName}: ${indexName}`
);
updates.push(
`${name}._svelte.${listName} = ${listName};\n${name}._svelte.${indexName} = ${indexName};`
);
}); });
if (initialProps.length) { if (initialProps.length) {
@ -103,7 +123,9 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
if (!state.parentNode) { if (!state.parentNode) {
// TODO we eventually need to consider what happens to elements // TODO we eventually need to consider what happens to elements
// that belong to the same outgroup as an outroing element... // that belong to the same outgroup as an outroing element...
block.builders.unmount.addLine( `${generator.helper( 'detachNode' )}( ${name} );` ); block.builders.unmount.addLine(
`${generator.helper('detachNode')}( ${name} );`
);
} }
if (node.name !== 'select') { if (node.name !== 'select') {
@ -127,7 +149,14 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
} }
// special case bound <option> without a value attribute // special case bound <option> without a value attribute
if ( node.name === 'option' && !node.attributes.find( ( attribute: Node ) => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound if (
node.name === 'option' &&
!node.attributes.find(
(attribute: Node) =>
attribute.type === 'Attribute' && attribute.name === 'value'
)
) {
// TODO check it's bound
const statement = `${name}.__value = ${name}.textContent;`; const statement = `${name}.__value = ${name}.textContent;`;
node.initialUpdate = node.lateUpdate = statement; node.initialUpdate = node.lateUpdate = statement;
} }
@ -149,7 +178,11 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
} }
} }
function getRenderStatement ( generator: DomGenerator, namespace: string, name: string ) { function getRenderStatement(
generator: DomGenerator,
namespace: string,
name: string
) {
if (namespace === 'http://www.w3.org/2000/svg') { if (namespace === 'http://www.w3.org/2000/svg') {
return `${generator.helper('createSvgElement')}( '${name}' )`; return `${generator.helper('createSvgElement')}( '${name}' )`;
} }

@ -5,7 +5,13 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function visitEventHandler ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) { export default function visitEventHandler(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node
) {
const name = attribute.name; const name = attribute.name;
const isCustomEvent = generator.events.has(name); const isCustomEvent = generator.events.has(name);
const shouldHoist = !isCustomEvent && state.inEachBlock; const shouldHoist = !isCustomEvent && state.inEachBlock;
@ -16,7 +22,10 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
if (flattened.name !== 'event' && flattened.name !== 'this') { if (flattened.name !== 'event' && flattened.name !== 'this') {
// allow event.stopPropagation(), this.select() etc // allow event.stopPropagation(), this.select() etc
// TODO verify that it's a valid callee (i.e. built-in or declared method) // TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.code.prependRight( attribute.expression.start, `${block.component}.` ); generator.code.prependRight(
attribute.expression.start,
`${block.component}.`
);
if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works! if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works!
} }
@ -27,7 +36,8 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
contexts.forEach(context => { contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context); if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if ( !~state.allUsedContexts.indexOf( context ) ) state.allUsedContexts.push( context ); if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
}); });
}); });
@ -47,7 +57,9 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
// get a name for the event handler that is globally unique // get a name for the event handler that is globally unique
// if hoisted, locally unique otherwise // if hoisted, locally unique otherwise
const handlerName = ( shouldHoist ? generator : block ).getUniqueName( `${name.replace( /[^a-zA-Z0-9_$]/g, '_' )}_handler` ); const handlerName = (shouldHoist ? generator : block).getUniqueName(
`${name.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler`
);
// create the handler body // create the handler body
const handlerBody = deindent` const handlerBody = deindent`
@ -56,22 +68,26 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
[${attribute.expression.start}-${attribute.expression.end}]; [${attribute.expression.start}-${attribute.expression.end}];
`; `;
const handler = isCustomEvent ? const handler = isCustomEvent
deindent` ? deindent`
var ${handlerName} = ${generator.alias( 'template' )}.events.${name}.call( ${block.component}, ${state.parentNode}, function ( event ) { var ${handlerName} = ${generator.alias(
'template'
)}.events.${name}.call( ${block.component}, ${state.parentNode}, function ( event ) {
${handlerBody} ${handlerBody}
}); });
` : `
deindent` : deindent`
function ${handlerName} ( event ) { function ${handlerName} ( event ) {
${handlerBody} ${handlerBody}
} }
`; `;
if (shouldHoist) { if (shouldHoist) {
generator.blocks.push(<Block>{ generator.blocks.push(
<Block>{
render: () => handler render: () => handler
}); }
);
} else { } else {
block.builders.create.addBlock(handler); block.builders.create.addBlock(handler);
} }
@ -82,11 +98,15 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
`); `);
} else { } else {
block.builders.create.addLine(deindent` block.builders.create.addLine(deindent`
${generator.helper( 'addEventListener' )}( ${state.parentNode}, '${name}', ${handlerName} ); ${generator.helper(
'addEventListener'
)}( ${state.parentNode}, '${name}', ${handlerName} );
`); `);
block.builders.destroy.addLine(deindent` block.builders.destroy.addLine(deindent`
${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${name}', ${handlerName} ); ${generator.helper(
'removeEventListener'
)}( ${state.parentNode}, '${name}', ${handlerName} );
`); `);
} }
} }

@ -4,7 +4,13 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function visitRef ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) { export default function visitRef(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node
) {
const name = attribute.name; const name = attribute.name;
block.builders.create.addLine( block.builders.create.addLine(

@ -4,12 +4,21 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
import { State } from '../../interfaces'; import { State } from '../../interfaces';
export default function addTransitions ( generator: DomGenerator, block: Block, state: State, node: Node, intro, outro ) { export default function addTransitions(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
intro,
outro
) {
const wrapTransition = generator.helper('wrapTransition'); const wrapTransition = generator.helper('wrapTransition');
if (intro === outro) { if (intro === outro) {
const name = block.getUniqueName(`${state.name}_transition`); const name = block.getUniqueName(`${state.name}_transition`);
const snippet = intro.expression ? block.contextualise( intro.expression ).snippet : '{}'; const snippet = intro.expression
? block.contextualise(intro.expression).snippet
: '{}';
block.addVariable(name); block.addVariable(name);
@ -31,15 +40,15 @@ export default function addTransitions ( generator: DomGenerator, block: Block,
${name} = null; ${name} = null;
}); });
`); `);
} } else {
else {
const introName = intro && block.getUniqueName(`${state.name}_intro`); const introName = intro && block.getUniqueName(`${state.name}_intro`);
const outroName = outro && block.getUniqueName(`${state.name}_outro`); const outroName = outro && block.getUniqueName(`${state.name}_outro`);
if (intro) { if (intro) {
block.addVariable(introName); block.addVariable(introName);
const snippet = intro.expression ? block.contextualise( intro.expression ).snippet : '{}'; const snippet = intro.expression
? block.contextualise(intro.expression).snippet
: '{}';
const fn = `${generator.alias('template')}.transitions.${intro.name}`; // TODO add built-in transitions? const fn = `${generator.alias('template')}.transitions.${intro.name}`; // TODO add built-in transitions?
@ -62,7 +71,9 @@ export default function addTransitions ( generator: DomGenerator, block: Block,
if (outro) { if (outro) {
block.addVariable(outroName); block.addVariable(outroName);
const snippet = outro.expression ? block.contextualise( outro.expression ).snippet : '{}'; const snippet = outro.expression
? block.contextualise(outro.expression).snippet
: '{}';
const fn = `${generator.alias('template')}.transitions.${outro.name}`; const fn = `${generator.alias('template')}.transitions.${outro.name}`;

@ -1,7 +1,9 @@
import { Node } from '../../../../interfaces'; import { Node } from '../../../../interfaces';
export default function getStaticAttributeValue(node: Node, name: string) { export default function getStaticAttributeValue(node: Node, name: string) {
const attribute = node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === name ); const attribute = node.attributes.find(
(attr: Node) => attr.name.toLowerCase() === name
);
if (!attribute) return null; if (!attribute) return null;
if (attribute.value.length !== 1 || attribute.value[0].type !== 'Text') { if (attribute.value.length !== 1 || attribute.value[0].type !== 'Text') {

@ -4,7 +4,24 @@ const lookup = {
'accept-charset': { propertyName: 'acceptCharset', appliesTo: ['form'] }, 'accept-charset': { propertyName: 'acceptCharset', appliesTo: ['form'] },
accesskey: { propertyName: 'accessKey' }, accesskey: { propertyName: 'accessKey' },
action: { appliesTo: ['form'] }, action: { appliesTo: ['form'] },
align: { appliesTo: [ 'applet', 'caption', 'col', 'colgroup', 'hr', 'iframe', 'img', 'table', 'tbody', 'td', 'tfoot' , 'th', 'thead', 'tr' ] }, align: {
appliesTo: [
'applet',
'caption',
'col',
'colgroup',
'hr',
'iframe',
'img',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr'
]
},
allowfullscreen: { propertyName: 'allowFullscreen', appliesTo: ['iframe'] }, allowfullscreen: { propertyName: 'allowFullscreen', appliesTo: ['iframe'] },
alt: { appliesTo: ['applet', 'area', 'img', 'input'] }, alt: { appliesTo: ['applet', 'area', 'img', 'input'] },
async: { appliesTo: ['script'] }, async: { appliesTo: ['script'] },
@ -12,7 +29,21 @@ const lookup = {
autofocus: { appliesTo: ['button', 'input', 'keygen', 'select', 'textarea'] }, autofocus: { appliesTo: ['button', 'input', 'keygen', 'select', 'textarea'] },
autoplay: { appliesTo: ['audio', 'video'] }, autoplay: { appliesTo: ['audio', 'video'] },
autosave: { appliesTo: ['input'] }, autosave: { appliesTo: ['input'] },
bgcolor: { propertyName: 'bgColor', appliesTo: [ 'body', 'col', 'colgroup', 'marquee', 'table', 'tbody', 'tfoot', 'td', 'th', 'tr' ] }, bgcolor: {
propertyName: 'bgColor',
appliesTo: [
'body',
'col',
'colgroup',
'marquee',
'table',
'tbody',
'tfoot',
'td',
'th',
'tr'
]
},
border: { appliesTo: ['img', 'object', 'table'] }, border: { appliesTo: ['img', 'object', 'table'] },
buffered: { appliesTo: ['audio', 'video'] }, buffered: { appliesTo: ['audio', 'video'] },
challenge: { appliesTo: ['keygen'] }, challenge: { appliesTo: ['keygen'] },
@ -36,16 +67,44 @@ const lookup = {
defer: { appliesTo: ['script'] }, defer: { appliesTo: ['script'] },
dir: {}, dir: {},
dirname: { propertyName: 'dirName', appliesTo: ['input', 'textarea'] }, dirname: { propertyName: 'dirName', appliesTo: ['input', 'textarea'] },
disabled: { appliesTo: [ 'button', 'command', 'fieldset', 'input', 'keygen', 'optgroup', 'option', 'select', 'textarea' ] }, disabled: {
appliesTo: [
'button',
'command',
'fieldset',
'input',
'keygen',
'optgroup',
'option',
'select',
'textarea'
]
},
download: { appliesTo: ['a', 'area'] }, download: { appliesTo: ['a', 'area'] },
draggable: {}, draggable: {},
dropzone: {}, dropzone: {},
enctype: { appliesTo: ['form'] }, enctype: { appliesTo: ['form'] },
for: { propertyName: 'htmlFor', appliesTo: ['label', 'output'] }, for: { propertyName: 'htmlFor', appliesTo: ['label', 'output'] },
form: { appliesTo: [ 'button', 'fieldset', 'input', 'keygen', 'label', 'meter', 'object', 'output', 'progress', 'select', 'textarea' ] }, form: {
appliesTo: [
'button',
'fieldset',
'input',
'keygen',
'label',
'meter',
'object',
'output',
'progress',
'select',
'textarea'
]
},
formaction: { appliesTo: ['input', 'button'] }, formaction: { appliesTo: ['input', 'button'] },
headers: { appliesTo: ['td', 'th'] }, headers: { appliesTo: ['td', 'th'] },
height: { appliesTo: [ 'canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video' ] }, height: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video']
},
hidden: {}, hidden: {},
high: { appliesTo: ['meter'] }, high: { appliesTo: ['meter'] },
href: { appliesTo: ['a', 'area', 'base', 'link'] }, href: { appliesTo: ['a', 'area', 'base', 'link'] },
@ -70,7 +129,23 @@ const lookup = {
min: { appliesTo: ['input', 'meter'] }, min: { appliesTo: ['input', 'meter'] },
multiple: { appliesTo: ['input', 'select'] }, multiple: { appliesTo: ['input', 'select'] },
muted: { appliesTo: ['video'] }, muted: { appliesTo: ['video'] },
name: { appliesTo: [ 'button', 'form', 'fieldset', 'iframe', 'input', 'keygen', 'object', 'output', 'select', 'textarea', 'map', 'meta', 'param' ] }, name: {
appliesTo: [
'button',
'form',
'fieldset',
'iframe',
'input',
'keygen',
'object',
'output',
'select',
'textarea',
'map',
'meta',
'param'
]
},
novalidate: { propertyName: 'noValidate', appliesTo: ['form'] }, novalidate: { propertyName: 'noValidate', appliesTo: ['form'] },
open: { appliesTo: ['details'] }, open: { appliesTo: ['details'] },
optimum: { appliesTo: ['meter'] }, optimum: { appliesTo: ['meter'] },
@ -96,7 +171,19 @@ const lookup = {
sizes: { appliesTo: ['link', 'img', 'source'] }, sizes: { appliesTo: ['link', 'img', 'source'] },
span: { appliesTo: ['col', 'colgroup'] }, span: { appliesTo: ['col', 'colgroup'] },
spellcheck: {}, spellcheck: {},
src: { appliesTo: [ 'audio', 'embed', 'iframe', 'img', 'input', 'script', 'source', 'track', 'video' ] }, src: {
appliesTo: [
'audio',
'embed',
'iframe',
'img',
'input',
'script',
'source',
'track',
'video'
]
},
srcdoc: { appliesTo: ['iframe'] }, srcdoc: { appliesTo: ['iframe'] },
srclang: { appliesTo: ['track'] }, srclang: { appliesTo: ['track'] },
srcset: { appliesTo: ['img'] }, srcset: { appliesTo: ['img'] },
@ -107,10 +194,36 @@ const lookup = {
tabindex: { propertyName: 'tabIndex' }, tabindex: { propertyName: 'tabIndex' },
target: { appliesTo: ['a', 'area', 'base', 'form'] }, target: { appliesTo: ['a', 'area', 'base', 'form'] },
title: {}, title: {},
type: { appliesTo: [ 'button', 'input', 'command', 'embed', 'object', 'script', 'source', 'style', 'menu' ] }, type: {
appliesTo: [
'button',
'input',
'command',
'embed',
'object',
'script',
'source',
'style',
'menu'
]
},
usemap: { propertyName: 'useMap', appliesTo: ['img', 'input', 'object'] }, usemap: { propertyName: 'useMap', appliesTo: ['img', 'input', 'object'] },
value: { appliesTo: [ 'button', 'option', 'input', 'li', 'meter', 'progress', 'param', 'select', 'textarea' ] }, value: {
width: { appliesTo: [ 'canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video' ] }, appliesTo: [
'button',
'option',
'input',
'li',
'meter',
'progress',
'param',
'select',
'textarea'
]
},
width: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video']
},
wrap: { appliesTo: ['textarea'] } wrap: { appliesTo: ['textarea'] }
}; };

@ -22,7 +22,11 @@ const readonly = new Set([
'online' 'online'
]); ]);
export default function visitWindow ( generator: DomGenerator, block: Block, node: Node ) { export default function visitWindow(
generator: DomGenerator,
block: Block,
node: Node
) {
const events = {}; const events = {};
const bindings = {}; const bindings = {};
@ -41,7 +45,10 @@ export default function visitWindow ( generator: DomGenerator, block: Block, nod
const flattened = flattenReference(attribute.expression.callee); const flattened = flattenReference(attribute.expression.callee);
if (flattened.name !== 'event' && flattened.name !== 'this') { if (flattened.name !== 'event' && flattened.name !== 'this') {
// allow event.stopPropagation(), this.select() etc // allow event.stopPropagation(), this.select() etc
generator.code.prependRight( attribute.expression.start, `${block.component}.` ); generator.code.prependRight(
attribute.expression.start,
`${block.component}.`
);
} }
const handlerName = block.getUniqueName(`onwindow${attribute.name}`); const handlerName = block.getUniqueName(`onwindow${attribute.name}`);
@ -80,7 +87,9 @@ export default function visitWindow ( generator: DomGenerator, block: Block, nod
} }
if (!events[associatedEvent]) events[associatedEvent] = []; if (!events[associatedEvent]) events[associatedEvent] = [];
events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` ); events[associatedEvent].push(
`${attribute.value.name}: this.${attribute.name}`
);
// add initial value // add initial value
generator.metaBindings.push( generator.metaBindings.push(
@ -95,7 +104,8 @@ export default function visitWindow ( generator: DomGenerator, block: Block, nod
const handlerName = block.getUniqueName(`onwindow${event}`); const handlerName = block.getUniqueName(`onwindow${event}`);
const props = events[event].join(',\n'); const props = events[event].join(',\n');
if ( event === 'scroll' ) { // TODO other bidirectional bindings... if (event === 'scroll') {
// TODO other bidirectional bindings...
block.addVariable(lock, 'false'); block.addVariable(lock, 'false');
} }
@ -130,19 +140,30 @@ export default function visitWindow ( generator: DomGenerator, block: Block, nod
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
function ${observerCallback} () { function ${observerCallback} () {
if ( ${lock} ) return; if ( ${lock} ) return;
var x = ${bindings.scrollX ? `${block.component}.get( '${bindings.scrollX}' )` : `window.scrollX`}; var x = ${bindings.scrollX
var y = ${bindings.scrollY ? `${block.component}.get( '${bindings.scrollY}' )` : `window.scrollY`}; ? `${block.component}.get( '${bindings.scrollX}' )`
: `window.scrollX`};
var y = ${bindings.scrollY
? `${block.component}.get( '${bindings.scrollY}' )`
: `window.scrollY`};
window.scrollTo( x, y ); window.scrollTo( x, y );
}; };
`); `);
if ( bindings.scrollX ) block.builders.create.addLine( `${block.component}.observe( '${bindings.scrollX}', ${observerCallback} );` ); if (bindings.scrollX)
if ( bindings.scrollY ) block.builders.create.addLine( `${block.component}.observe( '${bindings.scrollY}', ${observerCallback} );` ); block.builders.create.addLine(
`${block.component}.observe( '${bindings.scrollX}', ${observerCallback} );`
);
if (bindings.scrollY)
block.builders.create.addLine(
`${block.component}.observe( '${bindings.scrollY}', ${observerCallback} );`
);
} else if (bindings.scrollX || bindings.scrollY) { } else if (bindings.scrollX || bindings.scrollY) {
const isX = !!bindings.scrollX; const isX = !!bindings.scrollX;
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
${block.component}.observe( '${bindings.scrollX || bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) { ${block.component}.observe( '${bindings.scrollX ||
bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) {
if ( ${lock} ) return; if ( ${lock} ) return;
window.scrollTo( ${isX ? 'x, window.scrollY' : 'window.scrollX, y'} ); window.scrollTo( ${isX ? 'x, window.scrollY' : 'window.scrollX, y'} );
}); });

@ -6,21 +6,30 @@ import { Node } from '../../../interfaces';
import { State } from '../interfaces'; import { State } from '../interfaces';
function isElseIf(node: Node) { function isElseIf(node: Node) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
} }
function isElseBranch(branch) { function isElseBranch(branch) {
return branch.block && !branch.condition; return branch.block && !branch.condition;
} }
function getBranches ( generator: DomGenerator, block: Block, state: State, node: Node ) { function getBranches(
const branches = [{ generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const branches = [
{
condition: block.contextualise(node.expression).snippet, condition: block.contextualise(node.expression).snippet,
block: node._block.name, block: node._block.name,
hasUpdateMethod: node._block.hasUpdateMethod, hasUpdateMethod: node._block.hasUpdateMethod,
hasIntroMethod: node._block.hasIntroMethod, hasIntroMethod: node._block.hasIntroMethod,
hasOutroMethod: node._block.hasOutroMethod hasOutroMethod: node._block.hasOutroMethod
}]; }
];
visitChildren(generator, block, state, node); visitChildren(generator, block, state, node);
@ -45,15 +54,27 @@ function getBranches ( generator: DomGenerator, block: Block, state: State, node
return branches; return branches;
} }
function visitChildren ( generator: DomGenerator, block: Block, state: State, node: Node ) { function visitChildren(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
node.children.forEach((child: Node) => { node.children.forEach((child: Node) => {
visit(generator, node._block, node._state, child); visit(generator, node._block, node._state, child);
}); });
} }
export default function visitIfBlock ( generator: DomGenerator, block: Block, state: State, node: Node ) { export default function visitIfBlock(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const name = generator.getUniqueName(`if_block`); const name = generator.getUniqueName(`if_block`);
const anchor = node.needsAnchor ? block.getUniqueName( `${name}_anchor` ) : ( node.next && node.next._state.name ) || 'null'; const anchor = node.needsAnchor
? block.getUniqueName(`${name}_anchor`)
: (node.next && node.next._state.name) || 'null';
const params = block.params.join(', '); const params = block.params.join(', ');
const branches = getBranches(generator, block, state, node); const branches = getBranches(generator, block, state, node);
@ -68,7 +89,15 @@ export default function visitIfBlock ( generator: DomGenerator, block: Block, st
if (node.else) { if (node.else) {
if (hasOutros) { if (hasOutros) {
compoundWithOutros( generator, block, state, node, branches, dynamic, vars ); compoundWithOutros(
generator,
block,
state,
node,
branches,
dynamic,
vars
);
} else { } else {
compound(generator, block, state, node, branches, dynamic, vars); compound(generator, block, state, node, branches, dynamic, vars);
} }
@ -77,13 +106,26 @@ export default function visitIfBlock ( generator: DomGenerator, block: Block, st
} }
if (node.needsAnchor) { if (node.needsAnchor) {
block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true ); block.addElement(
anchor,
`${generator.helper('createComment')}()`,
state.parentNode,
true
);
} else if (node.next) { } else if (node.next) {
node.next.usedAsAnchor = true; node.next.usedAsAnchor = true;
} }
} }
function simple ( generator: DomGenerator, block: Block, state: State, node: Node, branch, dynamic, { name, anchor, params, if_name } ) { function simple(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
branch,
dynamic,
{ name, anchor, params, if_name }
) {
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
var ${name} = (${branch.condition}) && ${branch.block}( ${params}, ${block.component} ); var ${name} = (${branch.condition}) && ${branch.block}( ${params}, ${block.component} );
`); `);
@ -92,16 +134,20 @@ function simple ( generator: DomGenerator, block: Block, state: State, node: Nod
const mountOrIntro = branch.hasIntroMethod ? 'intro' : 'mount'; const mountOrIntro = branch.hasIntroMethod ? 'intro' : 'mount';
if (isTopLevel) { if (isTopLevel) {
block.builders.mount.addLine( `if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );` ); block.builders.mount.addLine(
`if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );`
);
} else { } else {
block.builders.create.addLine( `if ( ${name} ) ${name}.${mountOrIntro}( ${state.parentNode}, null );` ); block.builders.create.addLine(
`if ( ${name} ) ${name}.${mountOrIntro}( ${state.parentNode}, null );`
);
} }
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
const enter = dynamic ? const enter = dynamic
( branch.hasIntroMethod ? ? branch.hasIntroMethod
deindent` ? deindent`
if ( ${name} ) { if ( ${name} ) {
${name}.update( changed, ${params} ); ${name}.update( changed, ${params} );
} else { } else {
@ -109,38 +155,38 @@ function simple ( generator: DomGenerator, block: Block, state: State, node: Nod
} }
${name}.intro( ${parentNode}, ${anchor} ); ${name}.intro( ${parentNode}, ${anchor} );
` : `
deindent` : deindent`
if ( ${name} ) { if ( ${name} ) {
${name}.update( changed, ${params} ); ${name}.update( changed, ${params} );
} else { } else {
${name} = ${branch.block}( ${params}, ${block.component} ); ${name} = ${branch.block}( ${params}, ${block.component} );
${name}.mount( ${parentNode}, ${anchor} ); ${name}.mount( ${parentNode}, ${anchor} );
} }
` ) : `
( branch.hasIntroMethod ? : branch.hasIntroMethod
deindent` ? deindent`
if ( !${name} ) ${name} = ${branch.block}( ${params}, ${block.component} ); if ( !${name} ) ${name} = ${branch.block}( ${params}, ${block.component} );
${name}.intro( ${parentNode}, ${anchor} ); ${name}.intro( ${parentNode}, ${anchor} );
` : `
deindent` : deindent`
if ( !${name} ) { if ( !${name} ) {
${name} = ${branch.block}( ${params}, ${block.component} ); ${name} = ${branch.block}( ${params}, ${block.component} );
${name}.mount( ${parentNode}, ${anchor} ); ${name}.mount( ${parentNode}, ${anchor} );
} }
` ); `;
// no `update()` here — we don't want to update outroing nodes, // no `update()` here — we don't want to update outroing nodes,
// as that will typically result in glitching // as that will typically result in glitching
const exit = branch.hasOutroMethod ? const exit = branch.hasOutroMethod
deindent` ? deindent`
${name}.outro( function () { ${name}.outro( function () {
${name}.unmount(); ${name}.unmount();
${name}.destroy(); ${name}.destroy();
${name} = null; ${name} = null;
}); });
` : `
deindent` : deindent`
${name}.unmount(); ${name}.unmount();
${name}.destroy(); ${name}.destroy();
${name} = null; ${name} = null;
@ -154,25 +200,31 @@ function simple ( generator: DomGenerator, block: Block, state: State, node: Nod
} }
`); `);
block.builders.unmount.addLine( block.builders.unmount.addLine(`${if_name}${name}.unmount();`);
`${if_name}${name}.unmount();`
);
block.builders.destroy.addLine( block.builders.destroy.addLine(`${if_name}${name}.destroy();`);
`${if_name}${name}.destroy();`
);
} }
function compound ( generator: DomGenerator, block: Block, state: State, node: Node, branches, dynamic, { name, anchor, params, hasElse, if_name } ) { function compound(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
branches,
dynamic,
{ name, anchor, params, hasElse, if_name }
) {
const get_block = block.getUniqueName(`get_block`); const get_block = block.getUniqueName(`get_block`);
const current_block = block.getUniqueName(`current_block`); const current_block = block.getUniqueName(`current_block`);
const current_block_and = hasElse ? '' : `${current_block} && `; const current_block_and = hasElse ? '' : `${current_block} && `;
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
function ${get_block} ( ${params} ) { function ${get_block} ( ${params} ) {
${branches.map( ({ condition, block }) => { ${branches
.map(({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`; return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
} ).join( '\n' )} })
.join('\n')}
} }
var ${current_block} = ${get_block}( ${params} ); var ${current_block} = ${get_block}( ${params} );
@ -183,9 +235,13 @@ function compound ( generator: DomGenerator, block: Block, state: State, node: N
const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount'; const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount';
if (isTopLevel) { if (isTopLevel) {
block.builders.mount.addLine( `${if_name}${name}.${mountOrIntro}( ${block.target}, anchor );` ); block.builders.mount.addLine(
`${if_name}${name}.${mountOrIntro}( ${block.target}, anchor );`
);
} else { } else {
block.builders.create.addLine( `${if_name}${name}.${mountOrIntro}( ${state.parentNode}, null );` ); block.builders.create.addLine(
`${if_name}${name}.${mountOrIntro}( ${state.parentNode}, null );`
);
} }
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
@ -225,14 +281,24 @@ function compound ( generator: DomGenerator, block: Block, state: State, node: N
// if any of the siblings have outros, we need to keep references to the blocks // if any of the siblings have outros, we need to keep references to the blocks
// (TODO does this only apply to bidi transitions?) // (TODO does this only apply to bidi transitions?)
function compoundWithOutros ( generator: DomGenerator, block: Block, state: State, node: Node, branches, dynamic, { name, anchor, params, hasElse } ) { function compoundWithOutros(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
branches,
dynamic,
{ name, anchor, params, hasElse }
) {
const get_block = block.getUniqueName(`get_block`); const get_block = block.getUniqueName(`get_block`);
const current_block_index = block.getUniqueName(`current_block_index`); const current_block_index = block.getUniqueName(`current_block_index`);
const previous_block_index = block.getUniqueName(`previous_block_index`); const previous_block_index = block.getUniqueName(`previous_block_index`);
const if_block_creators = block.getUniqueName(`if_block_creators`); const if_block_creators = block.getUniqueName(`if_block_creators`);
const if_blocks = block.getUniqueName(`if_blocks`); const if_blocks = block.getUniqueName(`if_blocks`);
const if_current_block_index = hasElse ? '' : `if ( ~${current_block_index} ) `; const if_current_block_index = hasElse
? ''
: `if ( ~${current_block_index} ) `;
block.addVariable(current_block_index); block.addVariable(current_block_index);
block.addVariable(name); block.addVariable(name);
@ -245,9 +311,13 @@ function compoundWithOutros ( generator: DomGenerator, block: Block, state: Stat
var ${if_blocks} = []; var ${if_blocks} = [];
function ${get_block} ( ${params} ) { function ${get_block} ( ${params} ) {
${branches.map( ({ condition, block }, i ) => { ${branches
return `${condition ? `if ( ${condition} ) ` : ''}return ${block ? i : -1};`; .map(({ condition, block }, i) => {
} ).join( '\n' )} return `${condition ? `if ( ${condition} ) ` : ''}return ${block
? i
: -1};`;
})
.join('\n')}
} }
`); `);
@ -268,9 +338,13 @@ function compoundWithOutros ( generator: DomGenerator, block: Block, state: Stat
const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount'; const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount';
if (isTopLevel) { if (isTopLevel) {
block.builders.mount.addLine( `${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${block.target}, anchor );` ); block.builders.mount.addLine(
`${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${block.target}, anchor );`
);
} else { } else {
block.builders.create.addLine( `${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${state.parentNode}, null );` ); block.builders.create.addLine(
`${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${state.parentNode}, null );`
);
} }
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
@ -288,13 +362,13 @@ function compoundWithOutros ( generator: DomGenerator, block: Block, state: Stat
${name}.${mountOrIntro}( ${parentNode}, ${anchor} ); ${name}.${mountOrIntro}( ${parentNode}, ${anchor} );
`; `;
const changeBlock = hasElse ? const changeBlock = hasElse
deindent` ? deindent`
${destroyOldBlock} ${destroyOldBlock}
${createNewBlock} ${createNewBlock}
` : `
deindent` : deindent`
if ( ${name} ) { if ( ${name} ) {
${destroyOldBlock} ${destroyOldBlock}
} }

@ -4,14 +4,24 @@ import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import { State } from '../interfaces'; import { State } from '../interfaces';
export default function visitMustacheTag ( generator: DomGenerator, block: Block, state: State, node: Node ) { export default function visitMustacheTag(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const name = node._state.name; const name = node._state.name;
const value = block.getUniqueName(`${name}_value`); const value = block.getUniqueName(`${name}_value`);
const { snippet } = block.contextualise(node.expression); const { snippet } = block.contextualise(node.expression);
block.addVariable(value); block.addVariable(value);
block.addElement( name, `${generator.helper( 'createText' )}( ${value} = ${snippet} )`, state.parentNode, true ); block.addElement(
name,
`${generator.helper('createText')}( ${value} = ${snippet} )`,
state.parentNode,
true
);
block.builders.update.addBlock(deindent` block.builders.update.addBlock(deindent`
if ( ${value} !== ( ${value} = ${snippet} ) ) { if ( ${value} !== ( ${value} = ${snippet} ) ) {

@ -4,7 +4,12 @@ import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import { State } from '../interfaces'; import { State } from '../interfaces';
export default function visitRawMustacheTag ( generator: DomGenerator, block: Block, state: State, node: Node ) { export default function visitRawMustacheTag(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const name = node._state.basename; const name = node._state.basename;
const before = node._state.name; const before = node._state.name;
const value = block.getUniqueName(`${name}_value`); const value = block.getUniqueName(`${name}_value`);
@ -14,14 +19,26 @@ export default function visitRawMustacheTag ( generator: DomGenerator, block: Bl
// we would have used comments here, but the `insertAdjacentHTML` api only // we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s. // exists for `Element`s.
block.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, state.parentNode, true ); block.addElement(
block.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, state.parentNode, true ); before,
`${generator.helper('createElement')}( 'noscript' )`,
state.parentNode,
true
);
block.addElement(
after,
`${generator.helper('createElement')}( 'noscript' )`,
state.parentNode,
true
);
const isToplevel = !state.parentNode; const isToplevel = !state.parentNode;
block.builders.create.addLine(`var ${value} = ${snippet};`); block.builders.create.addLine(`var ${value} = ${snippet};`);
const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${value} );`; const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${value} );`;
const detachStatement = `${generator.helper( 'detachBetween' )}( ${before}, ${after} );`; const detachStatement = `${generator.helper(
'detachBetween'
)}( ${before}, ${after} );`;
if (isToplevel) { if (isToplevel) {
block.builders.mount.addLine(mountStatement); block.builders.mount.addLine(mountStatement);

@ -3,7 +3,17 @@ import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import { State } from '../interfaces'; import { State } from '../interfaces';
export default function visitText ( generator: DomGenerator, block: Block, state: State, node: Node ) { export default function visitText(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
if (!node._state.shouldCreate) return; if (!node._state.shouldCreate) return;
block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, node.usedAsAnchor ); block.addElement(
node._state.name,
`${generator.helper('createText')}( ${JSON.stringify(node.data)} )`,
state.parentNode,
node.usedAsAnchor
);
} }

@ -2,7 +2,11 @@ import { DomGenerator } from '../index';
import Block from '../Block'; import Block from '../Block';
import { State } from '../interfaces'; import { State } from '../interfaces';
export default function visitYieldTag ( generator: DomGenerator, block: Block, state: State ) { export default function visitYieldTag(
generator: DomGenerator,
block: Block,
state: State
) {
const parentNode = state.parentNode || block.target; const parentNode = state.parentNode || block.target;
(state.parentNode ? block.builders.create : block.builders.mount).addLine( (state.parentNode ? block.builders.create : block.builders.mount).addLine(

@ -1,7 +1,17 @@
import deindent from '../../../../../utils/deindent.js'; import deindent from '../../../../../utils/deindent.js';
export default function getSetter ({ block, name, snippet, context, attribute, dependencies, value }) { export default function getSetter({
const tail = attribute.value.type === 'MemberExpression' ? getTailSnippet( attribute.value ) : ''; block,
name,
snippet,
context,
attribute,
dependencies,
value
}) {
const tail = attribute.value.type === 'MemberExpression'
? getTailSnippet(attribute.value)
: '';
if (block.contexts.has(name)) { if (block.contexts.has(name)) {
const prop = dependencies[0]; const prop = dependencies[0];

@ -20,7 +20,8 @@ export default class Block {
} }
addBinding(binding: Node, name: string) { addBinding(binding: Node, name: string) {
const conditions = [ `!( '${binding.name}' in state )`].concat( // TODO handle contextual bindings... const conditions = [`!( '${binding.name}' in state )`].concat(
// TODO handle contextual bindings...
this.conditions.map(c => `(${c})`) this.conditions.map(c => `(${c})`)
); );
@ -42,6 +43,11 @@ export default class Block {
} }
contextualise(expression: Node, context?: string, isEventHandler?: boolean) { contextualise(expression: Node, context?: string, isEventHandler?: boolean) {
return this.generator.contextualise( this, expression, context, isEventHandler ); return this.generator.contextualise(
this,
expression,
context,
isEventHandler
);
} }
} }

@ -9,7 +9,12 @@ export class SsrGenerator extends Generator {
renderCode: string; renderCode: string;
elementDepth: number; elementDepth: number;
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) { constructor(
parsed: Parsed,
source: string,
name: string,
options: CompileOptions
) {
super(parsed, source, name, options); super(parsed, source, name, options);
this.bindings = []; this.bindings = [];
this.renderCode = ''; this.renderCode = '';
@ -21,7 +26,11 @@ export class SsrGenerator extends Generator {
} }
} }
export default function ssr ( parsed: Parsed, source: string, options: CompileOptions ) { export default function ssr(
parsed: Parsed,
source: string,
options: CompileOptions
) {
const format = options.format || 'cjs'; const format = options.format || 'cjs';
const name = options.name || 'SvelteComponent'; const name = options.name || 'SvelteComponent';
@ -49,17 +58,27 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp
${name}.filename = ${JSON.stringify(options.filename)}; ${name}.filename = ${JSON.stringify(options.filename)};
${name}.data = function () { ${name}.data = function () {
return ${templateProperties.data ? `${generator.alias( 'template' )}.data()` : `{}`}; return ${templateProperties.data
? `${generator.alias('template')}.data()`
: `{}`};
}; };
${name}.render = function ( state, options ) { ${name}.render = function ( state, options ) {
${templateProperties.data ? `state = Object.assign( ${generator.alias( 'template' )}.data(), state || {} );` : `state = state || {};`} ${templateProperties.data
? `state = Object.assign( ${generator.alias(
${computations.map( ({ key, deps }) => 'template'
`state.${key} = ${generator.alias( 'template' )}.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );` )}.data(), state || {} );`
: `state = state || {};`}
${computations.map(
({ key, deps }) =>
`state.${key} = ${generator.alias(
'template'
)}.computed.${key}( ${deps.map(dep => `state.${dep}`).join(', ')} );`
)} )}
${generator.bindings.length && deindent` ${generator.bindings.length &&
deindent`
var settled = false; var settled = false;
var tmp; var tmp;
@ -76,7 +95,8 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp
${name}.renderCss = function () { ${name}.renderCss = function () {
var components = []; var components = [];
${generator.css && deindent` ${generator.css &&
deindent`
components.push({ components.push({
filename: ${name}.filename, filename: ${name}.filename,
css: ${JSON.stringify(generator.css)}, css: ${JSON.stringify(generator.css)},
@ -84,7 +104,8 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp
}); });
`} `}
${templateProperties.components && deindent` ${templateProperties.components &&
deindent`
var seen = {}; var seen = {};
function addComponent ( component ) { function addComponent ( component ) {
@ -96,13 +117,13 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp
}); });
} }
${ ${templateProperties.components.value.properties.map(prop => {
templateProperties.components.value.properties.map( prop => {
const { name } = prop.key; const { name } = prop.key;
const expression = generator.importedComponents.get( name ) || `${generator.alias( 'template' )}.components.${name}`; const expression =
generator.importedComponents.get(name) ||
`${generator.alias('template')}.components.${name}`;
return `addComponent( ${expression} );`; return `addComponent( ${expression} );`;
}) })}
}
`} `}
return { return {

@ -3,7 +3,11 @@ import { SsrGenerator } from './index';
import Block from './Block'; import Block from './Block';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
export default function visit ( generator: SsrGenerator, block: Block, node: Node ) { export default function visit(
generator: SsrGenerator,
block: Block,
node: Node
) {
const visitor = visitors[node.type]; const visitor = visitors[node.type];
visitor(generator, block, node); visitor(generator, block, node);
} }

@ -4,7 +4,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block'; import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function visitComponent ( generator: SsrGenerator, block: Block, node: Node ) { export default function visitComponent(
generator: SsrGenerator,
block: Block,
node: Node
) {
function stringify(chunk: Node) { function stringify(chunk: Node) {
if (chunk.type === 'Text') return chunk.data; if (chunk.type === 'Text') return chunk.data;
if (chunk.type === 'MustacheTag') { if (chunk.type === 'MustacheTag') {
@ -46,14 +50,19 @@ export default function visitComponent ( generator: SsrGenerator, block: Block,
return `${attribute.name}: ${value}`; return `${attribute.name}: ${value}`;
}) })
.concat( bindings.map( binding => { .concat(
bindings.map(binding => {
const { name, keypath } = flattenReference(binding.value); const { name, keypath } = flattenReference(binding.value);
const value = block.contexts.has(name) ? keypath : `state.${keypath}`; const value = block.contexts.has(name) ? keypath : `state.${keypath}`;
return `${binding.name}: ${value}`; return `${binding.name}: ${value}`;
})) })
)
.join(', '); .join(', ');
const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`; const expression = node.name === ':Self'
? generator.name
: generator.importedComponents.get(node.name) ||
`${generator.alias('template')}.components.${node.name}`;
bindings.forEach(binding => { bindings.forEach(binding => {
block.addBinding(binding, expression); block.addBinding(binding, expression);

@ -3,10 +3,16 @@ import { SsrGenerator } from '../index';
import Block from '../Block'; import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function visitEachBlock ( generator: SsrGenerator, block: Block, node: Node ) { export default function visitEachBlock(
generator: SsrGenerator,
block: Block,
node: Node
) {
const { dependencies, snippet } = block.contextualise(node.expression); const { dependencies, snippet } = block.contextualise(node.expression);
const open = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \``; const open = `\${ ${snippet}.map( ${node.index
? `( ${node.context}, ${node.index} )`
: node.context} => \``;
generator.append(open); generator.append(open);
// TODO should this be the generator's job? It's duplicated between // TODO should this be the generator's job? It's duplicated between

@ -11,17 +11,23 @@ const meta = {
}; };
function stringifyAttributeValue(block: Block, chunks: Node[]) { function stringifyAttributeValue(block: Block, chunks: Node[]) {
return chunks.map( ( chunk: Node ) => { return chunks
.map((chunk: Node) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return chunk.data; return chunk.data;
} }
const { snippet } = block.contextualise(chunk.expression); const { snippet } = block.contextualise(chunk.expression);
return '${' + snippet + '}'; return '${' + snippet + '}';
}).join( '' ) })
.join('');
} }
export default function visitElement ( generator: SsrGenerator, block: Block, node: Node ) { export default function visitElement(
generator: SsrGenerator,
block: Block,
node: Node
) {
if (node.name in meta) { if (node.name in meta) {
return meta[node.name](generator, block, node); return meta[node.name](generator, block, node);
} }

@ -3,7 +3,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block'; import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function visitIfBlock ( generator: SsrGenerator, block: Block, node: Node ) { export default function visitIfBlock(
generator: SsrGenerator,
block: Block,
node: Node
) {
const { snippet } = block.contextualise(node.expression); const { snippet } = block.contextualise(node.expression);
generator.append('${ ' + snippet + ' ? `'); generator.append('${ ' + snippet + ' ? `');

@ -2,7 +2,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block'; import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function visitMustacheTag ( generator: SsrGenerator, block: Block, node: Node ) { export default function visitMustacheTag(
generator: SsrGenerator,
block: Block,
node: Node
) {
const { snippet } = block.contextualise(node.expression); const { snippet } = block.contextualise(node.expression);
generator.append('${__escape( ' + snippet + ' )}'); generator.append('${__escape( ' + snippet + ' )}');
} }

@ -2,7 +2,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block'; import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function visitRawMustacheTag ( generator: SsrGenerator, block: Block, node: Node ) { export default function visitRawMustacheTag(
generator: SsrGenerator,
block: Block,
node: Node
) {
const { snippet } = block.contextualise(node.expression); const { snippet } = block.contextualise(node.expression);
generator.append('${' + snippet + '}'); generator.append('${' + snippet + '}');
} }

@ -2,6 +2,10 @@ import { SsrGenerator } from '../index';
import Block from '../Block'; import Block from '../Block';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function visitText ( generator: SsrGenerator, block: Block, node: Node ) { export default function visitText(
generator: SsrGenerator,
block: Block,
node: Node
) {
generator.append(node.data.replace(/\${/g, '\\${')); generator.append(node.data.replace(/\${/g, '\\${'));
} }

@ -3,7 +3,11 @@ import { Parsed, Node } from '../../interfaces';
const commentsPattern = /\/\*[\s\S]*?\*\//g; const commentsPattern = /\/\*[\s\S]*?\*\//g;
export default function processCss ( parsed: Parsed, code: MagicString, cascade: boolean ) { export default function processCss(
parsed: Parsed,
code: MagicString,
cascade: boolean
) {
const css = parsed.css.content.styles; const css = parsed.css.content.styles;
const offset = parsed.css.content.start; const offset = parsed.css.content.start;
@ -57,9 +61,7 @@ export default function processCss ( parsed: Parsed, code: MagicString, cascade:
} }
code.overwrite(selector.start, selector.end, transformed); code.overwrite(selector.start, selector.end, transformed);
} } else {
else {
let shouldTransform = true; let shouldTransform = true;
let c = selector.start; let c = selector.start;
@ -114,7 +116,10 @@ export default function processCss ( parsed: Parsed, code: MagicString, cascade:
function walk(node: Node) { function walk(node: Node) {
if (node.type === 'Rule') { if (node.type === 'Rule') {
transform(node); transform(node);
} else if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) { } else if (
node.type === 'Atrule' &&
node.name.toLowerCase() === 'keyframes'
) {
// these have already been processed // these have already been processed
} else if (node.children) { } else if (node.children) {
node.children.forEach(walk); node.children.forEach(walk);
@ -127,7 +132,7 @@ export default function processCss ( parsed: Parsed, code: MagicString, cascade:
// remove comments. TODO would be nice if this was exposed in css-tree // remove comments. TODO would be nice if this was exposed in css-tree
let match; let match;
while ( match = commentsPattern.exec( css ) ) { while ((match = commentsPattern.exec(css))) {
const start = match.index + offset; const start = match.index + offset;
const end = start + match[0].length; const end = start + match[0].length;

@ -1,4 +1,4 @@
import { Declaration, Options } from './getIntro'; import { Declaration, Options } from './getIntro';
export type Globals = (id: string) => any; export type Globals = (id: string) => any;
@ -11,17 +11,19 @@ export default function getGlobals ( imports: Declaration[], options: Options )
if (!name) { if (!name) {
if (x.name.startsWith('__import')) { if (x.name.startsWith('__import')) {
const error = new Error( `Could not determine name for imported module '${x.source.value}' use options.globals` ); const error = new Error(
`Could not determine name for imported module '${x.source
.value}' use options.globals`
);
if (onerror) { if (onerror) {
onerror(error); onerror(error);
} else { } else {
throw error; throw error;
} }
} } else {
else {
const warning = { const warning = {
message: `No name was supplied for imported module '${x.source.value}'. Guessing '${x.name}', but you should use options.globals` message: `No name was supplied for imported module '${x.source
.value}'. Guessing '${x.name}', but you should use options.globals`
}; };
if (onwarn) { if (onwarn) {

@ -1,7 +1,7 @@
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import getGlobals, { Globals } from './getGlobals'; import getGlobals, { Globals } from './getGlobals';
export type ModuleFormat = "es" | "amd" | "cjs" | "iife" | "umd" | "eval"; export type ModuleFormat = 'es' | 'amd' | 'cjs' | 'iife' | 'umd' | 'eval';
export interface Options { export interface Options {
name: string; name: string;
@ -20,7 +20,11 @@ export interface Declaration {
}; };
} }
export default function getIntro ( format: ModuleFormat, options: Options, imports: Declaration[] ) { export default function getIntro(
format: ModuleFormat,
options: Options,
imports: Declaration[]
) {
if (format === 'es') return ''; if (format === 'es') return '';
if (format === 'amd') return getAmdIntro(options, imports); if (format === 'amd') return getAmdIntro(options, imports);
if (format === 'cjs') return getCjsIntro(options, imports); if (format === 'cjs') return getCjsIntro(options, imports);
@ -32,18 +36,25 @@ export default function getIntro ( format: ModuleFormat, options: Options, impor
} }
function getAmdIntro(options: Options, imports: Declaration[]) { function getAmdIntro(options: Options, imports: Declaration[]) {
const sourceString = imports.length ? const sourceString = imports.length
`[ ${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ' )} ], ` : ? `[ ${imports
''; .map(declaration => `'${removeExtension(declaration.source.value)}'`)
.join(', ')} ], `
: '';
const id = options.amd && options.amd.id; const id = options.amd && options.amd.id;
return `define(${id ? ` '${id}', ` : ''}${sourceString}function (${paramString( imports )}) { 'use strict';\n\n`; return `define(${id
? ` '${id}', `
: ''}${sourceString}function (${paramString(imports)}) { 'use strict';\n\n`;
} }
function getCjsIntro(options: Options, imports: Declaration[]) { function getCjsIntro(options: Options, imports: Declaration[]) {
const requireBlock = imports const requireBlock = imports
.map( declaration => `var ${declaration.name} = require( '${declaration.source.value}' );` ) .map(
declaration =>
`var ${declaration.name} = require( '${declaration.source.value}' );`
)
.join('\n\n'); .join('\n\n');
if (requireBlock) { if (requireBlock) {
@ -58,7 +69,9 @@ function getIifeIntro ( options: Options, imports: Declaration[] ) {
throw new Error(`Missing required 'name' option for IIFE export`); throw new Error(`Missing required 'name' option for IIFE export`);
} }
return `var ${options.name} = (function (${paramString( imports )}) { 'use strict';\n\n`; return `var ${options.name} = (function (${paramString(
imports
)}) { 'use strict';\n\n`;
} }
function getUmdIntro(options: Options, imports: Declaration[]) { function getUmdIntro(options: Options, imports: Declaration[]) {
@ -68,16 +81,24 @@ function getUmdIntro ( options: Options, imports: Declaration[] ) {
const amdId = options.amd && options.amd.id ? `'${options.amd.id}', ` : ''; const amdId = options.amd && options.amd.id ? `'${options.amd.id}', ` : '';
const amdDeps = imports.length ? `[${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ')}], ` : ''; const amdDeps = imports.length
const cjsDeps = imports.map( declaration => `require('${declaration.source.value}')` ).join( ', ' ); ? `[${imports
.map(declaration => `'${removeExtension(declaration.source.value)}'`)
.join(', ')}], `
: '';
const cjsDeps = imports
.map(declaration => `require('${declaration.source.value}')`)
.join(', ');
const globalDeps = getGlobals(imports, options); const globalDeps = getGlobals(imports, options);
return deindent` return (
deindent`
(function ( global, factory ) { (function ( global, factory ) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(${cjsDeps}) : typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(${cjsDeps}) :
typeof define === 'function' && define.amd ? define(${amdId}${amdDeps}factory) : typeof define === 'function' && define.amd ? define(${amdId}${amdDeps}factory) :
(global.${options.name} = factory(${globalDeps})); (global.${options.name} = factory(${globalDeps}));
}(this, (function (${paramString( imports )}) { 'use strict';` + '\n\n'; }(this, (function (${paramString(imports)}) { 'use strict';` + '\n\n'
);
} }
function getEvalIntro(options: Options, imports: Declaration[]) { function getEvalIntro(options: Options, imports: Declaration[]) {

@ -1,6 +1,11 @@
import getGlobals from './getGlobals'; import getGlobals from './getGlobals';
export default function getOutro ( format: string, name: string, options, imports ) { export default function getOutro(
format: string,
name: string,
options,
imports
) {
if (format === 'es') { if (format === 'es') {
return `export default ${name};`; return `export default ${name};`;
} }

@ -7,7 +7,8 @@ import { version } from '../package.json';
import { Parsed, CompileOptions, Warning } from './interfaces'; import { Parsed, CompileOptions, Warning } from './interfaces';
function normalizeOptions(options: CompileOptions): CompileOptions { function normalizeOptions(options: CompileOptions): CompileOptions {
return assign({ return assign(
{
generate: 'dom', generate: 'dom',
// a filename is necessary for sourcemap generation // a filename is necessary for sourcemap generation
@ -15,7 +16,9 @@ function normalizeOptions ( options: CompileOptions ) :CompileOptions {
onwarn: (warning: Warning) => { onwarn: (warning: Warning) => {
if (warning.loc) { if (warning.loc) {
console.warn( `(${warning.loc.line}:${warning.loc.column}) ${warning.message}` ); // eslint-disable-line no-console console.warn(
`(${warning.loc.line}:${warning.loc.column}) ${warning.message}`
); // eslint-disable-line no-console
} else { } else {
console.warn(warning.message); // eslint-disable-line no-console console.warn(warning.message); // eslint-disable-line no-console
} }
@ -24,7 +27,9 @@ function normalizeOptions ( options: CompileOptions ) :CompileOptions {
onerror: (error: Error) => { onerror: (error: Error) => {
throw error; throw error;
} }
}, options ); },
options
);
} }
export function compile(source: string, _options: CompileOptions) { export function compile(source: string, _options: CompileOptions) {
@ -41,9 +46,7 @@ export function compile ( source: string, _options: CompileOptions ) {
validate(parsed, source, options); validate(parsed, source, options);
const compiler = options.generate === 'ssr' const compiler = options.generate === 'ssr' ? generateSSR : generate;
? generateSSR
: generate;
return compiler(parsed, source, options); return compiler(parsed, source, options);
} }
@ -58,7 +61,7 @@ export function create ( source: string, _options: CompileOptions = {} ) {
} }
try { try {
return (new Function( 'return ' + compiled.code ))(); return new Function('return ' + compiled.code)();
} catch (err) { } catch (err) {
if (_options.onerror) { if (_options.onerror) {
_options.onerror(err); _options.onerror(err);

@ -26,10 +26,10 @@ export interface Parsed {
} }
export interface Warning { export interface Warning {
loc?: {line: number, column: number, pos: number}; loc?: { line: number; column: number; pos: number };
message: string message: string;
filename?: string filename?: string;
toString: () => string toString: () => string;
} }
export interface CompileOptions { export interface CompileOptions {
@ -42,6 +42,6 @@ export interface CompileOptions {
shared?: boolean | string; shared?: boolean | string;
cascade?: boolean; cascade?: boolean;
onerror?: (error: Error) => void onerror?: (error: Error) => void;
onwarn?: (warning: Warning) => void onwarn?: (warning: Warning) => void;
} }

@ -5,17 +5,22 @@ import { trimStart, trimEnd } from '../utils/trim';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import hash from './utils/hash'; import hash from './utils/hash';
import { Node, Parsed } from '../interfaces'; import { Node, Parsed } from '../interfaces';
import CompileError from '../utils/CompileError' import CompileError from '../utils/CompileError';
class ParseError extends CompileError { class ParseError extends CompileError {
constructor ( message: string, template: string, index: number, filename: string ) { constructor(
message: string,
template: string,
index: number,
filename: string
) {
super(message, template, index, filename); super(message, template, index, filename);
this.name = 'ParseError'; this.name = 'ParseError';
} }
} }
interface ParserOptions { interface ParserOptions {
filename?: string filename?: string;
} }
export class Parser { export class Parser {
@ -28,7 +33,7 @@ export class Parser {
html: Node; html: Node;
css: Node; css: Node;
js: Node; js: Node;
metaTags: {} metaTags: {};
constructor(template: string, options: ParserOptions) { constructor(template: string, options: ParserOptions) {
if (typeof template !== 'string') { if (typeof template !== 'string') {
@ -135,7 +140,10 @@ export class Parser {
} }
allowWhitespace() { allowWhitespace() {
while ( this.index < this.template.length && whitespace.test( this.template[ this.index ] ) ) { while (
this.index < this.template.length &&
whitespace.test(this.template[this.index])
) {
this.index++; this.index++;
} }
} }
@ -150,7 +158,8 @@ export class Parser {
} }
readUntil(pattern: RegExp) { readUntil(pattern: RegExp) {
if ( this.index >= this.template.length ) this.error( 'Unexpected end of input' ); if (this.index >= this.template.length)
this.error('Unexpected end of input');
const start = this.index; const start = this.index;
const match = pattern.exec(this.template.slice(start)); const match = pattern.exec(this.template.slice(start));
@ -178,7 +187,10 @@ export class Parser {
} }
} }
export default function parse ( template: string, options: ParserOptions = {} ) :Parsed { export default function parse(
template: string,
options: ParserOptions = {}
): Parsed {
const parser = new Parser(template, options); const parser = new Parser(template, options);
return { return {

@ -24,13 +24,9 @@ function readExpression ( parser: Parser, start: number, quoteMark ) {
} else { } else {
str += char; str += char;
} }
} } else if (/\s/.test(char)) {
else if ( /\s/.test( char ) ) {
break; break;
} } else {
else {
str += char; str += char;
} }
} }
@ -44,12 +40,12 @@ function readExpression ( parser: Parser, start: number, quoteMark ) {
return expression; return expression;
} }
export function readEventHandlerDirective ( parser: Parser, start: number, name: string ) { export function readEventHandlerDirective(
const quoteMark = ( parser: Parser,
parser.eat( `'` ) ? `'` : start: number,
parser.eat( `"` ) ? `"` : name: string
null ) {
); const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
const expressionStart = parser.index; const expressionStart = parser.index;
@ -68,15 +64,15 @@ export function readEventHandlerDirective ( parser: Parser, start: number, name:
}; };
} }
export function readBindingDirective ( parser: Parser, start: number, name: string ) { export function readBindingDirective(
parser: Parser,
start: number,
name: string
) {
let value; let value;
if (parser.eat('=')) { if (parser.eat('=')) {
const quoteMark = ( const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);
const a = parser.index; const a = parser.index;
@ -131,15 +127,16 @@ export function readBindingDirective ( parser: Parser, start: number, name: stri
}; };
} }
export function readTransitionDirective ( parser: Parser, start: number, name: string, type: string ) { export function readTransitionDirective(
parser: Parser,
start: number,
name: string,
type: string
) {
let expression = null; let expression = null;
if (parser.eat('=')) { if (parser.eat('=')) {
const quoteMark = ( const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);
const expressionStart = parser.index; const expressionStart = parser.index;

@ -1,11 +1,7 @@
import { parseExpressionAt } from 'acorn'; import { parseExpressionAt } from 'acorn';
import { Parser } from '../index'; import { Parser } from '../index';
const literals = new Map([ const literals = new Map([['true', true], ['false', false], ['null', null]]);
[ 'true', true ],
[ 'false', false ],
[ 'null', null ]
]);
export default function readExpression(parser: Parser) { export default function readExpression(parser: Parser) {
const start = parser.index; const start = parser.index;
@ -35,7 +31,9 @@ export default function readExpression ( parser: Parser ) {
parser.index = start; parser.index = start;
try { try {
const node = parseExpressionAt( parser.template, parser.index, { preserveParens: true } ); const node = parseExpressionAt(parser.template, parser.index, {
preserveParens: true
});
parser.index = node.end; parser.index = node.end;
return node; return node;

@ -2,7 +2,7 @@ import { parse } from 'acorn';
import spaces from '../../utils/spaces.js'; import spaces from '../../utils/spaces.js';
import { Parser } from '../index'; import { Parser } from '../index';
const scriptClosingTag = '<\/script>'; const scriptClosingTag = '</script>';
export default function readScript(parser: Parser, start: number, attributes) { export default function readScript(parser: Parser, start: number, attributes) {
const scriptStart = parser.index; const scriptStart = parser.index;
@ -10,7 +10,8 @@ export default function readScript ( parser: Parser, start: number, attributes )
if (scriptEnd === -1) parser.error(`<script> must have a closing tag`); if (scriptEnd === -1) parser.error(`<script> must have a closing tag`);
const source = spaces( scriptStart ) + parser.template.slice( scriptStart, scriptEnd ); const source =
spaces(scriptStart) + parser.template.slice(scriptStart, scriptEnd);
parser.index = scriptEnd + scriptClosingTag.length; parser.index = scriptEnd + scriptClosingTag.length;
let ast; let ast;

@ -80,11 +80,12 @@ export default function mustache ( parser: Parser ) {
block.end = parser.index; block.end = parser.index;
parser.stack.pop(); parser.stack.pop();
} } else if (parser.eat('elseif')) {
else if ( parser.eat( 'elseif' ) ) {
const block = parser.current(); const block = parser.current();
if ( block.type !== 'IfBlock' ) parser.error( 'Cannot have an {{elseif ...}} block outside an {{#if ...}} block' ); if (block.type !== 'IfBlock')
parser.error(
'Cannot have an {{elseif ...}} block outside an {{#if ...}} block'
);
parser.requireWhitespace(); parser.requireWhitespace();
@ -110,12 +111,12 @@ export default function mustache ( parser: Parser ) {
}; };
parser.stack.push(block.else.children[0]); parser.stack.push(block.else.children[0]);
} } else if (parser.eat('else')) {
else if ( parser.eat( 'else' ) ) {
const block = parser.current(); const block = parser.current();
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') { if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error( 'Cannot have an {{else}} block outside an {{#if ...}} or {{#each ...}} block' ); parser.error(
'Cannot have an {{else}} block outside an {{#if ...}} or {{#each ...}} block'
);
} }
parser.allowWhitespace(); parser.allowWhitespace();
@ -129,10 +130,8 @@ export default function mustache ( parser: Parser ) {
}; };
parser.stack.push(block.else); parser.stack.push(block.else);
} } else if (parser.eat('#')) {
// {{#if foo}} or {{#each foo}} // {{#if foo}} or {{#each foo}}
else if ( parser.eat( '#' ) ) {
let type; let type;
if (parser.eat('if')) { if (parser.eat('if')) {
@ -185,11 +184,8 @@ export default function mustache ( parser: Parser ) {
parser.current().children.push(block); parser.current().children.push(block);
parser.stack.push(block); parser.stack.push(block);
} } else if (parser.eat('yield')) {
// {{yield}} // {{yield}}
else if ( parser.eat( 'yield' ) ) {
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat('}}', true); parser.eat('}}', true);
@ -198,10 +194,8 @@ export default function mustache ( parser: Parser ) {
end: parser.index, end: parser.index,
type: 'YieldTag' type: 'YieldTag'
}); });
} } else if (parser.eat('{')) {
// {{{raw}}} mustache // {{{raw}}} mustache
else if ( parser.eat( '{' ) ) {
const expression = readExpression(parser); const expression = readExpression(parser);
parser.allowWhitespace(); parser.allowWhitespace();
@ -213,9 +207,7 @@ export default function mustache ( parser: Parser ) {
type: 'RawMustacheTag', type: 'RawMustacheTag',
expression expression
}); });
} } else {
else {
const expression = readExpression(parser); const expression = readExpression(parser);
parser.allowWhitespace(); parser.allowWhitespace();

@ -1,7 +1,11 @@
import readExpression from '../read/expression'; import readExpression from '../read/expression';
import readScript from '../read/script'; import readScript from '../read/script';
import readStyle from '../read/style'; import readStyle from '../read/style';
import { readEventHandlerDirective, readBindingDirective, readTransitionDirective } from '../read/directives'; import {
readEventHandlerDirective,
readBindingDirective,
readTransitionDirective
} from '../read/directives';
import { trimStart, trimEnd } from '../../utils/trim'; import { trimStart, trimEnd } from '../../utils/trim';
import { decodeCharacterReferences } from '../utils/html'; import { decodeCharacterReferences } from '../utils/html';
import isVoidElementName from '../../utils/isVoidElementName'; import isVoidElementName from '../../utils/isVoidElementName';
@ -17,14 +21,20 @@ const metaTags = {
}; };
const specials = new Map([ const specials = new Map([
[ 'script', { [
'script',
{
read: readScript, read: readScript,
property: 'js' property: 'js'
} ], }
[ 'style', { ],
[
'style',
{
read: readStyle, read: readStyle,
property: 'css' property: 'css'
} ] }
]
]); ]);
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
@ -32,7 +42,14 @@ const disallowedContents = new Map( [
['li', new Set(['li'])], ['li', new Set(['li'])],
['dt', new Set(['dt', 'dd'])], ['dt', new Set(['dt', 'dd'])],
['dd', new Set(['dt', 'dd'])], ['dd', new Set(['dt', 'dd'])],
[ 'p', new Set( 'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split( ' ' ) ) ], [
'p',
new Set(
'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split(
' '
)
)
],
['rt', new Set(['rt', 'rp'])], ['rt', new Set(['rt', 'rp'])],
['rp', new Set(['rt', 'rp'])], ['rp', new Set(['rt', 'rp'])],
['optgroup', new Set(['optgroup'])], ['optgroup', new Set(['optgroup'])],
@ -42,7 +59,7 @@ const disallowedContents = new Map( [
['tfoot', new Set(['tbody'])], ['tfoot', new Set(['tbody'])],
['tr', new Set(['tr', 'tbody'])], ['tr', new Set(['tr', 'tbody'])],
['td', new Set(['td', 'th', 'tr'])], ['td', new Set(['td', 'th', 'tr'])],
[ 'th', new Set( [ 'td', 'th', 'tr' ] ) ], ['th', new Set(['td', 'th', 'tr'])]
]); ]);
function stripWhitespace(element) { function stripWhitespace(element) {
@ -88,7 +105,10 @@ export default function tag ( parser: Parser ) {
if (name in metaTags) { if (name in metaTags) {
if (name in parser.metaTags) { if (name in parser.metaTags) {
if (isClosingTag && parser.current().children.length) { if (isClosingTag && parser.current().children.length) {
parser.error( `<${name}> cannot have children`, parser.current().children[0].start ); parser.error(
`<${name}> cannot have children`,
parser.current().children[0].start
);
} }
parser.error(`A component can only have one <${name}> tag`, start); parser.error(`A component can only have one <${name}> tag`, start);
@ -105,14 +125,21 @@ export default function tag ( parser: Parser ) {
if (isClosingTag) { if (isClosingTag) {
if (isVoidElementName(name)) { if (isVoidElementName(name)) {
parser.error( `<${name}> is a void element and cannot have children, or a closing tag`, start ); parser.error(
`<${name}> is a void element and cannot have children, or a closing tag`,
start
);
} }
if (!parser.eat('>')) parser.error(`Expected '>'`); if (!parser.eat('>')) parser.error(`Expected '>'`);
// close any elements that don't have their own closing tags, e.g. <div><p></div> // close any elements that don't have their own closing tags, e.g. <div><p></div>
while (parent.name !== name) { while (parent.name !== name) {
if ( parent.type !== 'Element' ) parser.error( `</${name}> attempted to close an element that was not open`, start ); if (parent.type !== 'Element')
parser.error(
`</${name}> attempted to close an element that was not open`,
start
);
parent.end = start; parent.end = start;
parser.stack.pop(); parser.stack.pop();
@ -142,7 +169,7 @@ export default function tag ( parser: Parser ) {
const uniqueNames = new Set(); const uniqueNames = new Set();
let attribute; let attribute;
while ( attribute = readAttribute( parser, uniqueNames ) ) { while ((attribute = readAttribute(parser, uniqueNames))) {
attributes.push(attribute); attributes.push(attribute);
parser.allowWhitespace(); parser.allowWhitespace();
} }
@ -155,7 +182,9 @@ export default function tag ( parser: Parser ) {
if (parser[special.property]) { if (parser[special.property]) {
parser.index = start; parser.index = start;
parser.error( `You can only have one top-level <${name}> tag per component` ); parser.error(
`You can only have one top-level <${name}> tag per component`
);
} }
parser.eat('>', true); parser.eat('>', true);
@ -182,7 +211,11 @@ export default function tag ( parser: Parser ) {
element.end = parser.index; element.end = parser.index;
} else if (name === 'textarea') { } else if (name === 'textarea') {
// special case // special case
element.children = readSequence( parser, () => parser.template.slice( parser.index, parser.index + 11 ) === '</textarea>' ); element.children = readSequence(
parser,
() =>
parser.template.slice(parser.index, parser.index + 11) === '</textarea>'
);
parser.read(/<\/textarea>/); parser.read(/<\/textarea>/);
element.end = parser.index; element.end = parser.index;
} else { } else {
@ -211,7 +244,10 @@ function readTagName ( parser: Parser ) {
} }
if (!legal) { if (!legal) {
parser.error( `<${SELF}> components can only exist inside if-blocks or each-blocks`, start ); parser.error(
`<${SELF}> components can only exist inside if-blocks or each-blocks`,
start
);
} }
return SELF; return SELF;
@ -261,7 +297,12 @@ function readAttribute ( parser: Parser, uniqueNames ) {
const match = /^(in|out|transition):/.exec(name); const match = /^(in|out|transition):/.exec(name);
if (match) { if (match) {
return readTransitionDirective( parser, start, name.slice( match[0].length ), match[1] ); return readTransitionDirective(
parser,
start,
name.slice(match[0].length),
match[1]
);
} }
let value; let value;
@ -284,19 +325,15 @@ function readAttribute ( parser: Parser, uniqueNames ) {
} }
function readAttributeValue(parser: Parser) { function readAttributeValue(parser: Parser) {
const quoteMark = ( const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);
const regex = ( const regex = quoteMark === `'`
quoteMark === `'` ? /'/ : ? /'/
quoteMark === `"` ? /"/ : : quoteMark === `"` ? /"/ : /[\s"'=<>\/`]/;
/[\s"'=<>\/`]/
);
const value = readSequence( parser, () => regex.test( parser.template[ parser.index ] ) ); const value = readSequence(parser, () =>
regex.test(parser.template[parser.index])
);
if (quoteMark) parser.index += 1; if (quoteMark) parser.index += 1;
return value; return value;
@ -305,7 +342,8 @@ function readAttributeValue ( parser: Parser ) {
function getShorthandValue(start: number, name: string) { function getShorthandValue(start: number, name: string) {
const end = start + name.length; const end = start + name.length;
return [{ return [
{
type: 'AttributeShorthand', type: 'AttributeShorthand',
start, start,
end, end,
@ -315,7 +353,8 @@ function getShorthandValue ( start: number, name: string ) {
end, end,
name name
} }
}]; }
];
} }
function readSequence(parser: Parser, done: () => boolean) { function readSequence(parser: Parser, done: () => boolean) {
@ -337,13 +376,12 @@ function readSequence ( parser: Parser, done: () => boolean ) {
if (currentChunk.data) chunks.push(currentChunk); if (currentChunk.data) chunks.push(currentChunk);
chunks.forEach(chunk => { chunks.forEach(chunk => {
if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data ); if (chunk.type === 'Text')
chunk.data = decodeCharacterReferences(chunk.data);
}); });
return chunks; return chunks;
} } else if (parser.eat('{{')) {
else if ( parser.eat( '{{' ) ) {
if (currentChunk.data) { if (currentChunk.data) {
currentChunk.end = index; currentChunk.end = index;
chunks.push(currentChunk); chunks.push(currentChunk);
@ -368,9 +406,7 @@ function readSequence ( parser: Parser, done: () => boolean ) {
type: 'Text', type: 'Text',
data: '' data: ''
}; };
} } else {
else {
currentChunk.data += parser.template[parser.index++]; currentChunk.data += parser.template[parser.index++];
} }
} }

@ -6,7 +6,11 @@ export default function text ( parser: Parser ) {
let data = ''; let data = '';
while ( parser.index < parser.template.length && !parser.match( '<' ) && !parser.match( '{{' ) ) { while (
parser.index < parser.template.length &&
!parser.match('<') &&
!parser.match('{{')
) {
data += parser.template[parser.index++]; data += parser.template[parser.index++];
} }

@ -1,7 +1,43 @@
import htmlEntities from './entities'; import htmlEntities from './entities';
const windows1252 = [ 8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141, 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 157, 382, 376 ]; const windows1252 = [
const entityPattern = new RegExp( `&(#?(?:x[\\w\\d]+|\\d+|${Object.keys( htmlEntities ).join( '|' )}));?`, 'g' ); 8364,
129,
8218,
402,
8222,
8230,
8224,
8225,
710,
8240,
352,
8249,
338,
141,
381,
143,
144,
8216,
8217,
8220,
8221,
8226,
8211,
8212,
732,
8482,
353,
8250,
339,
157,
382,
376
];
const entityPattern = new RegExp(
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(htmlEntities).join('|')}));?`,
'g'
);
export function decodeCharacterReferences(html: string) { export function decodeCharacterReferences(html: string) {
return html.replace(entityPattern, (match, entity) => { return html.replace(entityPattern, (match, entity) => {

@ -19,17 +19,20 @@ fs.readdirSync( __dirname ).forEach( file => {
const declaration = node.declaration; const declaration = node.declaration;
if (!declaration) return; if (!declaration) return;
const name = declaration.type === 'VariableDeclaration' ? const name = declaration.type === 'VariableDeclaration'
declaration.declarations[0].id.name : ? declaration.declarations[0].id.name
declaration.id.name; : declaration.id.name;
const value = declaration.type === 'VariableDeclaration' ? const value = declaration.type === 'VariableDeclaration'
declaration.declarations[0].init : ? declaration.declarations[0].init
declaration; : declaration;
declarations[name] = source.slice(value.start, value.end); declarations[name] = source.slice(value.start, value.end);
}); });
}); });
fs.writeFileSync( 'src/generators/dom/shared.ts', `// this file is auto-generated, do not edit it fs.writeFileSync(
export default ${JSON.stringify( declarations, null, '\t' )};` ); 'src/generators/dom/shared.ts',
`// this file is auto-generated, do not edit it
export default ${JSON.stringify(declarations, null, '\t')};`
);

@ -4,7 +4,7 @@ export * from './transitions.js';
export * from './utils.js'; export * from './utils.js';
export function differs(a, b) { export function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
export function dispatchObservers(component, group, newState, oldState) { export function dispatchObservers(component, group, newState, oldState) {
@ -35,7 +35,8 @@ export function get ( key ) {
} }
export function fire(eventName, data) { export function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -44,7 +45,9 @@ export function fire ( eventName, data ) {
} }
export function observe(key, callback, options) { export function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);
@ -65,8 +68,10 @@ export function observe ( key, callback, options ) {
export function observeDev(key, callback, options) { export function observeDev(key, callback, options) {
var c = (key = '' + key).search(/[^\w]/); var c = (key = '' + key).search(/[^\w]/);
if (c > -1) { if (c > -1) {
var message = "The first argument to component.observe(...) must be the name of a top-level property"; var message =
if ( c > 0 ) message += ", i.e. '" + key.slice( 0, c ) + "' rather than '" + key + "'"; 'The first argument to component.observe(...) must be the name of a top-level property';
if (c > 0)
message += ", i.e. '" + key.slice(0, c) + "' rather than '" + key + "'";
throw new Error(message); throw new Error(message);
} }
@ -90,7 +95,9 @@ export function on ( eventName, handler ) {
export function onDev(eventName, handler) { export function onDev(eventName, handler) {
if (eventName === 'teardown') { if (eventName === 'teardown') {
console.warn( "Use component.on('destroy', ...) instead of component.on('teardown', ...) which has been deprecated and will be unsupported in Svelte 2" ); console.warn(
"Use component.on('destroy', ...) instead of component.on('teardown', ...) which has been deprecated and will be unsupported in Svelte 2"
);
return this.on('destroy', handler); return this.on('destroy', handler);
} }

@ -4,13 +4,22 @@ export function linear ( t ) {
return t; return t;
} }
export function generateKeyframes ( a, b, delta, duration, ease, fn, node, style ) { export function generateKeyframes(
a,
b,
delta,
duration,
ease,
fn,
node,
style
) {
var id = '__svelte' + ~~(Math.random() * 1e9); // TODO make this more robust var id = '__svelte' + ~~(Math.random() * 1e9); // TODO make this more robust
var keyframes = '@keyframes ' + id + '{\n'; var keyframes = '@keyframes ' + id + '{\n';
for (var p = 0; p <= 1; p += 16.666 / duration) { for (var p = 0; p <= 1; p += 16.666 / duration) {
var t = a + delta * ease(p); var t = a + delta * ease(p);
keyframes += ( p * 100 ) + '%{' + fn( t ) + '}\n'; keyframes += p * 100 + '%{' + fn(t) + '}\n';
} }
keyframes += '100% {' + fn(b) + '}\n}'; keyframes += '100% {' + fn(b) + '}\n}';
@ -18,7 +27,8 @@ export function generateKeyframes ( a, b, delta, duration, ease, fn, node, style
document.head.appendChild(style); document.head.appendChild(style);
node.style.animation = ( node.style.animation || '' ).split( ',' ) node.style.animation = (node.style.animation || '')
.split(',')
.filter(function(anim) { .filter(function(anim) {
// when introing, discard old animations if there are any // when introing, discard old animations if there are any
return anim && (delta < 0 || !/__svelte/.test(anim)); return anim && (delta < 0 || !/__svelte/.test(anim));
@ -79,7 +89,16 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
if (obj.css) { if (obj.css) {
if (obj.delay) node.style.cssText = cssText; if (obj.delay) node.style.cssText = cssText;
generateKeyframes( program.a, program.b, program.delta, program.duration, ease, obj.css, node, style ); generateKeyframes(
program.a,
program.b,
program.delta,
program.duration,
ease,
obj.css,
node,
style
);
} }
this.program = program; this.program = program;

@ -1,7 +1,10 @@
export function noop() {} export function noop() {}
export function assign(target) { export function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];

@ -4,15 +4,17 @@ enum ChunkType {
} }
export default class CodeBuilder { export default class CodeBuilder {
result: string result: string;
first: ChunkType first: ChunkType;
last: ChunkType last: ChunkType;
lastCondition: string lastCondition: string;
constructor(str = '') { constructor(str = '') {
this.result = str; this.result = str;
const initial = str ? ( /\n/.test( str ) ? ChunkType.Block : ChunkType.Line ) : null; const initial = str
? /\n/.test(str) ? ChunkType.Block : ChunkType.Line
: null;
this.first = initial; this.first = initial;
this.last = initial; this.last = initial;

@ -2,12 +2,17 @@ import { locate } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
export default class CompileError extends Error { export default class CompileError extends Error {
frame: string frame: string;
loc: { line: number, column: number } loc: { line: number; column: number };
pos: number pos: number;
filename: string filename: string;
constructor ( message: string, template: string, index: number, filename: string ) { constructor(
message: string,
template: string,
index: number,
filename: string
) {
super(message); super(message);
const { line, column } = locate(template, index); const { line, column } = locate(template, index);
@ -20,6 +25,7 @@ export default class CompileError extends Error {
} }
toString() { toString() {
return `${this.message} (${this.loc.line}:${this.loc.column})\n${this.frame}`; return `${this.message} (${this.loc.line}:${this.loc.column})\n${this
.frame}`;
} }
} }

@ -70,7 +70,9 @@ describe( 'CodeBuilder', () => {
builder.addLine('// line 3'); builder.addLine('// line 3');
builder.addLine('// line 4'); builder.addLine('// line 4');
assert.equal( builder.toString(), deindent` assert.equal(
builder.toString(),
deindent`
// line 1 // line 1
// line 2 // line 2
@ -80,7 +82,8 @@ describe( 'CodeBuilder', () => {
// line 3 // line 3
// line 4 // line 4
` ); `
);
}); });
it('nests codebuilders with correct indentation', () => { it('nests codebuilders with correct indentation', () => {
@ -104,7 +107,9 @@ describe( 'CodeBuilder', () => {
builder.addLine('// line 3'); builder.addLine('// line 3');
builder.addLine('// line 4'); builder.addLine('// line 4');
assert.equal( builder.toString(), deindent` assert.equal(
builder.toString(),
deindent`
// line 1 // line 1
// line 2 // line 2
@ -116,7 +121,8 @@ describe( 'CodeBuilder', () => {
// line 3 // line 3
// line 4 // line 4
` ); `
);
}); });
it('adds a line at start', () => { it('adds a line at start', () => {
@ -125,10 +131,13 @@ describe( 'CodeBuilder', () => {
builder.addLine('// second'); builder.addLine('// second');
builder.addLineAtStart('// first'); builder.addLineAtStart('// first');
assert.equal( builder.toString(), deindent` assert.equal(
builder.toString(),
deindent`
// first // first
// second // second
` ); `
);
}); });
it('adds a line at start before a block', () => { it('adds a line at start before a block', () => {
@ -137,11 +146,14 @@ describe( 'CodeBuilder', () => {
builder.addBlock('// second'); builder.addBlock('// second');
builder.addLineAtStart('// first'); builder.addLineAtStart('// first');
assert.equal( builder.toString(), deindent` assert.equal(
builder.toString(),
deindent`
// first // first
// second // second
` ); `
);
}); });
it('adds a block at start', () => { it('adds a block at start', () => {
@ -150,11 +162,14 @@ describe( 'CodeBuilder', () => {
builder.addLine('// second'); builder.addLine('// second');
builder.addBlockAtStart('// first'); builder.addBlockAtStart('// first');
assert.equal( builder.toString(), deindent` assert.equal(
builder.toString(),
deindent`
// first // first
// second // second
` ); `
);
}); });
it('adds a block at start before a block', () => { it('adds a block at start before a block', () => {
@ -163,10 +178,13 @@ describe( 'CodeBuilder', () => {
builder.addBlock('// second'); builder.addBlock('// second');
builder.addBlockAtStart('// first'); builder.addBlockAtStart('// first');
assert.equal( builder.toString(), deindent` assert.equal(
builder.toString(),
deindent`
// first // first
// second // second
` ); `
);
}); });
}); });

@ -19,17 +19,11 @@ export default function annotateWithScopes ( expression: Node ) {
scope.declarations.add(name); scope.declarations.add(name);
}); });
}); });
} } else if (/For(?:In|Of)Statement/.test(node.type)) {
else if ( /For(?:In|Of)Statement/.test( node.type ) ) {
node._scope = scope = new Scope(scope, true); node._scope = scope = new Scope(scope, true);
} } else if (node.type === 'BlockStatement') {
else if ( node.type === 'BlockStatement' ) {
node._scope = scope = new Scope(scope, true); node._scope = scope = new Scope(scope, true);
} } else if (/(Function|Class|Variable)Declaration/.test(node.type)) {
else if ( /(Function|Class|Variable)Declaration/.test( node.type ) ) {
scope.addDeclaration(node); scope.addDeclaration(node);
} }
}, },
@ -45,9 +39,9 @@ export default function annotateWithScopes ( expression: Node ) {
} }
class Scope { class Scope {
parent: Scope parent: Scope;
block: boolean block: boolean;
declarations: Set<string> declarations: Set<string>;
constructor(parent: Scope, block: boolean) { constructor(parent: Scope, block: boolean) {
this.parent = parent; this.parent = parent;
@ -70,7 +64,9 @@ class Scope {
} }
has(name: string): boolean { has(name: string): boolean {
return this.declarations.has( name ) || this.parent && this.parent.has( name ); return (
this.declarations.has(name) || (this.parent && this.parent.has(name))
);
} }
} }

@ -17,11 +17,12 @@ export default function deindent ( strings, ...values ) {
} }
if (expression || expression === '') { if (expression || expression === '') {
const value = String( expression ).replace( /\n/g, `\n${trailingIndentation}` ); const value = String(expression).replace(
/\n/g,
`\n${trailingIndentation}`
);
result += value + string; result += value + string;
} } else {
else {
let c = result.length; let c = result.length;
while (/\s/.test(result[c - 1])) c -= 1; while (/\s/.test(result[c - 1])) c -= 1;
result = result.slice(0, c) + string; result = result.slice(0, c) + string;

@ -12,7 +12,9 @@ export default function flatten ( node: Node ) {
} }
const propStart = node.end; const propStart = node.end;
const name = node.type === 'Identifier' ? node.name : node.type === 'ThisExpression' ? 'this' : null; const name = node.type === 'Identifier'
? node.name
: node.type === 'ThisExpression' ? 'this' : null;
if (!name) return null; if (!name) return null;

@ -4,7 +4,11 @@ function tabsToSpaces ( str: string ) {
return str.replace(/^\t+/, match => match.split('\t').join(' ')); return str.replace(/^\t+/, match => match.split('\t').join(' '));
} }
export default function getCodeFrame ( source: string, line: number, column: number ) { export default function getCodeFrame(
source: string,
line: number,
column: number
) {
const lines = source.split('\n'); const lines = source.split('\n');
const frameStart = Math.max(0, line - 2); const frameStart = Math.max(0, line - 2);
@ -21,7 +25,8 @@ export default function getCodeFrame ( source: string, line: number, column: num
while (lineNum.length < digits) lineNum = ` ${lineNum}`; while (lineNum.length < digits) lineNum = ` ${lineNum}`;
if (isErrorLine) { if (isErrorLine) {
const indicator = spaces( digits + 2 + tabsToSpaces( str.slice( 0, column ) ).length ) + '^'; const indicator =
spaces(digits + 2 + tabsToSpaces(str.slice(0, column)).length) + '^';
return `${lineNum}: ${tabsToSpaces(str)}\n${indicator}`; return `${lineNum}: ${tabsToSpaces(str)}\n${indicator}`;
} }

@ -1 +1,26 @@
export default new Set( [ 'Array', 'Boolean', 'console', 'Date', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'Infinity', 'Intl', 'isFinite', 'isNaN', 'JSON', 'Map', 'Math', 'NaN', 'Number', 'Object', 'parseFloat', 'parseInt', 'RegExp', 'Set', 'String', 'undefined' ] ); export default new Set([
'Array',
'Boolean',
'console',
'Date',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'Infinity',
'Intl',
'isFinite',
'isNaN',
'JSON',
'Map',
'Math',
'NaN',
'Number',
'Object',
'parseFloat',
'parseInt',
'RegExp',
'Set',
'String',
'undefined'
]);

@ -12,12 +12,16 @@ export default function isReference ( node: Node, parent: Node ): boolean {
if (!parent) return true; if (!parent) return true;
// TODO is this right? // TODO is this right?
if ( parent.type === 'MemberExpression' || parent.type === 'MethodDefinition' ) { if (
parent.type === 'MemberExpression' ||
parent.type === 'MethodDefinition'
) {
return parent.computed || node === parent.object; return parent.computed || node === parent.object;
} }
// disregard the `bar` in `{ bar: foo }`, but keep it in `{ [bar]: foo }` // disregard the `bar` in `{ bar: foo }`, but keep it in `{ [bar]: foo }`
if ( parent.type === 'Property' ) return parent.computed || node === parent.value; if (parent.type === 'Property')
return parent.computed || node === parent.value;
// disregard the `bar` in `class Foo { bar () {...} }` // disregard the `bar` in `class Foo { bar () {...} }`
if (parent.type === 'MethodDefinition') return false; if (parent.type === 'MethodDefinition') return false;

@ -6,8 +6,18 @@ export const xml = 'http://www.w3.org/XML/1998/namespace';
export const xmlns = 'http://www.w3.org/2000/xmlns'; export const xmlns = 'http://www.w3.org/2000/xmlns';
export const validNamespaces = [ export const validNamespaces = [
'html', 'mathml', 'svg', 'xlink', 'xml', 'xmlns', 'html',
html, mathml, svg, xlink, xml, xmlns 'mathml',
'svg',
'xlink',
'xml',
'xmlns',
html,
mathml,
svg,
xlink,
xml,
xmlns
]; ];
export default { html, mathml, svg, xlink, xml, xmlns }; export default { html, mathml, svg, xlink, xml, xmlns };

@ -1,4 +1,53 @@
const reservedNames = new Set( [ 'arguments', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'enum', 'eval', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield' ] ); const reservedNames = new Set([
'arguments',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'eval',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'implements',
'import',
'in',
'instanceof',
'interface',
'let',
'new',
'null',
'package',
'private',
'protected',
'public',
'return',
'static',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield'
]);
// prevent e.g. `{{#each states as state}}` breaking // prevent e.g. `{{#each states as state}}` breaking
reservedNames.add('state'); reservedNames.add('state');

@ -6,17 +6,22 @@ import { Node } from '../../interfaces';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|title|tref|tspan|unknown|use|view|vkern)$/; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|title|tref|tspan|unknown|use|view|vkern)$/;
const meta = new Map([ const meta = new Map([[':Window', validateWindow]]);
[ ':Window', validateWindow ]
]);
export default function validateHtml(validator: Validator, html: Node) { export default function validateHtml(validator: Validator, html: Node) {
let elementDepth = 0; let elementDepth = 0;
function visit(node: Node) { function visit(node: Node) {
if (node.type === 'Element') { if (node.type === 'Element') {
if ( elementDepth === 0 && validator.namespace !== namespaces.svg && svg.test( node.name ) ) { if (
validator.warn( `<${node.name}> is an SVG element did you forget to add { namespace: 'svg' } ?`, node.start ); elementDepth === 0 &&
validator.namespace !== namespaces.svg &&
svg.test(node.name)
) {
validator.warn(
`<${node.name}> is an SVG element did you forget to add { namespace: 'svg' } ?`,
node.start
);
} }
if (meta.has(node.name)) { if (meta.has(node.name)) {
@ -35,7 +40,10 @@ export default function validateHtml ( validator: Validator, html: Node ) {
c += 2; c += 2;
while (/\s/.test(validator.source[c])) c += 1; while (/\s/.test(validator.source[c])) c += 1;
validator.warn( `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`, c ); validator.warn(
`Context clashes with a helper. Rename one or the other to eliminate any ambiguity`,
c
);
} }
} }

@ -3,7 +3,8 @@ import { Validator } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
export default function validateElement(validator: Validator, node: Node) { export default function validateElement(validator: Validator, node: Node) {
const isComponent = node.name === ':Self' || validator.components.has( node.name ); const isComponent =
node.name === ':Self' || validator.components.has(node.name);
let hasIntro: boolean; let hasIntro: boolean;
let hasOutro: boolean; let hasOutro: boolean;
@ -14,59 +15,94 @@ export default function validateElement ( validator: Validator, node: Node ) {
const { name } = attribute; const { name } = attribute;
if (name === 'value') { if (name === 'value') {
if ( node.name !== 'input' && node.name !== 'textarea' && node.name !== 'select' ) { if (
validator.error( `'value' is not a valid binding on <${node.name}> elements`, attribute.start ); node.name !== 'input' &&
} node.name !== 'textarea' &&
} node.name !== 'select'
) {
else if ( name === 'checked' ) { validator.error(
`'value' is not a valid binding on <${node.name}> elements`,
attribute.start
);
}
} else if (name === 'checked') {
if (node.name !== 'input') { if (node.name !== 'input') {
validator.error( `'checked' is not a valid binding on <${node.name}> elements`, attribute.start ); validator.error(
`'checked' is not a valid binding on <${node.name}> elements`,
attribute.start
);
} }
if (getType(validator, node) !== 'checkbox') { if (getType(validator, node) !== 'checkbox') {
validator.error( `'checked' binding can only be used with <input type="checkbox">`, attribute.start ); validator.error(
`'checked' binding can only be used with <input type="checkbox">`,
attribute.start
);
} }
} } else if (name === 'group') {
else if ( name === 'group' ) {
if (node.name !== 'input') { if (node.name !== 'input') {
validator.error( `'group' is not a valid binding on <${node.name}> elements`, attribute.start ); validator.error(
`'group' is not a valid binding on <${node.name}> elements`,
attribute.start
);
} }
const type = getType(validator, node); const type = getType(validator, node);
if (type !== 'checkbox' && type !== 'radio') { if (type !== 'checkbox' && type !== 'radio') {
validator.error( `'checked' binding can only be used with <input type="checkbox"> or <input type="radio">`, attribute.start ); validator.error(
} `'checked' binding can only be used with <input type="checkbox"> or <input type="radio">`,
} attribute.start
);
else if ( name === 'currentTime' || name === 'duration' || name === 'paused' ) { }
} else if (
name === 'currentTime' ||
name === 'duration' ||
name === 'paused'
) {
if (node.name !== 'audio' && node.name !== 'video') { if (node.name !== 'audio' && node.name !== 'video') {
validator.error( `'${name}' binding can only be used with <audio> or <video>`, attribute.start ); validator.error(
} `'${name}' binding can only be used with <audio> or <video>`,
} attribute.start
);
else { }
validator.error( `'${attribute.name}' is not a valid binding`, attribute.start ); } else {
} validator.error(
} `'${attribute.name}' is not a valid binding`,
attribute.start
else if ( attribute.type === 'EventHandler' ) { );
}
} else if (attribute.type === 'EventHandler') {
validateEventHandler(validator, attribute); validateEventHandler(validator, attribute);
} } else if (attribute.type === 'Transition') {
else if ( attribute.type === 'Transition' ) {
const bidi = attribute.intro && attribute.outro; const bidi = attribute.intro && attribute.outro;
if (hasTransition) { if (hasTransition) {
if ( bidi ) validator.error( `An element can only have one 'transition' directive`, attribute.start ); if (bidi)
validator.error( `An element cannot have both a 'transition' directive and an '${attribute.intro ? 'in' : 'out'}' directive`, attribute.start ); validator.error(
`An element can only have one 'transition' directive`,
attribute.start
);
validator.error(
`An element cannot have both a 'transition' directive and an '${attribute.intro
? 'in'
: 'out'}' directive`,
attribute.start
);
} }
if ((hasIntro && attribute.intro) || (hasOutro && attribute.outro)) { if ((hasIntro && attribute.intro) || (hasOutro && attribute.outro)) {
if ( bidi ) validator.error( `An element cannot have both an '${hasIntro ? 'in' : 'out'}' directive and a 'transition' directive`, attribute.start ); if (bidi)
validator.error( `An element can only have one '${hasIntro ? 'in' : 'out'}' directive`, attribute.start ); validator.error(
`An element cannot have both an '${hasIntro
? 'in'
: 'out'}' directive and a 'transition' directive`,
attribute.start
);
validator.error(
`An element can only have one '${hasIntro ? 'in' : 'out'}' directive`,
attribute.start
);
} }
if (attribute.intro) hasIntro = true; if (attribute.intro) hasIntro = true;
@ -74,14 +110,18 @@ export default function validateElement ( validator: Validator, node: Node ) {
if (bidi) hasTransition = true; if (bidi) hasTransition = true;
if (!validator.transitions.has(attribute.name)) { if (!validator.transitions.has(attribute.name)) {
validator.error( `Missing transition '${attribute.name}'`, attribute.start ); validator.error(
`Missing transition '${attribute.name}'`,
attribute.start
);
} }
} } else if (attribute.type === 'Attribute') {
else if ( attribute.type === 'Attribute' ) {
if (attribute.name === 'value' && node.name === 'textarea') { if (attribute.name === 'value' && node.name === 'textarea') {
if (node.children.length) { if (node.children.length) {
validator.error( `A <textarea> can have either a value attribute or (equivalently) child content, but not both`, attribute.start ); validator.error(
`A <textarea> can have either a value attribute or (equivalently) child content, but not both`,
attribute.start
);
} }
} }
} }
@ -89,7 +129,9 @@ export default function validateElement ( validator: Validator, node: Node ) {
} }
function getType(validator: Validator, node: Node) { function getType(validator: Validator, node: Node) {
const attribute = node.attributes.find( ( attribute: Node ) => attribute.name === 'type' ); const attribute = node.attributes.find(
(attribute: Node) => attribute.name === 'type'
);
if (!attribute) return null; if (!attribute) return null;
if (attribute.value === true) { if (attribute.value === true) {

@ -3,13 +3,12 @@ import list from '../utils/list';
import { Validator } from '../index'; import { Validator } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
const validBuiltins = new Set([ const validBuiltins = new Set(['set', 'fire', 'destroy']);
'set',
'fire',
'destroy'
]);
export default function validateEventHandlerCallee ( validator: Validator, attribute: Node ) { export default function validateEventHandlerCallee(
validator: Validator,
attribute: Node
) {
const { callee, start, type } = attribute.expression; const { callee, start, type } = attribute.expression;
if (type !== 'CallExpression') { if (type !== 'CallExpression') {
@ -19,15 +18,21 @@ export default function validateEventHandlerCallee ( validator: Validator, attri
const { name } = flattenReference(callee); const { name } = flattenReference(callee);
if (name === 'this' || name === 'event') return; if (name === 'this' || name === 'event') return;
if ( callee.type === 'Identifier' && validBuiltins.has( callee.name ) || validator.methods.has( callee.name ) ) return; if (
(callee.type === 'Identifier' && validBuiltins.has(callee.name)) ||
validator.methods.has(callee.name)
)
return;
const validCallees = [ 'this.*', 'event.*' ] const validCallees = ['this.*', 'event.*'].concat(
.concat(
Array.from(validBuiltins), Array.from(validBuiltins),
Array.from(validator.methods.keys()) Array.from(validator.methods.keys())
); );
let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${list( validCallees )})`; let message = `'${validator.source.slice(
callee.start,
callee.end
)}' is an invalid callee (should be one of ${list(validCallees)})`;
if (callee.type === 'Identifier' && validator.helpers.has(callee.name)) { if (callee.type === 'Identifier' && validator.helpers.has(callee.name)) {
message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`; message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;

@ -21,29 +21,35 @@ export default function validateWindow ( validator: Validator, node: Node ) {
const { parts } = flattenReference(attribute.value); const { parts } = flattenReference(attribute.value);
validator.error( validator.error(
`Bindings on <:Window/> must be to top-level properties, e.g. '${parts[ parts.length - 1 ]}' rather than '${parts.join( '.' )}'`, `Bindings on <:Window/> must be to top-level properties, e.g. '${parts[
parts.length - 1
]}' rather than '${parts.join('.')}'`,
attribute.value.start attribute.value.start
); );
} }
if (!~validBindings.indexOf(attribute.name)) { if (!~validBindings.indexOf(attribute.name)) {
const match = ( const match = attribute.name === 'width'
attribute.name === 'width' ? 'innerWidth' : ? 'innerWidth'
attribute.name === 'height' ? 'innerHeight' : : attribute.name === 'height'
fuzzymatch( attribute.name, validBindings ) ? 'innerHeight'
); : fuzzymatch(attribute.name, validBindings);
const message = `'${attribute.name}' is not a valid binding on <:Window>`; const message = `'${attribute.name}' is not a valid binding on <:Window>`;
if (match) { if (match) {
validator.error( `${message} (did you mean '${match}'?)`, attribute.start ); validator.error(
`${message} (did you mean '${match}'?)`,
attribute.start
);
} else { } else {
validator.error( `${message} — valid bindings are ${list( validBindings )}`, attribute.start ); validator.error(
} `${message} — valid bindings are ${list(validBindings)}`,
attribute.start
);
} }
} }
} else if (attribute.type === 'EventHandler') {
else if ( attribute.type === 'EventHandler' ) {
validateEventHandler(validator, attribute); validateEventHandler(validator, attribute);
} }
}); });

@ -2,11 +2,16 @@ import validateJs from './js/index';
import validateHtml from './html/index'; import validateHtml from './html/index';
import { getLocator, Location } from 'locate-character'; import { getLocator, Location } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import CompileError from '../utils/CompileError' import CompileError from '../utils/CompileError';
import { Node, Parsed, CompileOptions, Warning } from '../interfaces'; import { Node, Parsed, CompileOptions, Warning } from '../interfaces';
class ValidationError extends CompileError { class ValidationError extends CompileError {
constructor ( message: string, template: string, index: number, filename: string ) { constructor(
message: string,
template: string,
index: number,
filename: string
) {
super(message, template, index, filename); super(message, template, index, filename);
this.name = 'ValidationError'; this.name = 'ValidationError';
} }
@ -64,7 +69,11 @@ export class Validator {
} }
} }
export default function validate ( parsed: Parsed, source: string, options: CompileOptions ) { export default function validate(
parsed: Parsed,
source: string,
options: CompileOptions
) {
const { onwarn, onerror, name, filename } = options; const { onwarn, onerror, name, filename } = options;
try { try {

@ -17,7 +17,10 @@ export default function validateJs ( validator: Validator, js: Node ) {
if (node.type === 'ExportDefaultDeclaration') { if (node.type === 'ExportDefaultDeclaration') {
if (node.declaration.type !== 'ObjectExpression') { if (node.declaration.type !== 'ObjectExpression') {
return validator.error( `Default export must be an object literal`, node.declaration.start ); return validator.error(
`Default export must be an object literal`,
node.declaration.start
);
} }
checkForComputedKeys(validator, node.declaration.properties); checkForComputedKeys(validator, node.declaration.properties);
@ -31,11 +34,17 @@ export default function validateJs ( validator: Validator, js: Node ) {
// Remove these checks in version 2 // Remove these checks in version 2
if (props.has('oncreate') && props.has('onrender')) { if (props.has('oncreate') && props.has('onrender')) {
validator.error( 'Cannot have both oncreate and onrender', props.get( 'onrender' ).start ); validator.error(
'Cannot have both oncreate and onrender',
props.get('onrender').start
);
} }
if (props.has('ondestroy') && props.has('onteardown')) { if (props.has('ondestroy') && props.has('onteardown')) {
validator.error( 'Cannot have both ondestroy and onteardown', props.get( 'onteardown' ).start ); validator.error(
'Cannot have both ondestroy and onteardown',
props.get('onteardown').start
);
} }
// ensure all exported props are valid // ensure all exported props are valid
@ -47,11 +56,22 @@ export default function validateJs ( validator: Validator, js: Node ) {
} else { } else {
const match = fuzzymatch(prop.key.name, validPropList); const match = fuzzymatch(prop.key.name, validPropList);
if (match) { if (match) {
validator.error( `Unexpected property '${prop.key.name}' (did you mean '${match}'?)`, prop.start ); validator.error(
`Unexpected property '${prop.key
.name}' (did you mean '${match}'?)`,
prop.start
);
} else if (/FunctionExpression/.test(prop.value.type)) { } else if (/FunctionExpression/.test(prop.value.type)) {
validator.error( `Unexpected property '${prop.key.name}' (did you mean to include it in 'methods'?)`, prop.start ); validator.error(
`Unexpected property '${prop.key
.name}' (did you mean to include it in 'methods'?)`,
prop.start
);
} else { } else {
validator.error( `Unexpected property '${prop.key.name}'`, prop.start ); validator.error(
`Unexpected property '${prop.key.name}'`,
prop.start
);
} }
} }
}); });

@ -5,7 +5,10 @@ import { Node } from '../../../interfaces';
export default function components(validator: Validator, prop: Node) { export default function components(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error( `The 'components' property must be an object literal`, prop.start ); validator.error(
`The 'components' property must be an object literal`,
prop.start
);
return; return;
} }
@ -14,7 +17,10 @@ export default function components ( validator: Validator, prop: Node ) {
prop.value.properties.forEach((component: Node) => { prop.value.properties.forEach((component: Node) => {
if (component.key.name === 'state') { if (component.key.name === 'state') {
validator.error( `Component constructors cannot be called 'state' due to technical limitations`, component.start ); validator.error(
`Component constructors cannot be called 'state' due to technical limitations`,
component.start
);
} }
if (!/^[A-Z]/.test(component.key.name)) { if (!/^[A-Z]/.test(component.key.name)) {

@ -3,11 +3,17 @@ import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../'; import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
const isFunctionExpression = new Set( [ 'FunctionExpression', 'ArrowFunctionExpression' ] ); const isFunctionExpression = new Set([
'FunctionExpression',
'ArrowFunctionExpression'
]);
export default function computed(validator: Validator, prop: Node) { export default function computed(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error( `The 'computed' property must be an object literal`, prop.start ); validator.error(
`The 'computed' property must be an object literal`,
prop.start
);
return; return;
} }
@ -16,22 +22,34 @@ export default function computed ( validator: Validator, prop: Node ) {
prop.value.properties.forEach((computation: Node) => { prop.value.properties.forEach((computation: Node) => {
if (!isFunctionExpression.has(computation.value.type)) { if (!isFunctionExpression.has(computation.value.type)) {
validator.error( `Computed properties can be function expressions or arrow function expressions`, computation.value.start ); validator.error(
`Computed properties can be function expressions or arrow function expressions`,
computation.value.start
);
return; return;
} }
const params = computation.value.params; const params = computation.value.params;
if (params.length === 0) { if (params.length === 0) {
validator.error( `A computed value must depend on at least one property`, computation.value.start ); validator.error(
`A computed value must depend on at least one property`,
computation.value.start
);
return; return;
} }
params.forEach((param: Node) => { params.forEach((param: Node) => {
const valid = param.type === 'Identifier' || param.type === 'AssignmentPattern' && param.left.type === 'Identifier'; const valid =
param.type === 'Identifier' ||
(param.type === 'AssignmentPattern' &&
param.left.type === 'Identifier');
if (!valid) { if (!valid) {
validator.error( `Computed properties cannot use destructuring in function parameters`, param.start ); validator.error(
`Computed properties cannot use destructuring in function parameters`,
param.start
);
} }
}); });
}); });

@ -5,7 +5,10 @@ import { Node } from '../../../interfaces';
export default function events(validator: Validator, prop: Node) { export default function events(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error( `The 'events' property must be an object literal`, prop.start ); validator.error(
`The 'events' property must be an object literal`,
prop.start
);
return; return;
} }

@ -6,7 +6,10 @@ import { Node } from '../../../interfaces';
export default function helpers(validator: Validator, prop: Node) { export default function helpers(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error( `The 'helpers' property must be an object literal`, prop.start ); validator.error(
`The 'helpers' property must be an object literal`,
prop.start
);
return; return;
} }
@ -23,20 +26,28 @@ export default function helpers ( validator: Validator, prop: Node ) {
enter(node: Node) { enter(node: Node) {
if (/^Function/.test(node.type)) { if (/^Function/.test(node.type)) {
lexicalDepth += 1; lexicalDepth += 1;
} } else if (lexicalDepth === 0) {
else if ( lexicalDepth === 0 ) {
// handle special case that's caused some people confusion — using `this.get(...)` instead of passing argument // handle special case that's caused some people confusion — using `this.get(...)` instead of passing argument
// TODO do the same thing for computed values? // TODO do the same thing for computed values?
if ( node.type === 'CallExpression' && node.callee.type === 'MemberExpression' && node.callee.object.type === 'ThisExpression' && node.callee.property.name === 'get' && !node.callee.property.computed ) { if (
validator.error( `Cannot use this.get(...) — it must be passed into the helper function as an argument`, node.start ); node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'ThisExpression' &&
node.callee.property.name === 'get' &&
!node.callee.property.computed
) {
validator.error(
`Cannot use this.get(...) — it must be passed into the helper function as an argument`,
node.start
);
} }
if (node.type === 'ThisExpression') { if (node.type === 'ThisExpression') {
validator.error( `Helpers should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`, node.start ); validator.error(
} `Helpers should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`,
node.start
else if ( node.type === 'Identifier' && node.name === 'arguments' ) { );
} else if (node.type === 'Identifier' && node.name === 'arguments') {
usesArguments = true; usesArguments = true;
} }
} }
@ -50,7 +61,10 @@ export default function helpers ( validator: Validator, prop: Node ) {
}); });
if (prop.value.params.length === 0 && !usesArguments) { if (prop.value.params.length === 0 && !usesArguments) {
validator.warn( `Helpers should be pure functions, with at least one argument`, prop.start ); validator.warn(
`Helpers should be pure functions, with at least one argument`,
prop.start
);
} }
}); });
} }

@ -9,7 +9,10 @@ const builtin = new Set( [ 'set', 'get', 'on', 'fire', 'observe', 'destroy' ] );
export default function methods(validator: Validator, prop: Node) { export default function methods(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error( `The 'methods' property must be an object literal`, prop.start ); validator.error(
`The 'methods' property must be an object literal`,
prop.start
);
return; return;
} }
@ -19,12 +22,19 @@ export default function methods ( validator: Validator, prop: Node ) {
prop.value.properties.forEach((prop: Node) => { prop.value.properties.forEach((prop: Node) => {
if (builtin.has(prop.key.name)) { if (builtin.has(prop.key.name)) {
validator.error( `Cannot overwrite built-in method '${prop.key.name}'`, prop.start ); validator.error(
`Cannot overwrite built-in method '${prop.key.name}'`,
prop.start
);
} }
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error( `Method '${prop.key.name}' should be a function expression, not an arrow function expression`, prop.start ); validator.error(
`Method '${prop.key
.name}' should be a function expression, not an arrow function expression`,
prop.start
);
} }
} }
}); });

@ -9,13 +9,19 @@ export default function namespace ( validator: Validator, prop: Node ) {
const ns = prop.value.value; const ns = prop.value.value;
if (prop.value.type !== 'Literal' || typeof ns !== 'string') { if (prop.value.type !== 'Literal' || typeof ns !== 'string') {
validator.error( `The 'namespace' property must be a string literal representing a valid namespace`, prop.start ); validator.error(
`The 'namespace' property must be a string literal representing a valid namespace`,
prop.start
);
} }
if (!valid.has(ns)) { if (!valid.has(ns)) {
const match = fuzzymatch(ns, namespaces.validNamespaces); const match = fuzzymatch(ns, namespaces.validNamespaces);
if (match) { if (match) {
validator.error( `Invalid namespace '${ns}' (did you mean '${match}'?)`, prop.start ); validator.error(
`Invalid namespace '${ns}' (did you mean '${match}'?)`,
prop.start
);
} else { } else {
validator.error(`Invalid namespace '${ns}'`, prop.start); validator.error(`Invalid namespace '${ns}'`, prop.start);
} }

@ -5,7 +5,10 @@ import { Node } from '../../../interfaces';
export default function oncreate(validator: Validator, prop: Node) { export default function oncreate(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error( `'oncreate' should be a function expression, not an arrow function expression`, prop.start ); validator.error(
`'oncreate' should be a function expression, not an arrow function expression`,
prop.start
);
} }
} }
} }

@ -5,7 +5,10 @@ import { Node } from '../../../interfaces';
export default function ondestroy(validator: Validator, prop: Node) { export default function ondestroy(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error( `'ondestroy' should be a function expression, not an arrow function expression`, prop.start ); validator.error(
`'ondestroy' should be a function expression, not an arrow function expression`,
prop.start
);
} }
} }
} }

@ -3,6 +3,9 @@ import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function onrender(validator: Validator, prop: Node) { export default function onrender(validator: Validator, prop: Node) {
validator.warn( `'onrender' has been deprecated in favour of 'oncreate', and will cause an error in Svelte 2.x`, prop.start ); validator.warn(
`'onrender' has been deprecated in favour of 'oncreate', and will cause an error in Svelte 2.x`,
prop.start
);
oncreate(validator, prop); oncreate(validator, prop);
} }

@ -3,6 +3,9 @@ import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function onteardown(validator: Validator, prop: Node) { export default function onteardown(validator: Validator, prop: Node) {
validator.warn( `'onteardown' has been deprecated in favour of 'ondestroy', and will cause an error in Svelte 2.x`, prop.start ); validator.warn(
`'onteardown' has been deprecated in favour of 'ondestroy', and will cause an error in Svelte 2.x`,
prop.start
);
ondestroy(validator, prop); ondestroy(validator, prop);
} }

@ -5,7 +5,10 @@ import { Node } from '../../../interfaces';
export default function transitions(validator: Validator, prop: Node) { export default function transitions(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error( `The 'transitions' property must be an object literal`, prop.start ); validator.error(
`The 'transitions' property must be an object literal`,
prop.start
);
return; return;
} }

@ -1,7 +1,11 @@
import { Validator } from '../../'; import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function checkForAccessors ( validator: Validator, properties: Node[], label: string ) { export default function checkForAccessors(
validator: Validator,
properties: Node[],
label: string
) {
properties.forEach(prop => { properties.forEach(prop => {
if (prop.kind !== 'init') { if (prop.kind !== 'init') {
validator.error(`${label} cannot use getters and setters`, prop.start); validator.error(`${label} cannot use getters and setters`, prop.start);

@ -1,7 +1,10 @@
import { Validator } from '../../'; import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function checkForComputedKeys ( validator: Validator, properties: Node[] ) { export default function checkForComputedKeys(
validator: Validator,
properties: Node[]
) {
properties.forEach(prop => { properties.forEach(prop => {
if (prop.key.computed) { if (prop.key.computed) {
validator.error(`Cannot use computed keys`, prop.start); validator.error(`Cannot use computed keys`, prop.start);

@ -1,7 +1,10 @@
import { Validator } from '../../'; import { Validator } from '../../';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
export default function checkForDupes ( validator: Validator, properties: Node[] ) { export default function checkForDupes(
validator: Validator,
properties: Node[]
) {
const seen = new Set(); const seen = new Set();
properties.forEach(prop => { properties.forEach(prop => {

@ -7,7 +7,11 @@ export default function usesThisOrArguments ( node: Node ) {
walk(node, { walk(node, {
enter(node: Node, parent: Node) { enter(node: Node, parent: Node) {
if ( result || node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' ) { if (
result ||
node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration'
) {
return this.skip(); return this.skip();
} }
@ -15,7 +19,11 @@ export default function usesThisOrArguments ( node: Node ) {
result = true; result = true;
} }
if ( node.type === 'Identifier' && isReference( node, parent ) && node.name === 'arguments' ) { if (
node.type === 'Identifier' &&
isReference(node, parent) &&
node.name === 'arguments'
) {
result = true; result = true;
} }
} }

@ -1,12 +1,19 @@
// adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js // adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js
// BSD Licensed // BSD Licensed
export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUpper) { export default function FuzzySet(
arr,
useLevenshtein,
gramSizeLower,
gramSizeUpper
) {
// default options // default options
arr = arr || []; arr = arr || [];
this.gramSizeLower = gramSizeLower || 2; this.gramSizeLower = gramSizeLower || 2;
this.gramSizeUpper = gramSizeUpper || 3; this.gramSizeUpper = gramSizeUpper || 3;
this.useLevenshtein = (typeof useLevenshtein !== 'boolean') ? true : useLevenshtein; this.useLevenshtein = typeof useLevenshtein !== 'boolean'
? true
: useLevenshtein;
// define all the object functions and attributes // define all the object functions and attributes
this.exactSet = {}; this.exactSet = {};
@ -41,9 +48,11 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
// return an edit distance from 0 to 1 // return an edit distance from 0 to 1
function _distance(str1, str2) { function _distance(str1, str2) {
if (str1 === null && str2 === null) throw 'Trying to compare two null values'; if (str1 === null && str2 === null)
throw 'Trying to compare two null values';
if (str1 === null || str2 === null) return 0; if (str1 === null || str2 === null) return 0;
str1 = String(str1); str2 = String(str2); str1 = String(str1);
str2 = String(str2);
const distance = levenshtein(str1, str2); const distance = levenshtein(str1, str2);
if (str1.length > str2.length) { if (str1.length > str2.length) {
@ -109,7 +118,11 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
let results = []; let results = [];
// start with high gram size and if there are no results, go to lower gram sizes // start with high gram size and if there are no results, go to lower gram sizes
for (let gramSize = this.gramSizeUpper; gramSize >= this.gramSizeLower; --gramSize) { for (
let gramSize = this.gramSizeUpper;
gramSize >= this.gramSizeLower;
--gramSize
) {
results = this.__get(value, gramSize); results = this.__get(value, gramSize);
if (results) { if (results) {
return results; return results;
@ -148,8 +161,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
function isEmptyObject(obj) { function isEmptyObject(obj) {
for (const prop in obj) { for (const prop in obj) {
if ( obj.hasOwnProperty( prop ) ) if (obj.hasOwnProperty(prop)) return false;
return false;
} }
return true; return true;
} }
@ -165,7 +177,10 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
// build a results list of [score, str] // build a results list of [score, str]
for (const matchIndex in matches) { for (const matchIndex in matches) {
matchScore = matches[matchIndex]; matchScore = matches[matchIndex];
results.push([matchScore / (vectorNormal * items[matchIndex][0]), items[matchIndex][1]]); results.push([
matchScore / (vectorNormal * items[matchIndex][0]),
items[matchIndex][1]
]);
} }
function sortDescending(a, b) { function sortDescending(a, b) {
if (a[0] < b[0]) { if (a[0] < b[0]) {
@ -183,7 +198,10 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
const endIndex = Math.min(50, results.length); const endIndex = Math.min(50, results.length);
// truncate somewhat arbitrarily to 50 // truncate somewhat arbitrarily to 50
for (let i = 0; i < endIndex; ++i) { for (let i = 0; i < endIndex; ++i) {
newResults.push([_distance(results[i][1], normalizedValue), results[i][1]]); newResults.push([
_distance(results[i][1], normalizedValue),
results[i][1]
]);
} }
results = newResults; results = newResults;
results.sort(sortDescending); results.sort(sortDescending);
@ -236,7 +254,8 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
}; };
this._normalizeStr = function(str) { this._normalizeStr = function(str) {
if (Object.prototype.toString.call(str) !== '[object String]') throw 'Must use a string as argument to FuzzySet functions'; if (Object.prototype.toString.call(str) !== '[object String]')
throw 'Must use a string as argument to FuzzySet functions';
return str.toLowerCase(); return str.toLowerCase();
}; };
@ -275,7 +294,6 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
return values; return values;
}; };
// initialization // initialization
let i = this.gramSizeLower; let i = this.gramSizeLower;
for (i; i < this.gramSizeUpper + 1; ++i) { for (i; i < this.gramSizeUpper + 1; ++i) {

@ -4,7 +4,5 @@ export default function fuzzymatch ( name: string, names: string[] ) {
const set = new FuzzySet(names); const set = new FuzzySet(names);
const matches = set.get(name); const matches = set.get(name);
return matches && matches[0] && matches[0][0] > 0.7 ? return matches && matches[0] && matches[0][0] > 0.7 ? matches[0][1] : null;
matches[0][1] :
null;
} }

@ -1,4 +1,6 @@
export default function list(items: string[], conjunction = 'or') { export default function list(items: string[], conjunction = 'or') {
if (items.length === 1) return items[0]; if (items.length === 1) return items[0];
return `${items.slice( 0, -1 ).join( ', ' )} ${conjunction} ${items[ items.length - 1 ]}`; return `${items.slice(0, -1).join(', ')} ${conjunction} ${items[
items.length - 1
]}`;
} }

@ -1,7 +1,10 @@
function noop() {} function noop() {}
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function setAttribute ( node, attribute, value ) {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -66,7 +69,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,7 +1,10 @@
function noop() {} function noop() {}
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -11,7 +14,7 @@ function assign ( target ) {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -42,7 +45,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -51,7 +55,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,7 +1,10 @@
function noop() {} function noop() {}
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -44,7 +47,7 @@ function createText ( data ) {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -75,7 +78,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -84,7 +88,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,5 +1,8 @@
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -29,7 +32,7 @@ function createText ( data ) {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -60,7 +63,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -69,7 +73,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,7 +1,10 @@
function noop() {} function noop() {}
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function createComment () {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -66,7 +69,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,7 +1,10 @@
function noop() {} function noop() {}
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function createComment () {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -66,7 +69,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,7 +1,10 @@
import Imported from 'Imported.html'; import Imported from 'Imported.html';
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -23,7 +26,7 @@ function createText ( data ) {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -54,7 +57,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -63,7 +67,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,7 +1,10 @@
function noop() {} function noop() {}
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -11,7 +14,7 @@ function assign ( target ) {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -42,7 +45,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -51,7 +55,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -1,7 +1,10 @@
function noop() {} function noop() {}
function assign(target) { function assign(target) {
var k, source, i = 1, len = arguments.length; var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) { for (; i < len; i++) {
source = arguments[i]; source = arguments[i];
for (k in source) target[k] = source[k]; for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function createComment () {
} }
function differs(a, b) { function differs(a, b) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
function dispatchObservers(component, group, newState, oldState) { function dispatchObservers(component, group, newState, oldState) {
@ -66,7 +69,8 @@ function get ( key ) {
} }
function fire(eventName, data) { function fire(eventName, data) {
var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return; if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) { for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
} }
function observe(key, callback, options) { function observe(key, callback, options) {
var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback); (group[key] || (group[key] = [])).push(callback);

@ -0,0 +1,10 @@
// this file will replace all the expected.js and expected-bundle.js files with
// their _actual equivalents. Only use it when you're sure that you haven't
// broken anything!
const fs = require('fs');
const glob = require('glob');
glob.sync('samples/*/_actual*', {cwd: __dirname}).forEach(file => {
const actual = fs.readFileSync(`${__dirname}/${file}`, 'utf-8');
fs.writeFileSync(`${__dirname}/${file.replace('_actual', 'expected')}`, actual);
});
Loading…
Cancel
Save