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>;
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.source = source;
this.name = name;
@ -91,7 +96,12 @@ export default class Generator {
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);
const usedContexts: string[] = [];
@ -114,37 +124,36 @@ export default class Generator {
}
if (node.type === 'ThisExpression') {
if ( lexicalDepth === 0 && context ) code.overwrite( node.start, node.end, context, { storeName: true, contentOnly: false } );
}
else if ( isReference( node, parent ) ) {
if (lexicalDepth === 0 && context)
code.overwrite(node.start, node.end, context, {
storeName: true,
contentOnly: false
});
} else if (isReference(node, parent)) {
const { name } = flattenReference(node);
if (scope.has(name)) return;
if (name === 'event' && isEventHandler) {
// noop
}
else if ( contexts.has( name ) ) {
} else if (contexts.has(name)) {
const contextName = contexts.get(name);
if (contextName !== name) {
// 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);
}
else if ( helpers.has( name ) ) {
} else if (helpers.has(name)) {
code.prependRight(node.start, `${self.alias('template')}.helpers.`);
}
else if ( indexes.has( name ) ) {
} else if (indexes.has(name)) {
const context = indexes.get(name);
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
}
else {
} else {
// handle shorthand properties
if (parent && parent.type === 'Property' && parent.shorthand) {
if (key === 'key') {
@ -155,7 +164,10 @@ export default class Generator {
if (globalWhitelist.has(name)) {
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 {
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;
let scope = annotateWithScopes(expression);
@ -229,23 +245,40 @@ export default class Generator {
this.imports.forEach((declaration, i) => {
if (format === 'es') {
statements.push( this.source.slice( declaration.start, declaration.end ) );
statements.push(
this.source.slice(declaration.start, declaration.end)
);
return;
}
const defaultImport = declaration.specifiers.find( ( x: Node ) => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' );
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}`;
const defaultImport = declaration.specifiers.find(
(x: Node) =>
x.type === 'ImportDefaultSpecifier' ||
(x.type === 'ImportSpecifier' && x.imported.name === 'default')
);
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
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) {
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 {
code: compiled.toString(),
map: compiled.generateMap({ includeContent: true, file: options.outputFilename }),
map: compiled.generateMap({
includeContent: true,
file: options.outputFilename
}),
css: this.css
};
}
@ -306,7 +342,13 @@ export default class Generator {
getUniqueName(name: string) {
if (test) name = `${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);
return alias;
}
@ -316,7 +358,14 @@ export default class Generator {
return name => {
if (test) name = `${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);
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) {
defaultExport.declaration.properties.forEach((prop: Node) => {
@ -373,7 +424,10 @@ export default class Generator {
const key = prop.key.name;
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);
});
@ -391,7 +445,9 @@ export default class Generator {
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) {
@ -403,19 +459,28 @@ export default class Generator {
if (templateProperties.components) {
let hasNonImportedComponent = false;
templateProperties.components.value.properties.forEach( ( property: Node ) => {
templateProperties.components.value.properties.forEach(
(property: Node) => {
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)) {
this.importedComponents.set(key, value);
} else {
hasNonImportedComponent = true;
}
});
}
);
if (hasNonImportedComponent) {
// remove the specific components that were imported, as we'll refer to them directly
Array.from(this.importedComponents.keys()).forEach(key => {
removeObjectKey( this.code, templateProperties.components.value, key );
removeObjectKey(
this.code,
templateProperties.components.value,
key
);
});
} else {
// remove the entire components portion of the export
@ -426,22 +491,48 @@ export default class Generator {
// Remove these after version 2
if (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;
}
if (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;
}
// in an SSR context, we don't need to include events, methods, oncreate or ondestroy
if (ssr) {
if ( templateProperties.oncreate ) removeNode( this.code, 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 );
if (templateProperties.oncreate)
removeNode(
this.code,
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
@ -456,30 +547,47 @@ export default class Generator {
const finalNode = body[body.length - 1];
if (defaultExport === finalNode) {
// 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 {
const { declarations } = annotateWithScopes(js);
let template = 'template';
for ( let i = 1; declarations.has( template ); template = `template_${i++}` );
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var ${template} = ` );
for (
let i = 1;
declarations.has(template);
template = `template_${i++}`
);
this.code.overwrite(
defaultExport.start,
defaultExport.declaration.start,
`var ${template} = `
);
let i = defaultExport.start;
while (/\s/.test(source[i - 1])) i--;
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
if (js.content.body.length) {
const prefix = hasDefaultExport ? `var ${this.alias( 'template' )} = (function () {` : `(function () {`;
this.code.prependRight( js.content.start, prefix ).appendLeft( js.content.end, '}());' );
}
const prefix = hasDefaultExport
? `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
else {
this.code.remove(js.content.start, js.content.end);
hasJs = false;
}

@ -48,7 +48,7 @@ export default class Block {
unmount: CodeBuilder;
detachRaw: CodeBuilder;
destroy: CodeBuilder;
}
};
hasIntroMethod: 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;
if (needsIdentifier || isToplevel) {
this.builders.create.addLine(
`var ${name} = ${renderStatement};`
);
this.builders.create.addLine(`var ${name} = ${renderStatement};`);
this.mount(name, parentNode);
} else {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${parentNode} );` );
this.builders.create.addLine(
`${this.generator.helper(
'appendNode'
)}( ${renderStatement}, ${parentNode} );`
);
}
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) {
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);
@ -155,18 +166,32 @@ export default class Block {
}
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) {
return this.generator.findDependencies( this.contextDependencies, this.indexes, expression );
return this.generator.findDependencies(
this.contextDependencies,
this.indexes,
expression
);
}
mount(name: string, parentNode: string) {
if (parentNode) {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${parentNode} );` );
this.builders.create.addLine(
`${this.generator.helper('appendNode')}( ${name}, ${parentNode} );`
);
} 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`
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}
return {

@ -13,7 +13,7 @@ import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces';
export class DomGenerator extends Generator {
blocks: Block[]
blocks: Block[];
uses: Set<string>;
readonly: Set<string>;
metaBindings: string[];
@ -22,7 +22,12 @@ export class DomGenerator extends Generator {
hasOutroTransitions: 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);
this.blocks = [];
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 name = options.name || 'SvelteComponent';
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);
@ -70,47 +84,71 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
computations.forEach(({ key, deps }) => {
if (generator.readonly.has(key)) {
// <: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);
const condition = `isInitial || ${deps.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( ', ' )} );`;
const condition = `isInitial || ${deps
.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);
});
builders.main.addBlock(deindent`
function ${generator.alias( 'recompute' )} ( state, newState, oldState, isInitial ) {
function ${generator.alias(
'recompute'
)} ( state, newState, oldState, isInitial ) {
${builder}
}
`);
}
builders._set.addBlock(deindent`
${options.dev && deindent`
${options.dev &&
deindent`
if ( typeof newState !== 'object' ) {
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}'" );`
)}
`}
var oldState = this._state;
this._state = ${generator.helper('assign')}( {}, oldState, newState );
${computations.length && `${generator.alias( 'recompute' )}( this._state, newState, oldState, false )`}
${generator.helper( 'dispatchObservers' )}( this, this._observers.pre, newState, oldState );
${computations.length &&
`${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 );`}
${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState );
${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`}
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`}
${generator.helper(
'dispatchObservers'
)}( this, this._observers.post, newState, oldState );
${generator.hasComplexBindings &&
`while ( this._bindings.length ) this._bindings.pop()();`}
${(generator.hasComponents || generator.hasIntroTransitions) &&
`this._flush();`}
`);
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) {
@ -128,29 +166,50 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
builders.main.addBlock(block.render());
});
const sharedPath = options.shared === true ? 'svelte/shared.js' : options.shared;
const prototypeBase = `${name}.prototype` + ( templateProperties.methods ? `, ${generator.alias( 'template' )}.methods` : '' );
const proto = sharedPath ? `${generator.helper( 'proto' )} ` : deindent`
const sharedPath = options.shared === true
? 'svelte/shared.js'
: options.shared;
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)}`)
.join( ',\n' )
}
.join(',\n')}
}`;
// TODO deprecate component.teardown()
builders.main.addBlock(deindent`
function ${name} ( 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 = {};`}
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}
${computations.length && `${generator.alias( '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( ', ' )} ];`}
${computations.length &&
`${generator.alias(
'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 = {
pre: Object.create( null ),
@ -163,18 +222,30 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
this._yield = options._yield;
this._torndown = false;
${parsed.css && options.css !== false && `if ( !document.getElementById( ${JSON.stringify( generator.cssId + '-style' )} ) ) ${generator.alias( 'add_css' )}();`}
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._renderHooks = [];`}
${parsed.css &&
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 = [];`}
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 );
${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`}
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`}
${generator.hasComplexBindings &&
`while ( this._bindings.length ) this._bindings.pop()();`}
${(generator.hasComponents || generator.hasIntroTransitions) &&
`this._flush();`}
${templateProperties.oncreate && deindent`
${templateProperties.oncreate &&
deindent`
if ( options._root ) {
options._root._renderHooks.push( ${generator.alias( 'template' )}.oncreate.bind( this ) );
options._root._renderHooks.push( ${generator.alias(
'template'
)}.oncreate.bind( this ) );
} else {
${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 ) {
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();
this._fragment.destroy();
@ -202,11 +274,15 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
if (sharedPath) {
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 => {
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(
@ -224,14 +300,19 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
enter(node, parent) {
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) {
// this helper function depends on another one
const dependency = node.name;
generator.uses.add(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`;
builders.main.addBlock(
`var ${generator.alias( 'transitionManager' )} = window.${global} || ( window.${global} = ${code});`
`var ${generator.alias(
'transitionManager'
)} = window.${global} || ( window.${global} = ${code});`
);
} else {
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());
}
});
}
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;
namespace: string;
parentNode: string;
isTopLevel: boolean
isTopLevel: boolean;
parentNodeName?: string;
basename?: string;
inEachBlock?: boolean;

@ -6,7 +6,9 @@ import { Node } from '../../interfaces';
import { State } from './interfaces';
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 = {}) {
@ -28,7 +30,12 @@ const elementsWithoutText = new Set([
]);
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);
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);
block.addDependencies(dependencies);
@ -59,7 +71,12 @@ const preprocessors = {
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[] = [];
let dynamic = false;
let hasIntros = false;
@ -96,7 +113,12 @@ const preprocessors = {
node.else._state = getChildState(state);
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) {
dynamic = true;
@ -116,12 +138,18 @@ const preprocessors = {
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);
block.addDependencies(dependencies);
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);
const listNames = new Map(block.listNames);
@ -173,25 +201,40 @@ const preprocessors = {
node.else._state = getChildState(state);
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;
}
},
Element: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
Element: (
generator: DomGenerator,
block: Block,
state: State,
node: Node
) => {
const isComponent =
generator.components.has(node.name) || node.name === ':Self';
if (isComponent) {
node._state = getChildState(state);
} 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, {
isTopLevel: false,
name,
parentNode: 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: []
});
}
@ -204,15 +247,12 @@ const preprocessors = {
block.addDependencies(dependencies);
}
});
}
else if ( attribute.type === 'Binding' ) {
} else if (attribute.type === 'Binding') {
const dependencies = block.findDependencies(attribute.value);
block.addDependencies(dependencies);
}
else if ( attribute.type === 'Transition' ) {
if ( attribute.intro ) generator.hasIntroTransitions = block.hasIntroMethod = true;
} else if (attribute.type === 'Transition') {
if (attribute.intro)
generator.hasIntroTransitions = block.hasIntroMethod = true;
if (attribute.outro) {
generator.hasOutroTransitions = block.hasOutroMethod = true;
block.outros += 1;
@ -222,7 +262,9 @@ const preprocessors = {
if (node.children.length) {
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({
name: generator.getUniqueName(`create_${name}_yield_fragment`)
@ -232,16 +274,20 @@ const preprocessors = {
preprocessChildren(generator, node._block, node._state, node);
block.addDependencies(node._block.dependencies);
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
}
else {
} else {
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
const cleaned: Node[] = [];
let lastChild: Node;
@ -295,7 +341,11 @@ function preprocessChildren ( generator: DomGenerator, block: Block, state: Stat
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({
generator,
name: generator.alias('create_main_fragment'),

@ -3,7 +3,12 @@ import { DomGenerator } from './index';
import Block from './Block';
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];
visitor(generator, block, state, node);
}

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

@ -6,14 +6,27 @@ import Block from '../../Block';
import { Node } 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 { 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 => {
if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context );
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
});
const contextual = block.contexts.has(name);
@ -25,7 +38,8 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
obj = block.listNames.get(name);
prop = block.indexNames.get(name);
} 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}✂]`;
} else {
obj = 'state';
@ -39,7 +53,15 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
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;
@ -54,12 +76,16 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
${updating} = true;
${setter}
${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`
if ( !${updating} && ${dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) {
if ( !${updating} && ${dependencies
.map(dependency => `'${dependency}' in changed`)
.join('||')} ) {
${updating} = true;
${local.name}._set({ ${attribute.name}: ${snippet} });
${updating} = false;

@ -36,9 +36,16 @@ const visitors = {
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 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;
@ -63,27 +70,38 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
node.attributes
.sort((a, b) => order[a.type] - order[b.type])
.forEach(attribute => {
visitors[ attribute.type ]( generator, block, childState, node, attribute, local );
visitors[attribute.type](
generator,
block,
childState,
node,
attribute,
local
);
});
if (local.allUsedContexts.length) {
const initialProps = local.allUsedContexts.map( contextName => {
const initialProps = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `state: state`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
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;`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
}).join( '\n' );
})
.join('\n');
local.create.addBlock(deindent`
${name}._context = {
@ -121,16 +139,18 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
);
}
block.builders.destroy.addLine(
`${yieldFragment}.destroy();`
);
block.builders.destroy.addLine(`${yieldFragment}.destroy();`);
componentInitProperties.push(`_yield: ${yieldFragment}`);
}
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
.concat(local.dynamicAttributes)
.map(attribute => `${attribute.name}: ${attribute.value}`);
@ -143,7 +163,9 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
statements.push(`var ${initialData} = ${initialPropString};`);
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}`);
@ -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`
${statements.join('\n')}
@ -162,14 +187,20 @@ export default function visitComponent ( generator: DomGenerator, block: Block,
`);
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) {
const updates = local.dynamicAttributes.map(attribute => {
if (attribute.dependencies.length) {
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.create.addBlock(local.create);

@ -4,10 +4,20 @@ import Block from '../../Block';
import { Node } 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)
generator.addSourcemapLocations(attribute.expression);
generator.code.prependRight( attribute.expression.start, `${block.component}.` );
generator.code.prependRight(
attribute.expression.start,
`${block.component}.`
);
const usedContexts: string[] = [];
attribute.expression.arguments.forEach((arg: Node) => {
@ -15,7 +25,8 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
contexts.forEach(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}]`;
});
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.name}.on( '${attribute.name}', function ( event ) {

@ -4,7 +4,14 @@ import Block from '../../Block';
import { Node } 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;
local.create.addLine(

@ -5,17 +5,33 @@ import Block from '../Block';
import { Node } 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 create_each_block = node._block.name;
const each_block_value = node._block.listName;
const iterations = block.getUniqueName(`${each_block}_iterations`);
const i = block.alias(`i`);
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 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);
@ -30,7 +46,12 @@ export default function visitEachBlock ( generator: DomGenerator, block: Block,
const isToplevel = !state.parentNode;
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) {
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
block.builders.create.addBlock(deindent`
if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
${!isToplevel ? `${each_block_else}.${mountOrIntro}( ${state.parentNode}, null );` : ''}
${each_block_else} = ${node.else._block
.name}( ${params}, ${block.component} );
${!isToplevel
? `${each_block_else}.${mountOrIntro}( ${state.parentNode}, null );`
: ''}
}
`);
block.builders.mount.addBlock(deindent`
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} ) {
${each_block_else}.update( changed, ${params} );
} 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} );
} else if ( ${each_block_else} ) {
${each_block_else}.unmount();
@ -78,7 +104,8 @@ export default function visitEachBlock ( generator: DomGenerator, block: Block,
${each_block_else} = null;
}
} 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} );
}
`);
@ -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 lookup = block.getUniqueName(`${each_block}_lookup`);
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 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
} else {
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`
@ -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 ) {
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} );
${state.parentNode && `${iteration}.${mountOrIntro}( ${state.parentNode}, null );`}
${state.parentNode &&
`${iteration}.${mountOrIntro}( ${state.parentNode}, null );`}
if ( ${last} ) ${last}.next = ${iteration};
${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 ${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 ( ${key} === ${expected}.key ) {
@ -256,7 +306,8 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
if ( ${last} ) ${last}.next = ${iteration};
${iteration}.last = ${last};
${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`}
${node._block.hasIntroMethod &&
`${iteration}.intro( ${parentNode}, ${anchor} );`}
${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`
var ${iterations} = [];
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} );
${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`;
if (condition !== '') {
const forLoopBody = node._block.hasUpdateMethod ?
node._block.hasIntroMethod ?
deindent`
const forLoopBody = node._block.hasUpdateMethod
? node._block.hasIntroMethod
? deindent`
if ( ${iterations}[${i}] ) {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
}
${iterations}[${i}].intro( ${parentNode}, ${anchor} );
` :
deindent`
`
: deindent`
if ( ${iterations}[${i}] ) {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${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}].${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 outro = block.getUniqueName('outro');
const destroy = node._block.hasOutroMethod ?
deindent`
const destroy = node._block.hasOutroMethod
? deindent`
function ${outro} ( i ) {
if ( ${iterations}[i] ) {
${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} );
` :
deindent`
`
: deindent`
for ( ; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].unmount();
${iterations}[${i}].destroy();

@ -6,25 +6,39 @@ import Block from '../../Block';
import { Node } 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;
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' && (
node.name === 'option' || // TODO check it's actually bound
node.name === 'input' && /^(checkbox|radio)$/.test( getStaticAttributeValue( node, 'type' ) )
);
const isIndirectlyBoundValue =
name === 'value' &&
(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
// namespaced attributes but I'm not sure that's applicable in
// 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) {
let value;
@ -35,35 +49,42 @@ export default function visitAttribute ( generator: DomGenerator, block: Block,
value = snippet;
} else {
// '{{foo}} {{bar}}' — treat as string concatenation
value = ( attribute.value[0].type === 'Text' ? '' : `"" + ` ) + (
attribute.value.map( ( chunk: Node ) => {
value =
(attribute.value[0].type === 'Text' ? '' : `"" + `) +
attribute.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return JSON.stringify(chunk.data);
} else {
const { snippet } = block.contextualise(chunk.expression);
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);
const isSelectValueAttribute = name === 'value' && state.parentNodeName === 'select';
const isSelectValueAttribute =
name === 'value' && state.parentNodeName === 'select';
let updater;
if (isSelectValueAttribute) {
// 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 option = block.getUniqueName('option');
const ifStatement = isMultipleSelect ?
deindent`
${option}.selected = ~${last}.indexOf( ${option}.__value );` :
deindent`
const ifStatement = isMultipleSelect
? deindent`
${option}.selected = ~${last}.indexOf( ${option}.__value );`
: deindent`
if ( ${option}.__value === ${last} ) {
${option}.selected = true;
break;
@ -82,11 +103,19 @@ export default function visitAttribute ( generator: DomGenerator, block: Block,
${updater}
`);
} 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};`;
} else {
block.builders.create.addLine( `${generator.helper( method )}( ${state.parentNode}, '${name}', ${last} = ${value} );` );
updater = `${generator.helper( method )}( ${state.parentNode}, '${name}', ${last} );`;
block.builders.create.addLine(
`${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${last} = ${value} );`
);
updater = `${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${last} );`;
}
block.builders.update.addBlock(deindent`
@ -94,17 +123,18 @@ export default function visitAttribute ( generator: DomGenerator, block: Block,
${updater}
}
`);
}
else {
const value = attribute.value === true ? 'true' :
attribute.value.length === 0 ? `''` :
JSON.stringify( attribute.value[0].data );
const statement = propertyName ?
`${state.parentNode}.${propertyName} = ${value};` :
`${generator.helper( method )}( ${state.parentNode}, '${name}', ${value} );`;
} else {
const value = attribute.value === true
? 'true'
: attribute.value.length === 0
? `''`
: JSON.stringify(attribute.value[0].data);
const statement = propertyName
? `${state.parentNode}.${propertyName} = ${value};`
: `${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${value} );`;
block.builders.create.addLine(statement);

@ -13,25 +13,62 @@ function getObject ( 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 { 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 => {
if ( !~state.allUsedContexts.indexOf( context ) ) state.allUsedContexts.push( context );
if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
});
const eventName = getBindingEventName(node, attribute);
const handler = block.getUniqueName( `${state.parentNode}_${eventName}_handler` );
const isMultipleSelect = node.name === 'select' && node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue
const handler = block.getUniqueName(
`${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 bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, attribute.value ) : null;
const value = getBindingValue( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type );
const bindingGroup = attribute.name === 'group'
? 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};`;
const lock = block.alias(`${state.parentNode}_updating`);
let updateCondition = `!${lock}`;
@ -48,10 +85,10 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
const i = block.alias('i');
const option = block.getUniqueName('option');
const ifStatement = isMultipleSelect ?
deindent`
${option}.selected = ~${value}.indexOf( ${option}.__value );` :
deindent`
const ifStatement = isMultipleSelect
? deindent`
${option}.selected = ~${value}.indexOf( ${option}.__value );`
: deindent`
if ( ${option}.__value === ${value} ) {
${option}.selected = true;
break;
@ -65,10 +102,8 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
${ifStatement}
}
`;
}
} else if (attribute.name === 'group') {
// <input type='checkbox|radio' bind:group='selected'> special case
else if ( attribute.name === 'group' ) {
if (type === 'radio') {
setter = deindent`
if ( !${state.parentNode}.checked ) return;
@ -76,9 +111,9 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
`;
}
const condition = type === 'checkbox' ?
`~${snippet}.indexOf( ${state.parentNode}.__value )` :
`${state.parentNode}.__value === ${snippet}`;
const condition = type === 'checkbox'
? `~${snippet}.indexOf( ${state.parentNode}.__value )`
: `${state.parentNode}.__value === ${snippet}`;
block.builders.create.addLine(
`${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};`;
}
else if ( node.name === 'audio' || node.name === 'video' ) {
} else if (node.name === 'audio' || node.name === 'video') {
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') {
const frame = block.getUniqueName(`${state.parentNode}_animationframe`);
@ -105,13 +140,9 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
`;
updateCondition += ` && !isNaN( ${snippet} )`;
}
else if ( attribute.name === 'duration' ) {
} else if (attribute.name === 'duration') {
updateCondition = null;
}
else if ( attribute.name === 'paused' ) {
} else if (attribute.name === 'paused') {
// this is necessary to prevent the audio restarting by itself
const last = block.getUniqueName(`${state.parentNode}_paused_value`);
block.addVariable(last, 'true');
@ -128,10 +159,13 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st
${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) {
// 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`
${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} );
${generator.helper(
'removeEventListener'
)}( ${state.parentNode}, '${eventName}', ${handler} );
`);
if (attribute.name === 'paused') {
block.builders.create.addLine( `${generator.helper( 'addEventListener' )}( ${state.parentNode}, 'play', ${handler} );` );
block.builders.destroy.addLine( `${generator.helper( 'removeEventListener' )}( ${state.parentNode}, 'play', ${handler} );` );
block.builders.create.addLine(
`${generator.helper(
'addEventListener'
)}( ${state.parentNode}, 'play', ${handler} );`
);
block.builders.destroy.addLine(
`${generator.helper(
'removeEventListener'
)}( ${state.parentNode}, 'play', ${handler} );`
);
}
}
function getBindingEventName(node: Node, attribute: Node) {
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
return type === 'checkbox' || type === 'radio' ? 'change' : 'input';
@ -168,7 +214,16 @@ function getBindingEventName ( node: Node, attribute: Node ) {
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>
if (isMultipleSelect) {
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'>
if (attribute.name === 'group') {
if (type === 'checkbox') {
return `${generator.helper( 'getBindingGroupValue' )}( ${block.component}._bindingGroups[${bindingGroup}] )`;
return `${generator.helper(
'getBindingGroupValue'
)}( ${block.component}._bindingGroups[${bindingGroup}] )`;
}
return `${state.parentNode}.__value`;
@ -190,7 +247,9 @@ function getBindingValue ( generator: DomGenerator, block: Block, state: State,
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `${generator.helper( 'toNumber' )}( ${state.parentNode}.${attribute.name} )`;
return `${generator.helper(
'toNumber'
)}( ${state.parentNode}.${attribute.name} )`;
}
// everything else

@ -30,7 +30,12 @@ const visitors = {
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) {
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 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);
// add CSS encapsulation attribute
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() {
@ -66,7 +81,8 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
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) {
const initialProps: string[] = [];
@ -82,8 +98,12 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
initialProps.push( `${listName}: ${listName},\n${indexName}: ${indexName}` );
updates.push( `${name}._svelte.${listName} = ${listName};\n${name}._svelte.${indexName} = ${indexName};` );
initialProps.push(
`${listName}: ${listName},\n${indexName}: ${indexName}`
);
updates.push(
`${name}._svelte.${listName} = ${listName};\n${name}._svelte.${indexName} = ${indexName};`
);
});
if (initialProps.length) {
@ -103,7 +123,9 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
if (!state.parentNode) {
// TODO we eventually need to consider what happens to elements
// 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') {
@ -127,7 +149,14 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
}
// 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;`;
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') {
return `${generator.helper('createSvgElement')}( '${name}' )`;
}

@ -5,7 +5,13 @@ import Block from '../../Block';
import { Node } 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 isCustomEvent = generator.events.has(name);
const shouldHoist = !isCustomEvent && state.inEachBlock;
@ -16,7 +22,10 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
if (flattened.name !== 'event' && flattened.name !== 'this') {
// allow event.stopPropagation(), this.select() etc
// 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!
}
@ -27,7 +36,8 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
contexts.forEach(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
// 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
const handlerBody = deindent`
@ -56,22 +68,26 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
[${attribute.expression.start}-${attribute.expression.end}];
`;
const handler = isCustomEvent ?
deindent`
var ${handlerName} = ${generator.alias( 'template' )}.events.${name}.call( ${block.component}, ${state.parentNode}, function ( event ) {
const handler = isCustomEvent
? deindent`
var ${handlerName} = ${generator.alias(
'template'
)}.events.${name}.call( ${block.component}, ${state.parentNode}, function ( event ) {
${handlerBody}
});
` :
deindent`
`
: deindent`
function ${handlerName} ( event ) {
${handlerBody}
}
`;
if (shouldHoist) {
generator.blocks.push(<Block>{
generator.blocks.push(
<Block>{
render: () => handler
});
}
);
} else {
block.builders.create.addBlock(handler);
}
@ -82,11 +98,15 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
`);
} else {
block.builders.create.addLine(deindent`
${generator.helper( 'addEventListener' )}( ${state.parentNode}, '${name}', ${handlerName} );
${generator.helper(
'addEventListener'
)}( ${state.parentNode}, '${name}', ${handlerName} );
`);
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 { 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;
block.builders.create.addLine(

@ -4,12 +4,21 @@ import Block from '../../Block';
import { Node } 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');
if (intro === outro) {
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);
@ -31,15 +40,15 @@ export default function addTransitions ( generator: DomGenerator, block: Block,
${name} = null;
});
`);
}
else {
} else {
const introName = intro && block.getUniqueName(`${state.name}_intro`);
const outroName = outro && block.getUniqueName(`${state.name}_outro`);
if (intro) {
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?
@ -62,7 +71,9 @@ export default function addTransitions ( generator: DomGenerator, block: Block,
if (outro) {
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}`;

@ -1,7 +1,9 @@
import { Node } from '../../../../interfaces';
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.value.length !== 1 || attribute.value[0].type !== 'Text') {

@ -4,7 +4,24 @@ const lookup = {
'accept-charset': { propertyName: 'acceptCharset', appliesTo: ['form'] },
accesskey: { propertyName: 'accessKey' },
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'] },
alt: { appliesTo: ['applet', 'area', 'img', 'input'] },
async: { appliesTo: ['script'] },
@ -12,7 +29,21 @@ const lookup = {
autofocus: { appliesTo: ['button', 'input', 'keygen', 'select', 'textarea'] },
autoplay: { appliesTo: ['audio', 'video'] },
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'] },
buffered: { appliesTo: ['audio', 'video'] },
challenge: { appliesTo: ['keygen'] },
@ -36,16 +67,44 @@ const lookup = {
defer: { appliesTo: ['script'] },
dir: {},
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'] },
draggable: {},
dropzone: {},
enctype: { appliesTo: ['form'] },
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'] },
headers: { appliesTo: ['td', 'th'] },
height: { appliesTo: [ 'canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video' ] },
height: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video']
},
hidden: {},
high: { appliesTo: ['meter'] },
href: { appliesTo: ['a', 'area', 'base', 'link'] },
@ -70,7 +129,23 @@ const lookup = {
min: { appliesTo: ['input', 'meter'] },
multiple: { appliesTo: ['input', 'select'] },
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'] },
open: { appliesTo: ['details'] },
optimum: { appliesTo: ['meter'] },
@ -96,7 +171,19 @@ const lookup = {
sizes: { appliesTo: ['link', 'img', 'source'] },
span: { appliesTo: ['col', 'colgroup'] },
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'] },
srclang: { appliesTo: ['track'] },
srcset: { appliesTo: ['img'] },
@ -107,10 +194,36 @@ const lookup = {
tabindex: { propertyName: 'tabIndex' },
target: { appliesTo: ['a', 'area', 'base', 'form'] },
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'] },
value: { appliesTo: [ 'button', 'option', 'input', 'li', 'meter', 'progress', 'param', 'select', 'textarea' ] },
width: { appliesTo: [ 'canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video' ] },
value: {
appliesTo: [
'button',
'option',
'input',
'li',
'meter',
'progress',
'param',
'select',
'textarea'
]
},
width: {
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video']
},
wrap: { appliesTo: ['textarea'] }
};

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

@ -6,21 +6,30 @@ import { Node } from '../../../interfaces';
import { State } from '../interfaces';
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) {
return branch.block && !branch.condition;
}
function getBranches ( generator: DomGenerator, block: Block, state: State, node: Node ) {
const branches = [{
function getBranches(
generator: DomGenerator,
block: Block,
state: State,
node: Node
) {
const branches = [
{
condition: block.contextualise(node.expression).snippet,
block: node._block.name,
hasUpdateMethod: node._block.hasUpdateMethod,
hasIntroMethod: node._block.hasIntroMethod,
hasOutroMethod: node._block.hasOutroMethod
}];
}
];
visitChildren(generator, block, state, node);
@ -45,15 +54,27 @@ function getBranches ( generator: DomGenerator, block: Block, state: State, node
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) => {
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 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 branches = getBranches(generator, block, state, node);
@ -68,7 +89,15 @@ export default function visitIfBlock ( generator: DomGenerator, block: Block, st
if (node.else) {
if (hasOutros) {
compoundWithOutros( generator, block, state, node, branches, dynamic, vars );
compoundWithOutros(
generator,
block,
state,
node,
branches,
dynamic,
vars
);
} else {
compound(generator, block, state, node, branches, dynamic, vars);
}
@ -77,13 +106,26 @@ export default function visitIfBlock ( generator: DomGenerator, block: Block, st
}
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) {
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`
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';
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 {
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 enter = dynamic ?
( branch.hasIntroMethod ?
deindent`
const enter = dynamic
? branch.hasIntroMethod
? deindent`
if ( ${name} ) {
${name}.update( changed, ${params} );
} else {
@ -109,38 +155,38 @@ function simple ( generator: DomGenerator, block: Block, state: State, node: Nod
}
${name}.intro( ${parentNode}, ${anchor} );
` :
deindent`
`
: deindent`
if ( ${name} ) {
${name}.update( changed, ${params} );
} else {
${name} = ${branch.block}( ${params}, ${block.component} );
${name}.mount( ${parentNode}, ${anchor} );
}
` ) :
( branch.hasIntroMethod ?
deindent`
`
: branch.hasIntroMethod
? deindent`
if ( !${name} ) ${name} = ${branch.block}( ${params}, ${block.component} );
${name}.intro( ${parentNode}, ${anchor} );
` :
deindent`
`
: deindent`
if ( !${name} ) {
${name} = ${branch.block}( ${params}, ${block.component} );
${name}.mount( ${parentNode}, ${anchor} );
}
` );
`;
// no `update()` here — we don't want to update outroing nodes,
// as that will typically result in glitching
const exit = branch.hasOutroMethod ?
deindent`
const exit = branch.hasOutroMethod
? deindent`
${name}.outro( function () {
${name}.unmount();
${name}.destroy();
${name} = null;
});
` :
deindent`
`
: deindent`
${name}.unmount();
${name}.destroy();
${name} = null;
@ -154,25 +200,31 @@ function simple ( generator: DomGenerator, block: Block, state: State, node: Nod
}
`);
block.builders.unmount.addLine(
`${if_name}${name}.unmount();`
);
block.builders.unmount.addLine(`${if_name}${name}.unmount();`);
block.builders.destroy.addLine(
`${if_name}${name}.destroy();`
);
block.builders.destroy.addLine(`${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 current_block = block.getUniqueName(`current_block`);
const current_block_and = hasElse ? '' : `${current_block} && `;
block.builders.create.addBlock(deindent`
function ${get_block} ( ${params} ) {
${branches.map( ({ condition, block }) => {
${branches
.map(({ condition, block }) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`;
} ).join( '\n' )}
})
.join('\n')}
}
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';
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 {
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`;
@ -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
// (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 current_block_index = block.getUniqueName(`current_block_index`);
const previous_block_index = block.getUniqueName(`previous_block_index`);
const if_block_creators = block.getUniqueName(`if_block_creators`);
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(name);
@ -245,9 +311,13 @@ function compoundWithOutros ( generator: DomGenerator, block: Block, state: Stat
var ${if_blocks} = [];
function ${get_block} ( ${params} ) {
${branches.map( ({ condition, block }, i ) => {
return `${condition ? `if ( ${condition} ) ` : ''}return ${block ? i : -1};`;
} ).join( '\n' )}
${branches
.map(({ condition, block }, i) => {
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';
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 {
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`;
@ -288,13 +362,13 @@ function compoundWithOutros ( generator: DomGenerator, block: Block, state: Stat
${name}.${mountOrIntro}( ${parentNode}, ${anchor} );
`;
const changeBlock = hasElse ?
deindent`
const changeBlock = hasElse
? deindent`
${destroyOldBlock}
${createNewBlock}
` :
deindent`
`
: deindent`
if ( ${name} ) {
${destroyOldBlock}
}

@ -4,14 +4,24 @@ import Block from '../Block';
import { Node } 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 value = block.getUniqueName(`${name}_value`);
const { snippet } = block.contextualise(node.expression);
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`
if ( ${value} !== ( ${value} = ${snippet} ) ) {

@ -4,7 +4,12 @@ import Block from '../Block';
import { Node } 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 before = node._state.name;
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
// exists for `Element`s.
block.addElement( before, `${generator.helper( 'createElement' )}( 'noscript' )`, state.parentNode, true );
block.addElement( after, `${generator.helper( 'createElement' )}( 'noscript' )`, state.parentNode, true );
block.addElement(
before,
`${generator.helper('createElement')}( 'noscript' )`,
state.parentNode,
true
);
block.addElement(
after,
`${generator.helper('createElement')}( 'noscript' )`,
state.parentNode,
true
);
const isToplevel = !state.parentNode;
block.builders.create.addLine(`var ${value} = ${snippet};`);
const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${value} );`;
const detachStatement = `${generator.helper( 'detachBetween' )}( ${before}, ${after} );`;
const detachStatement = `${generator.helper(
'detachBetween'
)}( ${before}, ${after} );`;
if (isToplevel) {
block.builders.mount.addLine(mountStatement);

@ -3,7 +3,17 @@ import Block from '../Block';
import { Node } 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;
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 { 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;
(state.parentNode ? block.builders.create : block.builders.mount).addLine(

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

@ -20,7 +20,8 @@ export default class Block {
}
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})`)
);
@ -42,6 +43,11 @@ export default class Block {
}
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;
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);
this.bindings = [];
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 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}.data = function () {
return ${templateProperties.data ? `${generator.alias( 'template' )}.data()` : `{}`};
return ${templateProperties.data
? `${generator.alias('template')}.data()`
: `{}`};
};
${name}.render = function ( state, options ) {
${templateProperties.data ? `state = Object.assign( ${generator.alias( 'template' )}.data(), state || {} );` : `state = state || {};`}
${computations.map( ({ key, deps }) =>
`state.${key} = ${generator.alias( 'template' )}.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );`
${templateProperties.data
? `state = Object.assign( ${generator.alias(
'template'
)}.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 tmp;
@ -76,7 +95,8 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp
${name}.renderCss = function () {
var components = [];
${generator.css && deindent`
${generator.css &&
deindent`
components.push({
filename: ${name}.filename,
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 = {};
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 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 {

@ -3,7 +3,11 @@ import { SsrGenerator } from './index';
import Block from './Block';
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];
visitor(generator, block, node);
}

@ -4,7 +4,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block';
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) {
if (chunk.type === 'Text') return chunk.data;
if (chunk.type === 'MustacheTag') {
@ -46,14 +50,19 @@ export default function visitComponent ( generator: SsrGenerator, block: Block,
return `${attribute.name}: ${value}`;
})
.concat( bindings.map( binding => {
.concat(
bindings.map(binding => {
const { name, keypath } = flattenReference(binding.value);
const value = block.contexts.has(name) ? keypath : `state.${keypath}`;
return `${binding.name}: ${value}`;
}))
})
)
.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 => {
block.addBinding(binding, expression);

@ -3,10 +3,16 @@ import { SsrGenerator } from '../index';
import Block from '../Block';
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 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);
// TODO should this be the generator's job? It's duplicated between

@ -11,17 +11,23 @@ const meta = {
};
function stringifyAttributeValue(block: Block, chunks: Node[]) {
return chunks.map( ( chunk: Node ) => {
return chunks
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return chunk.data;
}
const { snippet } = block.contextualise(chunk.expression);
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) {
return meta[node.name](generator, block, node);
}

@ -3,7 +3,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block';
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);
generator.append('${ ' + snippet + ' ? `');

@ -2,7 +2,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block';
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);
generator.append('${__escape( ' + snippet + ' )}');
}

@ -2,7 +2,11 @@ import { SsrGenerator } from '../index';
import Block from '../Block';
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);
generator.append('${' + snippet + '}');
}

@ -2,6 +2,10 @@ import { SsrGenerator } from '../index';
import Block from '../Block';
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, '\\${'));
}

@ -3,7 +3,11 @@ import { Parsed, Node } from '../../interfaces';
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 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);
}
else {
} else {
let shouldTransform = true;
let c = selector.start;
@ -114,7 +116,10 @@ export default function processCss ( parsed: Parsed, code: MagicString, cascade:
function walk(node: Node) {
if (node.type === 'Rule') {
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
} else if (node.children) {
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
let match;
while ( match = commentsPattern.exec( css ) ) {
while ((match = commentsPattern.exec(css))) {
const start = match.index + offset;
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;
@ -11,17 +11,19 @@ export default function getGlobals ( imports: Declaration[], options: Options )
if (!name) {
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) {
onerror(error);
} else {
throw error;
}
}
else {
} else {
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) {

@ -1,7 +1,7 @@
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 {
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 === 'amd') return getAmdIntro(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[]) {
const sourceString = imports.length ?
`[ ${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ' )} ], ` :
'';
const sourceString = imports.length
? `[ ${imports
.map(declaration => `'${removeExtension(declaration.source.value)}'`)
.join(', ')} ], `
: '';
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[]) {
const requireBlock = imports
.map( declaration => `var ${declaration.name} = require( '${declaration.source.value}' );` )
.map(
declaration =>
`var ${declaration.name} = require( '${declaration.source.value}' );`
)
.join('\n\n');
if (requireBlock) {
@ -58,7 +69,9 @@ function getIifeIntro ( options: Options, imports: Declaration[] ) {
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[]) {
@ -68,16 +81,24 @@ function getUmdIntro ( options: Options, imports: Declaration[] ) {
const amdId = options.amd && options.amd.id ? `'${options.amd.id}', ` : '';
const amdDeps = imports.length ? `[${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ')}], ` : '';
const cjsDeps = imports.map( declaration => `require('${declaration.source.value}')` ).join( ', ' );
const amdDeps = imports.length
? `[${imports
.map(declaration => `'${removeExtension(declaration.source.value)}'`)
.join(', ')}], `
: '';
const cjsDeps = imports
.map(declaration => `require('${declaration.source.value}')`)
.join(', ');
const globalDeps = getGlobals(imports, options);
return deindent`
return (
deindent`
(function ( global, factory ) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(${cjsDeps}) :
typeof define === 'function' && define.amd ? define(${amdId}${amdDeps}factory) :
(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[]) {

@ -1,6 +1,11 @@
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') {
return `export default ${name};`;
}

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

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

@ -5,17 +5,22 @@ import { trimStart, trimEnd } from '../utils/trim';
import getCodeFrame from '../utils/getCodeFrame';
import hash from './utils/hash';
import { Node, Parsed } from '../interfaces';
import CompileError from '../utils/CompileError'
import CompileError from '../utils/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);
this.name = 'ParseError';
}
}
interface ParserOptions {
filename?: string
filename?: string;
}
export class Parser {
@ -28,7 +33,7 @@ export class Parser {
html: Node;
css: Node;
js: Node;
metaTags: {}
metaTags: {};
constructor(template: string, options: ParserOptions) {
if (typeof template !== 'string') {
@ -135,7 +140,10 @@ export class Parser {
}
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++;
}
}
@ -150,7 +158,8 @@ export class Parser {
}
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 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);
return {

@ -24,13 +24,9 @@ function readExpression ( parser: Parser, start: number, quoteMark ) {
} else {
str += char;
}
}
else if ( /\s/.test( char ) ) {
} else if (/\s/.test(char)) {
break;
}
else {
} else {
str += char;
}
}
@ -44,12 +40,12 @@ function readExpression ( parser: Parser, start: number, quoteMark ) {
return expression;
}
export function readEventHandlerDirective ( parser: Parser, start: number, name: string ) {
const quoteMark = (
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);
export function readEventHandlerDirective(
parser: Parser,
start: number,
name: string
) {
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
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;
if (parser.eat('=')) {
const quoteMark = (
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
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;
if (parser.eat('=')) {
const quoteMark = (
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
const expressionStart = parser.index;

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

@ -2,7 +2,7 @@ import { parse } from 'acorn';
import spaces from '../../utils/spaces.js';
import { Parser } from '../index';
const scriptClosingTag = '<\/script>';
const scriptClosingTag = '</script>';
export default function readScript(parser: Parser, start: number, attributes) {
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`);
const source = spaces( scriptStart ) + parser.template.slice( scriptStart, scriptEnd );
const source =
spaces(scriptStart) + parser.template.slice(scriptStart, scriptEnd);
parser.index = scriptEnd + scriptClosingTag.length;
let ast;

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

@ -1,7 +1,11 @@
import readExpression from '../read/expression';
import readScript from '../read/script';
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 { decodeCharacterReferences } from '../utils/html';
import isVoidElementName from '../../utils/isVoidElementName';
@ -17,14 +21,20 @@ const metaTags = {
};
const specials = new Map([
[ 'script', {
[
'script',
{
read: readScript,
property: 'js'
} ],
[ 'style', {
}
],
[
'style',
{
read: readStyle,
property: 'css'
} ]
}
]
]);
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
@ -32,7 +42,14 @@ const disallowedContents = new Map( [
['li', new Set(['li'])],
['dt', 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'])],
['rp', new Set(['rt', 'rp'])],
['optgroup', new Set(['optgroup'])],
@ -42,7 +59,7 @@ const disallowedContents = new Map( [
['tfoot', new Set(['tbody'])],
['tr', new Set(['tr', 'tbody'])],
['td', new Set(['td', 'th', 'tr'])],
[ 'th', new Set( [ 'td', 'th', 'tr' ] ) ],
['th', new Set(['td', 'th', 'tr'])]
]);
function stripWhitespace(element) {
@ -88,7 +105,10 @@ export default function tag ( parser: Parser ) {
if (name in metaTags) {
if (name in parser.metaTags) {
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);
@ -105,14 +125,21 @@ export default function tag ( parser: Parser ) {
if (isClosingTag) {
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 '>'`);
// close any elements that don't have their own closing tags, e.g. <div><p></div>
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;
parser.stack.pop();
@ -142,7 +169,7 @@ export default function tag ( parser: Parser ) {
const uniqueNames = new Set();
let attribute;
while ( attribute = readAttribute( parser, uniqueNames ) ) {
while ((attribute = readAttribute(parser, uniqueNames))) {
attributes.push(attribute);
parser.allowWhitespace();
}
@ -155,7 +182,9 @@ export default function tag ( parser: Parser ) {
if (parser[special.property]) {
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);
@ -182,7 +211,11 @@ export default function tag ( parser: Parser ) {
element.end = parser.index;
} else if (name === 'textarea') {
// 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>/);
element.end = parser.index;
} else {
@ -211,7 +244,10 @@ function readTagName ( parser: Parser ) {
}
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;
@ -261,7 +297,12 @@ function readAttribute ( parser: Parser, uniqueNames ) {
const match = /^(in|out|transition):/.exec(name);
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;
@ -284,19 +325,15 @@ function readAttribute ( parser: Parser, uniqueNames ) {
}
function readAttributeValue(parser: Parser) {
const quoteMark = (
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
const regex = (
quoteMark === `'` ? /'/ :
quoteMark === `"` ? /"/ :
/[\s"'=<>\/`]/
);
const regex = quoteMark === `'`
? /'/
: quoteMark === `"` ? /"/ : /[\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;
return value;
@ -305,7 +342,8 @@ function readAttributeValue ( parser: Parser ) {
function getShorthandValue(start: number, name: string) {
const end = start + name.length;
return [{
return [
{
type: 'AttributeShorthand',
start,
end,
@ -315,7 +353,8 @@ function getShorthandValue ( start: number, name: string ) {
end,
name
}
}];
}
];
}
function readSequence(parser: Parser, done: () => boolean) {
@ -337,13 +376,12 @@ function readSequence ( parser: Parser, done: () => boolean ) {
if (currentChunk.data) chunks.push(currentChunk);
chunks.forEach(chunk => {
if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data );
if (chunk.type === 'Text')
chunk.data = decodeCharacterReferences(chunk.data);
});
return chunks;
}
else if ( parser.eat( '{{' ) ) {
} else if (parser.eat('{{')) {
if (currentChunk.data) {
currentChunk.end = index;
chunks.push(currentChunk);
@ -368,9 +406,7 @@ function readSequence ( parser: Parser, done: () => boolean ) {
type: 'Text',
data: ''
};
}
else {
} else {
currentChunk.data += parser.template[parser.index++];
}
}

@ -6,7 +6,11 @@ export default function text ( parser: Parser ) {
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++];
}

@ -1,7 +1,43 @@
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 entityPattern = new RegExp( `&(#?(?:x[\\w\\d]+|\\d+|${Object.keys( htmlEntities ).join( '|' )}));?`, 'g' );
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 entityPattern = new RegExp(
`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(htmlEntities).join('|')}));?`,
'g'
);
export function decodeCharacterReferences(html: string) {
return html.replace(entityPattern, (match, entity) => {

@ -19,17 +19,20 @@ fs.readdirSync( __dirname ).forEach( file => {
const declaration = node.declaration;
if (!declaration) return;
const name = declaration.type === 'VariableDeclaration' ?
declaration.declarations[0].id.name :
declaration.id.name;
const name = declaration.type === 'VariableDeclaration'
? declaration.declarations[0].id.name
: declaration.id.name;
const value = declaration.type === 'VariableDeclaration' ?
declaration.declarations[0].init :
declaration;
const value = declaration.type === 'VariableDeclaration'
? declaration.declarations[0].init
: declaration;
declarations[name] = source.slice(value.start, value.end);
});
});
fs.writeFileSync( 'src/generators/dom/shared.ts', `// this file is auto-generated, do not edit it
export default ${JSON.stringify( declarations, null, '\t' )};` );
fs.writeFileSync(
'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 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) {
@ -35,7 +35,8 @@ export function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -44,7 +45,9 @@ export function fire ( eventName, data ) {
}
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);
@ -65,8 +68,10 @@ export function observe ( key, callback, options ) {
export function observeDev(key, callback, options) {
var c = (key = '' + key).search(/[^\w]/);
if (c > -1) {
var message = "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 + "'";
var message =
'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);
}
@ -90,7 +95,9 @@ export function on ( eventName, handler ) {
export function onDev(eventName, handler) {
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);
}

@ -4,13 +4,22 @@ export function linear ( 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 keyframes = '@keyframes ' + id + '{\n';
for (var p = 0; p <= 1; p += 16.666 / duration) {
var t = a + delta * ease(p);
keyframes += ( p * 100 ) + '%{' + fn( t ) + '}\n';
keyframes += p * 100 + '%{' + fn(t) + '}\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);
node.style.animation = ( node.style.animation || '' ).split( ',' )
node.style.animation = (node.style.animation || '')
.split(',')
.filter(function(anim) {
// when introing, discard old animations if there are any
return anim && (delta < 0 || !/__svelte/.test(anim));
@ -79,7 +89,16 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
if (obj.css) {
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;

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

@ -4,15 +4,17 @@ enum ChunkType {
}
export default class CodeBuilder {
result: string
first: ChunkType
last: ChunkType
lastCondition: string
result: string;
first: ChunkType;
last: ChunkType;
lastCondition: string;
constructor(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.last = initial;

@ -2,12 +2,17 @@ import { locate } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame';
export default class CompileError extends Error {
frame: string
loc: { line: number, column: number }
pos: number
filename: string
frame: string;
loc: { line: number; column: number };
pos: number;
filename: string;
constructor ( message: string, template: string, index: number, filename: string ) {
constructor(
message: string,
template: string,
index: number,
filename: string
) {
super(message);
const { line, column } = locate(template, index);
@ -20,6 +25,7 @@ export default class CompileError extends Error {
}
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 4');
assert.equal( builder.toString(), deindent`
assert.equal(
builder.toString(),
deindent`
// line 1
// line 2
@ -80,7 +82,8 @@ describe( 'CodeBuilder', () => {
// line 3
// line 4
` );
`
);
});
it('nests codebuilders with correct indentation', () => {
@ -104,7 +107,9 @@ describe( 'CodeBuilder', () => {
builder.addLine('// line 3');
builder.addLine('// line 4');
assert.equal( builder.toString(), deindent`
assert.equal(
builder.toString(),
deindent`
// line 1
// line 2
@ -116,7 +121,8 @@ describe( 'CodeBuilder', () => {
// line 3
// line 4
` );
`
);
});
it('adds a line at start', () => {
@ -125,10 +131,13 @@ describe( 'CodeBuilder', () => {
builder.addLine('// second');
builder.addLineAtStart('// first');
assert.equal( builder.toString(), deindent`
assert.equal(
builder.toString(),
deindent`
// first
// second
` );
`
);
});
it('adds a line at start before a block', () => {
@ -137,11 +146,14 @@ describe( 'CodeBuilder', () => {
builder.addBlock('// second');
builder.addLineAtStart('// first');
assert.equal( builder.toString(), deindent`
assert.equal(
builder.toString(),
deindent`
// first
// second
` );
`
);
});
it('adds a block at start', () => {
@ -150,11 +162,14 @@ describe( 'CodeBuilder', () => {
builder.addLine('// second');
builder.addBlockAtStart('// first');
assert.equal( builder.toString(), deindent`
assert.equal(
builder.toString(),
deindent`
// first
// second
` );
`
);
});
it('adds a block at start before a block', () => {
@ -163,10 +178,13 @@ describe( 'CodeBuilder', () => {
builder.addBlock('// second');
builder.addBlockAtStart('// first');
assert.equal( builder.toString(), deindent`
assert.equal(
builder.toString(),
deindent`
// first
// second
` );
`
);
});
});

@ -19,17 +19,11 @@ export default function annotateWithScopes ( expression: Node ) {
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);
}
else if ( node.type === 'BlockStatement' ) {
} else if (node.type === 'BlockStatement') {
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);
}
},
@ -45,9 +39,9 @@ export default function annotateWithScopes ( expression: Node ) {
}
class Scope {
parent: Scope
block: boolean
declarations: Set<string>
parent: Scope;
block: boolean;
declarations: Set<string>;
constructor(parent: Scope, block: boolean) {
this.parent = parent;
@ -70,7 +64,9 @@ class Scope {
}
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 === '') {
const value = String( expression ).replace( /\n/g, `\n${trailingIndentation}` );
const value = String(expression).replace(
/\n/g,
`\n${trailingIndentation}`
);
result += value + string;
}
else {
} else {
let c = result.length;
while (/\s/.test(result[c - 1])) c -= 1;
result = result.slice(0, c) + string;

@ -12,7 +12,9 @@ export default function flatten ( node: Node ) {
}
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;

@ -4,7 +4,11 @@ function tabsToSpaces ( str: string ) {
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 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}`;
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}`;
}

@ -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;
// TODO is this right?
if ( parent.type === 'MemberExpression' || parent.type === 'MethodDefinition' ) {
if (
parent.type === 'MemberExpression' ||
parent.type === 'MethodDefinition'
) {
return parent.computed || node === parent.object;
}
// 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 () {...} }`
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 validNamespaces = [
'html', 'mathml', 'svg', 'xlink', 'xml', 'xmlns',
html, mathml, svg, xlink, xml, xmlns
'html',
'mathml',
'svg',
'xlink',
'xml',
'xmlns',
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
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 meta = new Map([
[ ':Window', validateWindow ]
]);
const meta = new Map([[':Window', validateWindow]]);
export default function validateHtml(validator: Validator, html: Node) {
let elementDepth = 0;
function visit(node: Node) {
if (node.type === 'Element') {
if ( 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 (
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)) {
@ -35,7 +40,10 @@ export default function validateHtml ( validator: Validator, html: Node ) {
c += 2;
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';
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 hasOutro: boolean;
@ -14,59 +15,94 @@ export default function validateElement ( validator: Validator, node: Node ) {
const { name } = attribute;
if (name === 'value') {
if ( node.name !== 'input' && node.name !== 'textarea' && node.name !== 'select' ) {
validator.error( `'value' is not a valid binding on <${node.name}> elements`, attribute.start );
}
}
else if ( name === 'checked' ) {
if (
node.name !== 'input' &&
node.name !== 'textarea' &&
node.name !== 'select'
) {
validator.error(
`'value' is not a valid binding on <${node.name}> elements`,
attribute.start
);
}
} else if (name === 'checked') {
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') {
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') {
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);
if (type !== 'checkbox' && type !== 'radio') {
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' ) {
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'
) {
if (node.name !== 'audio' && node.name !== 'video') {
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 if ( attribute.type === 'EventHandler' ) {
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 if (attribute.type === 'EventHandler') {
validateEventHandler(validator, attribute);
}
else if ( attribute.type === 'Transition' ) {
} else if (attribute.type === 'Transition') {
const bidi = attribute.intro && attribute.outro;
if (hasTransition) {
if ( bidi ) 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 (bidi)
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 ( bidi ) 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 (bidi)
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;
@ -74,14 +110,18 @@ export default function validateElement ( validator: Validator, node: Node ) {
if (bidi) hasTransition = true;
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 (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) {
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.value === true) {

@ -3,13 +3,12 @@ import list from '../utils/list';
import { Validator } from '../index';
import { Node } from '../../interfaces';
const validBuiltins = new Set([
'set',
'fire',
'destroy'
]);
const validBuiltins = new Set(['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;
if (type !== 'CallExpression') {
@ -19,15 +18,21 @@ export default function validateEventHandlerCallee ( validator: Validator, attri
const { name } = flattenReference(callee);
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.*' ]
.concat(
const validCallees = ['this.*', 'event.*'].concat(
Array.from(validBuiltins),
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)) {
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);
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
);
}
if (!~validBindings.indexOf(attribute.name)) {
const match = (
attribute.name === 'width' ? 'innerWidth' :
attribute.name === 'height' ? 'innerHeight' :
fuzzymatch( attribute.name, validBindings )
);
const match = attribute.name === 'width'
? 'innerWidth'
: attribute.name === 'height'
? 'innerHeight'
: fuzzymatch(attribute.name, validBindings);
const message = `'${attribute.name}' is not a valid binding on <:Window>`;
if (match) {
validator.error( `${message} (did you mean '${match}'?)`, attribute.start );
validator.error(
`${message} (did you mean '${match}'?)`,
attribute.start
);
} 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);
}
});

@ -2,11 +2,16 @@ import validateJs from './js/index';
import validateHtml from './html/index';
import { getLocator, Location } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame';
import CompileError from '../utils/CompileError'
import CompileError from '../utils/CompileError';
import { Node, Parsed, CompileOptions, Warning } from '../interfaces';
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);
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;
try {

@ -17,7 +17,10 @@ export default function validateJs ( validator: Validator, js: Node ) {
if (node.type === 'ExportDefaultDeclaration') {
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);
@ -31,11 +34,17 @@ export default function validateJs ( validator: Validator, js: Node ) {
// Remove these checks in version 2
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')) {
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
@ -47,11 +56,22 @@ export default function validateJs ( validator: Validator, js: Node ) {
} else {
const match = fuzzymatch(prop.key.name, validPropList);
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)) {
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 {
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) {
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;
}
@ -14,7 +17,10 @@ export default function components ( validator: Validator, prop: Node ) {
prop.value.properties.forEach((component: Node) => {
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)) {

@ -3,11 +3,17 @@ import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../';
import { Node } from '../../../interfaces';
const isFunctionExpression = new Set( [ 'FunctionExpression', 'ArrowFunctionExpression' ] );
const isFunctionExpression = new Set([
'FunctionExpression',
'ArrowFunctionExpression'
]);
export default function computed(validator: Validator, prop: Node) {
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;
}
@ -16,22 +22,34 @@ export default function computed ( validator: Validator, prop: Node ) {
prop.value.properties.forEach((computation: Node) => {
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;
}
const params = computation.value.params;
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;
}
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) {
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) {
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;
}

@ -6,7 +6,10 @@ import { Node } from '../../../interfaces';
export default function helpers(validator: Validator, prop: Node) {
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;
}
@ -23,20 +26,28 @@ export default function helpers ( validator: Validator, prop: Node ) {
enter(node: Node) {
if (/^Function/.test(node.type)) {
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
// 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 ) {
validator.error( `Cannot use this.get(...) — it must be passed into the helper function as an argument`, node.start );
if (
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') {
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' ) {
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') {
usesArguments = true;
}
}
@ -50,7 +61,10 @@ export default function helpers ( validator: Validator, prop: Node ) {
});
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) {
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;
}
@ -19,12 +22,19 @@ export default function methods ( validator: Validator, prop: Node ) {
prop.value.properties.forEach((prop: Node) => {
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 (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;
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)) {
const match = fuzzymatch(ns, namespaces.validNamespaces);
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 {
validator.error(`Invalid namespace '${ns}'`, prop.start);
}

@ -5,7 +5,10 @@ import { Node } from '../../../interfaces';
export default function oncreate(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') {
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) {
if (prop.value.type === 'ArrowFunctionExpression') {
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';
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);
}

@ -3,6 +3,9 @@ import { Validator } from '../../';
import { Node } from '../../../interfaces';
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);
}

@ -5,7 +5,10 @@ import { Node } from '../../../interfaces';
export default function transitions(validator: Validator, prop: Node) {
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;
}

@ -1,7 +1,11 @@
import { Validator } from '../../';
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 => {
if (prop.kind !== 'init') {
validator.error(`${label} cannot use getters and setters`, prop.start);

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

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

@ -7,7 +7,11 @@ export default function usesThisOrArguments ( node: Node ) {
walk(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();
}
@ -15,7 +19,11 @@ export default function usesThisOrArguments ( node: Node ) {
result = true;
}
if ( node.type === 'Identifier' && isReference( node, parent ) && node.name === 'arguments' ) {
if (
node.type === 'Identifier' &&
isReference(node, parent) &&
node.name === 'arguments'
) {
result = true;
}
}

@ -1,12 +1,19 @@
// adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js
// BSD Licensed
export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUpper) {
export default function FuzzySet(
arr,
useLevenshtein,
gramSizeLower,
gramSizeUpper
) {
// default options
arr = arr || [];
this.gramSizeLower = gramSizeLower || 2;
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
this.exactSet = {};
@ -41,9 +48,11 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
// return an edit distance from 0 to 1
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;
str1 = String(str1); str2 = String(str2);
str1 = String(str1);
str2 = String(str2);
const distance = levenshtein(str1, str2);
if (str1.length > str2.length) {
@ -109,7 +118,11 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
let results = [];
// 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);
if (results) {
return results;
@ -148,8 +161,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
function isEmptyObject(obj) {
for (const prop in obj) {
if ( obj.hasOwnProperty( prop ) )
return false;
if (obj.hasOwnProperty(prop)) return false;
}
return true;
}
@ -165,7 +177,10 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
// build a results list of [score, str]
for (const matchIndex in matches) {
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) {
if (a[0] < b[0]) {
@ -183,7 +198,10 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
const endIndex = Math.min(50, results.length);
// truncate somewhat arbitrarily to 50
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.sort(sortDescending);
@ -236,7 +254,8 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
};
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();
};
@ -275,7 +294,6 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp
return values;
};
// initialization
let i = this.gramSizeLower;
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 matches = set.get(name);
return matches && matches[0] && matches[0][0] > 0.7 ?
matches[0][1] :
null;
return matches && matches[0] && matches[0][0] > 0.7 ? matches[0][1] : null;
}

@ -1,4 +1,6 @@
export default function list(items: string[], conjunction = 'or') {
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 assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function setAttribute ( node, attribute, value ) {
}
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) {
@ -66,7 +69,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,7 +1,10 @@
function noop() {}
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -11,7 +14,7 @@ function assign ( target ) {
}
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) {
@ -42,7 +45,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -51,7 +55,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,7 +1,10 @@
function noop() {}
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -44,7 +47,7 @@ function createText ( data ) {
}
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) {
@ -75,7 +78,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -84,7 +88,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,5 +1,8 @@
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -29,7 +32,7 @@ function createText ( data ) {
}
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) {
@ -60,7 +63,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -69,7 +73,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,7 +1,10 @@
function noop() {}
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function createComment () {
}
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) {
@ -66,7 +69,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,7 +1,10 @@
function noop() {}
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function createComment () {
}
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) {
@ -66,7 +69,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,7 +1,10 @@
import Imported from 'Imported.html';
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -23,7 +26,7 @@ function createText ( data ) {
}
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) {
@ -54,7 +57,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -63,7 +67,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,7 +1,10 @@
function noop() {}
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -11,7 +14,7 @@ function assign ( target ) {
}
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) {
@ -42,7 +45,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -51,7 +55,9 @@ function fire ( eventName, data ) {
}
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);

@ -1,7 +1,10 @@
function noop() {}
function assign(target) {
var k, source, i = 1, len = arguments.length;
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
@ -35,7 +38,7 @@ function createComment () {
}
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) {
@ -66,7 +69,8 @@ function get ( key ) {
}
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;
for (var i = 0; i < handlers.length; i += 1) {
@ -75,7 +79,9 @@ function fire ( eventName, data ) {
}
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);

@ -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