diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 6ee4171cf1..09b7e3409a 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -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; @@ -61,9 +66,9 @@ export default class Generator { // in dev mode this.expectedProperties = new Set(); - this.code = new MagicString( source ); + this.code = new MagicString(source); this.cascade = options.cascade !== false; // TODO remove this option in v2 - this.css = parsed.css ? processCss( parsed, this.code, this.cascade ) : null; + this.css = parsed.css ? processCss(parsed, this.code, this.cascade) : null; this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.usesRefs = false; @@ -71,105 +76,112 @@ export default class Generator { // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; this.importedNames = new Set(); this.aliases = new Map(); - this.usedNames = new Set( [ name ] ); + this.usedNames = new Set([name]); } - addSourcemapLocations ( node: Node ) { - walk( node, { - enter: ( node: Node ) => { - this.code.addSourcemapLocation( node.start ); - this.code.addSourcemapLocation( node.end ); + addSourcemapLocations(node: Node) { + walk(node, { + enter: (node: Node) => { + this.code.addSourcemapLocation(node.start); + this.code.addSourcemapLocation(node.end); } }); } - alias ( name: string ) { - if ( !this.aliases.has( name ) ) { - this.aliases.set( name, this.getUniqueName( name ) ); + alias(name: string) { + if (!this.aliases.has(name)) { + this.aliases.set(name, this.getUniqueName(name)); } - return this.aliases.get( name ); + return this.aliases.get(name); } - contextualise ( block: DomBlock | SsrBlock, expression: Node, context: string, isEventHandler: boolean ) { - this.addSourcemapLocations( expression ); + contextualise( + block: DomBlock | SsrBlock, + expression: Node, + context: string, + isEventHandler: boolean + ) { + this.addSourcemapLocations(expression); const usedContexts: string[] = []; const { code, helpers } = this; const { contexts, indexes } = block; - let scope = annotateWithScopes( expression ); // TODO this already happens in findDependencies + let scope = annotateWithScopes(expression); // TODO this already happens in findDependencies let lexicalDepth = 0; const self = this; - walk( expression, { - enter ( node: Node, parent: Node, key: string ) { - if ( /^Function/.test( node.type ) ) lexicalDepth += 1; + walk(expression, { + enter(node: Node, parent: Node, key: string) { + if (/^Function/.test(node.type)) lexicalDepth += 1; - if ( node._scope ) { + if (node._scope) { scope = node._scope; return; } - if ( node.type === 'ThisExpression' ) { - 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 ) { + if (node.type === 'ThisExpression') { + 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 ) ) { - const contextName = contexts.get( name ); - if ( contextName !== 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 ) ) { - code.prependRight( node.start, `${self.alias( 'template' )}.helpers.` ); - } - - else if ( indexes.has( name ) ) { - const context = indexes.get( name ); - if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); - } - - else { + if (!~usedContexts.indexOf(name)) usedContexts.push(name); + } else if (helpers.has(name)) { + code.prependRight(node.start, `${self.alias('template')}.helpers.`); + } else if (indexes.has(name)) { + const context = indexes.get(name); + if (!~usedContexts.indexOf(context)) usedContexts.push(context); + } else { // handle shorthand properties - if ( parent && parent.type === 'Property' && parent.shorthand ) { - if ( key === 'key' ) { - code.appendLeft( node.start, `${name}: ` ); + if (parent && parent.type === 'Property' && parent.shorthand) { + if (key === 'key') { + code.appendLeft(node.start, `${name}: `); return; } } - if ( globalWhitelist.has( name ) ) { - code.prependRight( node.start, `( '${name}' in state ? state.` ); - code.appendLeft( node.object ? node.object.end : node.end, ` : ${name} )` ); + if (globalWhitelist.has(name)) { + code.prependRight(node.start, `( '${name}' in state ? state.`); + code.appendLeft( + node.object ? node.object.end : node.end, + ` : ${name} )` + ); } else { - code.prependRight( node.start, `state.` ); + code.prependRight(node.start, `state.`); } - if ( !~usedContexts.indexOf( 'state' ) ) usedContexts.push( 'state' ); + if (!~usedContexts.indexOf('state')) usedContexts.push('state'); } this.skip(); } }, - leave ( node: Node ) { - if ( /^Function/.test( node.type ) ) lexicalDepth -= 1; - if ( node._scope ) scope = scope.parent; + leave(node: Node) { + if (/^Function/.test(node.type)) lexicalDepth -= 1; + if (node._scope) scope = scope.parent; } }); @@ -180,112 +192,133 @@ export default class Generator { }; } - findDependencies ( contextDependencies: Map<string, string[]>, indexes: Map<string, string>, expression: Node ) { - if ( expression._dependencies ) return expression._dependencies; + findDependencies( + contextDependencies: Map<string, string[]>, + indexes: Map<string, string>, + expression: Node + ) { + if (expression._dependencies) return expression._dependencies; - let scope = annotateWithScopes( expression ); + let scope = annotateWithScopes(expression); const dependencies: string[] = []; const generator = this; // can't use arrow functions, because of this.skip() - walk( expression, { - enter ( node: Node, parent: Node ) { - if ( node._scope ) { + walk(expression, { + enter(node: Node, parent: Node) { + if (node._scope) { scope = node._scope; return; } - if ( isReference( node, parent ) ) { - const { name } = flattenReference( node ); - if ( scope.has( name ) || generator.helpers.has( name ) ) return; + if (isReference(node, parent)) { + const { name } = flattenReference(node); + if (scope.has(name) || generator.helpers.has(name)) return; - if ( contextDependencies.has( name ) ) { - dependencies.push( ...contextDependencies.get( name ) ); - } else if ( !indexes.has( name ) ) { - dependencies.push( name ); + if (contextDependencies.has(name)) { + dependencies.push(...contextDependencies.get(name)); + } else if (!indexes.has(name)) { + dependencies.push(name); } this.skip(); } }, - leave ( node: Node ) { - if ( node._scope ) scope = scope.parent; + leave(node: Node) { + if (node._scope) scope = scope.parent; } }); - dependencies.forEach( name => { - if ( !globalWhitelist.has( name ) ) { - this.expectedProperties.add( name ); + dependencies.forEach(name => { + if (!globalWhitelist.has(name)) { + this.expectedProperties.add(name); } }); - return ( expression._dependencies = dependencies ); + return (expression._dependencies = dependencies); } - generate ( result, options, { name, format } ) { - if ( this.imports.length ) { + generate(result, options, { name, format }) { + if (this.imports.length) { const statements: string[] = []; - this.imports.forEach( ( declaration, i ) => { - if ( format === 'es' ) { - statements.push( this.source.slice( declaration.start, declaration.end ) ); + this.imports.forEach((declaration, i) => { + if (format === 'es') { + 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}` ); + namedImports.forEach((specifier: Node) => { + statements.push( + `var ${specifier.local.name} = ${name}.${specifier.imported.name}` + ); }); - if ( defaultImport ) { - statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` ); + if (defaultImport) { + statements.push( + `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` + ); } }); - result = `${statements.join( '\n' )}\n\n${result}`; + result = `${statements.join('\n')}\n\n${result}`; } const pattern = /\[✂(\d+)-(\d+)$/; - const parts = result.split( '✂]' ); + const parts = result.split('✂]'); const finalChunk = parts.pop(); const compiled = new Bundle({ separator: '' }); - function addString ( str: string ) { + function addString(str: string) { compiled.addSource({ - content: new MagicString( str ) + content: new MagicString(str) }); } - const intro = getIntro( format, options, this.imports ); - if ( intro ) addString( intro ); + const intro = getIntro(format, options, this.imports); + if (intro) addString(intro); const { filename } = options; // special case — the source file doesn't actually get used anywhere. we need // to add an empty file to populate map.sources and map.sourcesContent - if ( !parts.length ) { + if (!parts.length) { compiled.addSource({ filename, - content: new MagicString( this.source ).remove( 0, this.source.length ) + content: new MagicString(this.source).remove(0, this.source.length) }); } - parts.forEach( ( str: string ) => { - const chunk = str.replace( pattern, '' ); - if ( chunk ) addString( chunk ); + parts.forEach((str: string) => { + const chunk = str.replace(pattern, ''); + if (chunk) addString(chunk); - const match = pattern.exec( str ); + const match = pattern.exec(str); - const snippet = this.code.snip( +match[1], +match[2] ); + const snippet = this.code.snip(+match[1], +match[2]); compiled.addSource({ filename, @@ -293,36 +326,52 @@ export default class Generator { }); }); - addString( finalChunk ); - addString( '\n\n' + getOutro( format, name, options, this.imports ) ); + addString(finalChunk); + addString('\n\n' + getOutro(format, name, options, this.imports)); return { code: compiled.toString(), - map: compiled.generateMap({ includeContent: true, file: options.outputFilename }), + map: compiled.generateMap({ + includeContent: true, + file: options.outputFilename + }), css: this.css }; } - getUniqueName ( name: string ) { - if ( test ) name = `${name}$`; + 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++}` ); - this.usedNames.add( alias ); + for ( + let i = 1; + reservedNames.has(alias) || + this.importedNames.has(alias) || + this.usedNames.has(alias); + alias = `${name}_${i++}` + ); + this.usedNames.add(alias); return alias; } - getUniqueNameMaker ( params ) { - const localUsedNames = new Set( params ); + getUniqueNameMaker(params) { + const localUsedNames = new Set(params); return name => { - if ( test ) name = `${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++}` ); - localUsedNames.add( alias ); + 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; }; } - parseJs ( ssr: boolean = false ) { + parseJs(ssr: boolean = false) { const { source } = this; const { js } = this.parsed; @@ -333,154 +382,213 @@ export default class Generator { let namespace = null; let hasJs = !!js; - if ( js ) { - this.addSourcemapLocations( js.content ); + if (js) { + this.addSourcemapLocations(js.content); const body = js.content.body.slice(); // slice, because we're going to be mutating the original // imports need to be hoisted out of the IIFE - for ( let i = 0; i < body.length; i += 1 ) { + for (let i = 0; i < body.length; i += 1) { const node = body[i]; - if ( node.type === 'ImportDeclaration' ) { - removeNode( this.code, js.content, node ); - imports.push( node ); + if (node.type === 'ImportDeclaration') { + removeNode(this.code, js.content, node); + imports.push(node); - node.specifiers.forEach( ( specifier: Node ) => { - this.importedNames.add( specifier.local.name ); + node.specifiers.forEach((specifier: Node) => { + this.importedNames.add(specifier.local.name); }); } } - 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 ) => { - templateProperties[ prop.key.name ] = prop; + if (defaultExport) { + defaultExport.declaration.properties.forEach((prop: Node) => { + templateProperties[prop.key.name] = prop; }); } - [ 'helpers', 'events', 'components', 'transitions' ].forEach( key => { - if ( templateProperties[ key ] ) { - templateProperties[ key ].value.properties.forEach( ( prop: node ) => { - this[ key ].add( prop.key.name ); + ['helpers', 'events', 'components', 'transitions'].forEach(key => { + if (templateProperties[key]) { + templateProperties[key].value.properties.forEach((prop: node) => { + this[key].add(prop.key.name); }); } }); - if ( templateProperties.computed ) { + if (templateProperties.computed) { const dependencies = new Map(); - templateProperties.computed.value.properties.forEach( ( prop: Node ) => { + templateProperties.computed.value.properties.forEach((prop: Node) => { const key = prop.key.name; const value = prop.value; - const deps = value.params.map( ( param: Node ) => param.type === 'AssignmentPattern' ? param.left.name : param.name ); - dependencies.set( key, deps ); + const deps = value.params.map( + (param: Node) => + param.type === 'AssignmentPattern' ? param.left.name : param.name + ); + dependencies.set(key, deps); }); const visited = new Set(); - function visit ( key ) { - if ( !dependencies.has( key ) ) return; // not a computation + function visit(key) { + if (!dependencies.has(key)) return; // not a computation - if ( visited.has( key ) ) return; - visited.add( key ); + if (visited.has(key)) return; + visited.add(key); - const deps = dependencies.get( key ); - deps.forEach( visit ); + const deps = dependencies.get(key); + deps.forEach(visit); computations.push({ key, deps }); } - templateProperties.computed.value.properties.forEach( ( prop: Node ) => visit( prop.key.name ) ); + templateProperties.computed.value.properties.forEach((prop: Node) => + visit(prop.key.name) + ); } - if ( templateProperties.namespace ) { + if (templateProperties.namespace) { const ns = templateProperties.namespace.value.value; - namespace = namespaces[ ns ] || ns; + namespace = namespaces[ns] || ns; - removeObjectKey( this.code, defaultExport.declaration, 'namespace' ); + removeObjectKey(this.code, defaultExport.declaration, 'namespace'); } - if ( templateProperties.components ) { + if (templateProperties.components) { let hasNonImportedComponent = false; - templateProperties.components.value.properties.forEach( ( property: Node ) => { - const key = property.key.name; - const value = source.slice( property.value.start, property.value.end ); - if ( this.importedNames.has( value ) ) { - this.importedComponents.set( key, value ); - } else { - hasNonImportedComponent = true; + templateProperties.components.value.properties.forEach( + (property: Node) => { + const key = property.key.name; + 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 ) { + ); + 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 ); + Array.from(this.importedComponents.keys()).forEach(key => { + removeObjectKey( + this.code, + templateProperties.components.value, + key + ); }); } else { // remove the entire components portion of the export - removeObjectKey( this.code, defaultExport.declaration, 'components' ); + removeObjectKey(this.code, defaultExport.declaration, 'components'); } } // Remove these after version 2 - if ( templateProperties.onrender ) { + 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 ) { + 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 (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 + ); } // now that we've analysed the default export, we can determine whether or not we need to keep it let hasDefaultExport = !!defaultExport; - if ( defaultExport && defaultExport.declaration.properties.length === 0 ) { + if (defaultExport && defaultExport.declaration.properties.length === 0) { hasDefaultExport = false; - removeNode( this.code, js.content, defaultExport ); + removeNode(this.code, js.content, defaultExport); } // if we do need to keep it, then we need to generate a return statement - if ( hasDefaultExport ) { - const finalNode = body[ body.length - 1 ]; - if ( defaultExport === finalNode ) { + if (hasDefaultExport) { + 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 ); + 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--; + while (/\s/.test(source[i - 1])) i--; - const indentation = source.slice( i, defaultExport.start ); - this.code.appendLeft( finalNode.end, `\n\n${indentation}return ${template};` ); + const indentation = source.slice(i, defaultExport.start); + 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, '}());' ); - } - - // if there's no need to include user code, remove it altogether - else { - this.code.remove( js.content.start, js.content.end ); + 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, '}());'); + } else { + // if there's no need to include user code, remove it altogether + this.code.remove(js.content.start, js.content.end); hasJs = false; } } diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index 0131ca1e12..f99c1d52c9 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -48,7 +48,7 @@ export default class Block { unmount: CodeBuilder; detachRaw: CodeBuilder; destroy: CodeBuilder; - } + }; hasIntroMethod: boolean; hasOutroMethod: boolean; @@ -64,7 +64,7 @@ export default class Block { hasUpdateMethod: boolean; autofocus: string; - constructor ( options: BlockOptions ) { + constructor(options: BlockOptions) { this.generator = options.generator; this.name = options.name; this.expression = options.expression; @@ -102,144 +102,169 @@ export default class Block { this.aliases = new Map(); this.variables = new Map(); - this.getUniqueName = this.generator.getUniqueNameMaker( options.params ); + this.getUniqueName = this.generator.getUniqueNameMaker(options.params); // unique names - this.component = this.getUniqueName( 'component' ); - this.target = this.getUniqueName( 'target' ); + this.component = this.getUniqueName('component'); + this.target = this.getUniqueName('target'); this.hasUpdateMethod = false; // determined later } - addDependencies ( dependencies ) { - dependencies.forEach( dependency => { - this.dependencies.add( dependency ); + addDependencies(dependencies) { + dependencies.forEach(dependency => { + this.dependencies.add(dependency); }); } - 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};` - ); + if (needsIdentifier || isToplevel) { + this.builders.create.addLine(`var ${name} = ${renderStatement};`); - this.mount( name, parentNode ); + 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} );` ); + if (isToplevel) { + 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` ); + 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` + ); } - this.variables.set( name, init ); + this.variables.set(name, init); } - alias ( name: string ) { - if ( !this.aliases.has( name ) ) { - this.aliases.set( name, this.getUniqueName( name ) ); + alias(name: string) { + if (!this.aliases.has(name)) { + this.aliases.set(name, this.getUniqueName(name)); } - return this.aliases.get( name ); + return this.aliases.get(name); } - child ( options: BlockOptions ) { - return new Block( Object.assign( {}, this, options, { parent: this } ) ); + child(options: BlockOptions) { + return new Block(Object.assign({}, this, options, { parent: this })); } - contextualise ( expression: Node, context?: string, isEventHandler?: boolean ) { - return this.generator.contextualise( this, expression, context, isEventHandler ); + contextualise(expression: Node, context?: string, isEventHandler?: boolean) { + return this.generator.contextualise( + this, + expression, + context, + isEventHandler + ); } - findDependencies ( expression ) { - return this.generator.findDependencies( this.contextDependencies, this.indexes, expression ); + findDependencies(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} );` ); + mount(name: string, parentNode: string) { + if (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 );` + ); } } - render () { + render() { let introing; const hasIntros = !this.builders.intro.isEmpty(); - if ( hasIntros ) { - introing = this.getUniqueName( 'introing' ); - this.addVariable( introing ); + if (hasIntros) { + introing = this.getUniqueName('introing'); + this.addVariable(introing); } let outroing; const hasOutros = !this.builders.outro.isEmpty(); - if ( hasOutros ) { - outroing = this.getUniqueName( 'outroing' ); - this.addVariable( outroing ); + if (hasOutros) { + outroing = this.getUniqueName('outroing'); + this.addVariable(outroing); } - if ( this.variables.size ) { - const variables = Array.from( this.variables.keys() ) - .map( key => { - const init = this.variables.get( key ); + if (this.variables.size) { + const variables = Array.from(this.variables.keys()) + .map(key => { + const init = this.variables.get(key); return init !== undefined ? `${key} = ${init}` : key; }) - .join( ', ' ); + .join(', '); - this.builders.create.addBlockAtStart( `var ${variables};` ); + this.builders.create.addBlockAtStart(`var ${variables};`); } - if ( this.autofocus ) { - this.builders.create.addLine( `${this.autofocus}.focus();` ); + if (this.autofocus) { + this.builders.create.addLine(`${this.autofocus}.focus();`); } // minor hack – we need to ensure that any {{{triples}}} are detached first - this.builders.unmount.addBlockAtStart( this.builders.detachRaw ); + this.builders.unmount.addBlockAtStart(this.builders.detachRaw); const properties = new CodeBuilder(); let localKey; - if ( this.key ) { - localKey = this.getUniqueName( 'key' ); - properties.addBlock( `key: ${localKey},` ); + if (this.key) { + localKey = this.getUniqueName('key'); + properties.addBlock(`key: ${localKey},`); } - if ( this.first ) { - properties.addBlock( `first: ${this.first},` ); + if (this.first) { + properties.addBlock(`first: ${this.first},`); } - if ( this.builders.mount.isEmpty() ) { - properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` ); + if (this.builders.mount.isEmpty()) { + properties.addBlock(`mount: ${this.generator.helper('noop')},`); } else { - properties.addBlock( deindent` + properties.addBlock(deindent` mount: function ( ${this.target}, anchor ) { ${this.builders.mount} }, - ` ); + `); } - if ( this.hasUpdateMethod ) { - if ( this.builders.update.isEmpty() ) { - properties.addBlock( `update: ${this.generator.helper( 'noop' )},` ); + if (this.hasUpdateMethod) { + if (this.builders.update.isEmpty()) { + properties.addBlock(`update: ${this.generator.helper('noop')},`); } else { - properties.addBlock( deindent` - update: function ( changed, ${this.params.join( ', ' )} ) { + properties.addBlock(deindent` + update: function ( changed, ${this.params.join(', ')} ) { ${this.builders.update} }, - ` ); + `); } } - if ( this.hasIntroMethod ) { - if ( hasIntros ) { - properties.addBlock( deindent` + if (this.hasIntroMethod) { + if (hasIntros) { + properties.addBlock(deindent` intro: function ( ${this.target}, anchor ) { if ( ${introing} ) return; ${introing} = true; @@ -249,60 +274,63 @@ export default class Block { this.mount( ${this.target}, anchor ); }, - ` ); + `); } else { - properties.addBlock( deindent` + properties.addBlock(deindent` intro: function ( ${this.target}, anchor ) { this.mount( ${this.target}, anchor ); }, - ` ); + `); } } - if ( this.hasOutroMethod ) { - if ( hasOutros ) { - properties.addBlock( deindent` - outro: function ( ${this.alias( 'outrocallback' )} ) { + if (this.hasOutroMethod) { + if (hasOutros) { + properties.addBlock(deindent` + outro: function ( ${this.alias('outrocallback')} ) { if ( ${outroing} ) return; ${outroing} = true; ${hasIntros && `${introing} = false;`} - var ${this.alias( 'outros' )} = ${this.outros}; + var ${this.alias('outros')} = ${this.outros}; ${this.builders.outro} }, - ` ); + `); } else { - properties.addBlock( deindent` + properties.addBlock(deindent` outro: function ( outrocallback ) { outrocallback(); }, - ` ); + `); } } - if ( this.builders.unmount.isEmpty() ) { - properties.addBlock( `unmount: ${this.generator.helper('noop')},`); + if (this.builders.unmount.isEmpty()) { + properties.addBlock(`unmount: ${this.generator.helper('noop')},`); } else { - properties.addBlock( deindent` + properties.addBlock(deindent` unmount: function () { ${this.builders.unmount} }, - ` ); + `); } - if ( this.builders.destroy.isEmpty() ) { - properties.addBlock( `destroy: ${this.generator.helper( 'noop' )}` ); + if (this.builders.destroy.isEmpty()) { + properties.addBlock(`destroy: ${this.generator.helper('noop')}`); } else { - properties.addBlock( deindent` + properties.addBlock(deindent` destroy: function () { ${this.builders.destroy} } - ` ); + `); } 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 { @@ -311,4 +339,4 @@ export default class Block { } `; } -} \ No newline at end of file +} diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 341608d428..cfb3bacdca 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -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,8 +22,13 @@ export class DomGenerator extends Generator { hasOutroTransitions: boolean; hasComplexBindings: boolean; - constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) { - super( parsed, source, name, options ); + constructor( + parsed: Parsed, + source: string, + name: string, + options: CompileOptions + ) { + super(parsed, source, name, options); this.blocks = []; this.uses = new Set(); @@ -33,29 +38,38 @@ export class DomGenerator extends Generator { this.metaBindings = []; } - helper ( name: string ) { - if ( this.options.dev && `${name}Dev` in shared ) { + helper(name: string) { + if (this.options.dev && `${name}Dev` in shared) { name = `${name}Dev`; } - this.uses.add( name ); + this.uses.add(name); - return this.alias( name ); + return this.alias(name); } } -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 generator = new DomGenerator(parsed, source, name, options); - const { computations, hasJs, templateProperties, namespace } = generator.parseJs(); + const { + computations, + hasJs, + templateProperties, + namespace + } = generator.parseJs(); - const { block, state } = preprocess( generator, namespace, parsed.html ); + const { block, state } = preprocess(generator, namespace, parsed.html); - parsed.html.children.forEach( ( node: Node ) => { - visit( generator, block, state, node ); + parsed.html.children.forEach((node: Node) => { + visit(generator, block, state, node); }); const builders = { @@ -63,94 +77,139 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp _set: new CodeBuilder() }; - if ( computations.length ) { + if (computations.length) { const builder = new CodeBuilder(); - const differs = generator.helper( 'differs' ); + const differs = generator.helper('differs'); - computations.forEach( ({ key, deps }) => { - if ( generator.readonly.has( key ) ) { + 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 ); + 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 ); + builder.addConditionalLine(condition, statement); }); - builders.main.addBlock( deindent` - function ${generator.alias( 'recompute' )} ( state, newState, oldState, isInitial ) { + builders.main.addBlock(deindent` + function ${generator.alias( + 'recompute' + )} ( state, newState, oldState, isInitial ) { ${builder} } - ` ); + `); } - builders._set.addBlock( deindent` - ${options.dev && deindent` + builders._set.addBlock(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 => - `if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${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 ); + 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 ); ${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();`} - ` ); - - if ( hasJs ) { - builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` ); + ${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}✂]` + ); } - if ( generator.css && options.css !== false ) { - builders.main.addBlock( deindent` - function ${generator.alias( 'add_css' )} () { - var style = ${generator.helper( 'createElement' )}( 'style' ); - style.id = ${JSON.stringify( generator.cssId + '-style' )}; - style.textContent = ${JSON.stringify( generator.css )}; - ${generator.helper( 'appendNode' )}( style, document.head ); + if (generator.css && options.css !== false) { + builders.main.addBlock(deindent` + function ${generator.alias('add_css')} () { + var style = ${generator.helper('createElement')}( 'style' ); + style.id = ${JSON.stringify(generator.cssId + '-style')}; + style.textContent = ${JSON.stringify(generator.css)}; + ${generator.helper('appendNode')}( style, document.head ); } - ` ); + `); } - generator.blocks.forEach( block => { - builders.main.addBlock( block.render() ); + generator.blocks.forEach(block => { + 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' ] - .map( n => `${n}: ${generator.helper( n )}` ) - .join( ',\n' ) - } + ${['get', 'fire', 'observe', 'on', 'set', '_flush'] + .map(n => `${n}: ${generator.helper(n)}`) + .join(',\n')} }`; // TODO deprecate component.teardown() - builders.main.addBlock( deindent` + 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,25 +222,37 @@ 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 ); + ${generator.alias('template')}.oncreate.call( this ); } `} } - ${generator.helper( 'assign' )}( ${prototypeBase}, ${proto}); + ${generator.helper('assign')}( ${prototypeBase}, ${proto}); ${name}.prototype._set = function _set ( newState ) { ${builders._set} @@ -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(); @@ -198,63 +270,79 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp this._state = {}; this._torndown = true; }; - ` ); + `); - if ( sharedPath ) { - if ( format !== 'es' ) { - throw new Error( `Components with shared helpers must be compiled to ES2015 modules (format: 'es')` ); + if (sharedPath) { + if (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; + const names = Array.from(generator.uses).sort().map(name => { + return name !== generator.alias(name) + ? `${name} as ${generator.alias(name)}` + : name; }); builders.main.addLineAtStart( - `import { ${names.join( ', ' )} } from ${JSON.stringify( sharedPath )};` + `import { ${names.join(', ')} } from ${JSON.stringify(sharedPath)};` ); } else { - generator.uses.forEach( key => { - const str = shared[ key ]; - const code = new MagicString( str ); - const expression = parseExpressionAt( str, 0 ); - - let scope = annotateWithScopes( expression ); - - walk( expression, { - enter ( node, parent ) { - if ( node._scope ) scope = node._scope; - - if ( node.type === 'Identifier' && isReference( node, parent ) && !scope.has( node.name ) ) { - if ( node.name in shared ) { + generator.uses.forEach(key => { + const str = shared[key]; + const code = new MagicString(str); + const expression = parseExpressionAt(str, 0); + + let scope = annotateWithScopes(expression); + + walk(expression, { + enter(node, parent) { + if (node._scope) scope = node._scope; + + 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 ); + generator.uses.add(dependency); - const alias = generator.alias( dependency ); - if ( alias !== node.name ) code.overwrite( node.start, node.end, alias ); + const alias = generator.alias(dependency); + if (alias !== node.name) + code.overwrite(node.start, node.end, alias); } } }, - leave ( node ) { - if ( node._scope ) scope = scope.parent; + leave(node) { + if (node._scope) scope = scope.parent; } }); - 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 ); + const alias = generator.alias(expression.id.name); + if (alias !== expression.id.name) + code.overwrite(expression.id.start, expression.id.end, alias); - builders.main.addBlock( code.toString() ); + builders.main.addBlock(code.toString()); } }); } - return generator.generate( builders.main.toString(), options, { name, format } ); + return generator.generate(builders.main.toString(), options, { + name, + format + }); } diff --git a/src/generators/dom/interfaces.ts b/src/generators/dom/interfaces.ts index a58069da90..8c460a4733 100644 --- a/src/generators/dom/interfaces.ts +++ b/src/generators/dom/interfaces.ts @@ -2,7 +2,7 @@ export interface State { name: string; namespace: string; parentNode: string; - isTopLevel: boolean + isTopLevel: boolean; parentNodeName?: string; basename?: string; inEachBlock?: boolean; diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 3a96aca3f2..595b753d57 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -5,12 +5,14 @@ import { DomGenerator } from './index'; import { Node } from '../../interfaces'; import { State } from './interfaces'; -function isElseIf ( node: Node ) { - return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; +function isElseIf(node: Node) { + return ( + node && node.children.length === 1 && node.children[0].type === 'IfBlock' + ); } -function getChildState ( parent: State, child = {} ) { - return assign( {}, parent, { name: null, parentNode: null }, child || {} ); +function getChildState(parent: State, child = {}) { + return assign({}, parent, { name: null, parentNode: null }, child || {}); } // Whitespace inside one of these elements will not result in @@ -28,118 +30,144 @@ const elementsWithoutText = new Set([ ]); const preprocessors = { - MustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { - const dependencies = block.findDependencies( node.expression ); - block.addDependencies( dependencies ); - - node._state = getChildState( state, { - name: block.getUniqueName( 'text' ) + MustacheTag: ( + generator: DomGenerator, + block: Block, + state: State, + node: Node + ) => { + const dependencies = block.findDependencies(node.expression); + block.addDependencies(dependencies); + + node._state = getChildState(state, { + name: block.getUniqueName('text') }); }, - RawMustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { - const dependencies = block.findDependencies( node.expression ); - block.addDependencies( dependencies ); + RawMustacheTag: ( + generator: DomGenerator, + block: Block, + state: State, + node: Node + ) => { + const dependencies = block.findDependencies(node.expression); + block.addDependencies(dependencies); - const basename = block.getUniqueName( 'raw' ); - const name = block.getUniqueName( `${basename}_before` ); + const basename = block.getUniqueName('raw'); + const name = block.getUniqueName(`${basename}_before`); - node._state = getChildState( state, { basename, name }); + node._state = getChildState(state, { basename, name }); }, - Text: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { - node._state = getChildState( state ); + Text: (generator: DomGenerator, block: Block, state: State, node: Node) => { + node._state = getChildState(state); - if ( !/\S/.test( node.data ) ) { - if ( state.namespace ) return; - if ( elementsWithoutText.has( state.parentNodeName ) ) return; + if (!/\S/.test(node.data)) { + if (state.namespace) return; + if (elementsWithoutText.has(state.parentNodeName)) return; } node._state.shouldCreate = true; - node._state.name = block.getUniqueName( `text` ); + node._state.name = block.getUniqueName(`text`); }, - IfBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { + IfBlock: ( + generator: DomGenerator, + block: Block, + state: State, + node: Node + ) => { const blocks: Block[] = []; let dynamic = false; let hasIntros = false; let hasOutros = false; - function attachBlocks ( node: Node ) { - const dependencies = block.findDependencies( node.expression ); - block.addDependencies( dependencies ); + function attachBlocks(node: Node) { + const dependencies = block.findDependencies(node.expression); + block.addDependencies(dependencies); node._block = block.child({ - name: generator.getUniqueName( `create_if_block` ) + name: generator.getUniqueName(`create_if_block`) }); - node._state = getChildState( state ); + node._state = getChildState(state); - blocks.push( node._block ); - preprocessChildren( generator, node._block, node._state, node ); + blocks.push(node._block); + preprocessChildren(generator, node._block, node._state, node); - if ( node._block.dependencies.size > 0 ) { + if (node._block.dependencies.size > 0) { dynamic = true; - block.addDependencies( node._block.dependencies ); + block.addDependencies(node._block.dependencies); } - if ( node._block.hasIntroMethod ) hasIntros = true; - if ( node._block.hasOutroMethod ) hasOutros = true; + if (node._block.hasIntroMethod) hasIntros = true; + if (node._block.hasOutroMethod) hasOutros = true; - if ( isElseIf( node.else ) ) { - attachBlocks( node.else.children[0] ); - } else if ( node.else ) { + if (isElseIf(node.else)) { + attachBlocks(node.else.children[0]); + } else if (node.else) { node.else._block = block.child({ - name: generator.getUniqueName( `create_if_block` ) + name: generator.getUniqueName(`create_if_block`) }); - node.else._state = getChildState( state ); + node.else._state = getChildState(state); - blocks.push( node.else._block ); - preprocessChildren( generator, node.else._block, node.else._state, node.else ); + blocks.push(node.else._block); + preprocessChildren( + generator, + node.else._block, + node.else._state, + node.else + ); - if ( node.else._block.dependencies.size > 0 ) { + if (node.else._block.dependencies.size > 0) { dynamic = true; - block.addDependencies( node.else._block.dependencies ); + block.addDependencies(node.else._block.dependencies); } } } - attachBlocks( node ); + attachBlocks(node); - blocks.forEach( block => { + blocks.forEach(block => { block.hasUpdateMethod = dynamic; block.hasIntroMethod = hasIntros; block.hasOutroMethod = hasOutros; }); - generator.blocks.push( ...blocks ); + generator.blocks.push(...blocks); }, - EachBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => { - const dependencies = block.findDependencies( node.expression ); - block.addDependencies( dependencies ); + 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` ); - indexNames.set( node.context, indexName ); + const indexNames = new Map(block.indexNames); + const indexName = + node.index || block.getUniqueName(`${node.context}_index`); + indexNames.set(node.context, indexName); - const listNames = new Map( block.listNames ); - const listName = block.getUniqueName( `each_block_value` ); - listNames.set( node.context, listName ); + const listNames = new Map(block.listNames); + const listName = block.getUniqueName(`each_block_value`); + listNames.set(node.context, listName); - const context = generator.getUniqueName( node.context ); - const contexts = new Map( block.contexts ); - contexts.set( node.context, context ); + const context = generator.getUniqueName(node.context); + const contexts = new Map(block.contexts); + contexts.set(node.context, context); - const indexes = new Map( block.indexes ); - if ( node.index ) indexes.set( indexName, node.context ); + const indexes = new Map(block.indexes); + if (node.index) indexes.set(indexName, node.context); - const contextDependencies = new Map( block.contextDependencies ); - contextDependencies.set( node.context, dependencies ); + const contextDependencies = new Map(block.contextDependencies); + contextDependencies.set(node.context, dependencies); node._block = block.child({ - name: generator.getUniqueName( 'create_each_block' ), + name: generator.getUniqueName('create_each_block'), expression: node.expression, context: node.context, key: node.key, @@ -153,134 +181,152 @@ const preprocessors = { indexNames, listNames, - params: block.params.concat( listName, context, indexName ) + params: block.params.concat(listName, context, indexName) }); - node._state = getChildState( state, { + node._state = getChildState(state, { inEachBlock: true }); - generator.blocks.push( node._block ); - preprocessChildren( generator, node._block, node._state, node ); - block.addDependencies( node._block.dependencies ); + generator.blocks.push(node._block); + preprocessChildren(generator, node._block, node._state, node); + block.addDependencies(node._block.dependencies); node._block.hasUpdateMethod = node._block.dependencies.size > 0; - if ( node.else ) { + if (node.else) { node.else._block = block.child({ - name: generator.getUniqueName( `${node._block.name}_else` ) + name: generator.getUniqueName(`${node._block.name}_else`) }); - node.else._state = getChildState( state ); + node.else._state = getChildState(state); - generator.blocks.push( node.else._block ); - preprocessChildren( generator, node.else._block, node.else._state, node.else ); + generator.blocks.push(node.else._block); + 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'; - - if ( isComponent ) { - node._state = getChildState( state ); + 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, { + 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: [] }); } - node.attributes.forEach( ( attribute: Node ) => { - if ( attribute.type === 'Attribute' && attribute.value !== true ) { - attribute.value.forEach( ( chunk: Node ) => { - if ( chunk.type !== 'Text' ) { - const dependencies = block.findDependencies( chunk.expression ); - block.addDependencies( dependencies ); + node.attributes.forEach((attribute: Node) => { + if (attribute.type === 'Attribute' && attribute.value !== true) { + attribute.value.forEach((chunk: Node) => { + if (chunk.type !== 'Text') { + const dependencies = block.findDependencies(chunk.expression); + block.addDependencies(dependencies); } }); - } - - 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; - if ( attribute.outro ) { + } 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; + if (attribute.outro) { generator.hasOutroTransitions = block.hasOutroMethod = true; block.outros += 1; } } }); - if ( node.children.length ) { - if ( isComponent ) { - const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() ); + if (node.children.length) { + if (isComponent) { + const name = block.getUniqueName( + (node.name === ':Self' ? generator.name : node.name).toLowerCase() + ); node._block = block.child({ - name: generator.getUniqueName( `create_${name}_yield_fragment` ) + name: generator.getUniqueName(`create_${name}_yield_fragment`) }); - generator.blocks.push( node._block ); - preprocessChildren( generator, node._block, node._state, node ); - block.addDependencies( node._block.dependencies ); + generator.blocks.push(node._block); + preprocessChildren(generator, node._block, node._state, node); + block.addDependencies(node._block.dependencies); node._block.hasUpdateMethod = node._block.dependencies.size > 0; - } - - else { - preprocessChildren( generator, block, node._state, node ); + } 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; - node.children.forEach( ( child: Node ) => { - if ( child.type === 'Comment' ) return; + node.children.forEach((child: Node) => { + if (child.type === 'Comment') return; - if ( child.type === 'Text' && lastChild && lastChild.type === 'Text' ) { + if (child.type === 'Text' && lastChild && lastChild.type === 'Text') { lastChild.data += child.data; lastChild.end = child.end; } else { - cleaned.push( child ); + cleaned.push(child); } lastChild = child; }); - if ( isTopLevel ) { + if (isTopLevel) { // trim leading and trailing whitespace from the top level const firstChild = cleaned[0]; - if ( firstChild && firstChild.type === 'Text' ) { - firstChild.data = trimStart( firstChild.data ); - if ( !firstChild.data ) cleaned.shift(); + if (firstChild && firstChild.type === 'Text') { + firstChild.data = trimStart(firstChild.data); + if (!firstChild.data) cleaned.shift(); } - const lastChild = cleaned[ cleaned.length - 1 ]; - if ( lastChild && lastChild.type === 'Text' ) { - lastChild.data = trimEnd( lastChild.data ); - if ( !lastChild.data ) cleaned.pop(); + const lastChild = cleaned[cleaned.length - 1]; + if (lastChild && lastChild.type === 'Text') { + lastChild.data = trimEnd(lastChild.data); + if (!lastChild.data) cleaned.pop(); } } lastChild = null; - cleaned.forEach( ( child: Node ) => { - const preprocess = preprocessors[ child.type ]; - if ( preprocess ) preprocess( generator, block, state, child ); + cleaned.forEach((child: Node) => { + const preprocess = preprocessors[child.type]; + if (preprocess) preprocess(generator, block, state, child); - if ( lastChild ) { + if (lastChild) { lastChild.next = child; lastChild.needsAnchor = !child._state || !child._state.name; } @@ -288,24 +334,28 @@ function preprocessChildren ( generator: DomGenerator, block: Block, state: Stat lastChild = child; }); - if ( lastChild ) { + if (lastChild) { lastChild.needsAnchor = !state.parentNode; } 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' ), + name: generator.alias('create_main_fragment'), key: null, contexts: new Map(), indexes: new Map(), contextDependencies: new Map(), - params: [ 'state' ], + params: ['state'], indexNames: new Map(), listNames: new Map(), @@ -318,9 +368,9 @@ export default function preprocess ( generator: DomGenerator, namespace: string, isTopLevel: true }; - generator.blocks.push( block ); - preprocessChildren( generator, block, state, node, true ); + generator.blocks.push(block); + preprocessChildren(generator, block, state, node, true); block.hasUpdateMethod = block.dependencies.size > 0; return { block, state }; -} \ No newline at end of file +} diff --git a/src/generators/dom/visit.ts b/src/generators/dom/visit.ts index 1443a32049..91601fa8fd 100644 --- a/src/generators/dom/visit.ts +++ b/src/generators/dom/visit.ts @@ -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 ) { - const visitor = visitors[ node.type ]; - visitor( generator, block, state, node ); -} \ No newline at end of file +export default function visit( + generator: DomGenerator, + block: Block, + state, + node: Node +) { + const visitor = visitors[node.type]; + visitor(generator, block, state, node); +} diff --git a/src/generators/dom/visitors/Component/Attribute.ts b/src/generators/dom/visitors/Component/Attribute.ts index 690cb88639..1bd477a396 100644 --- a/src/generators/dom/visitors/Component/Attribute.ts +++ b/src/generators/dom/visitors/Component/Attribute.ts @@ -3,37 +3,40 @@ 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 ) { - if ( attribute.value === true ) { +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' ) { + 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 ); + const { dependencies, snippet } = block.contextualise(value.expression); // TODO only update attributes that have changed local.dynamicAttributes.push({ @@ -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 => { - if ( chunk.type === 'Text' ) { - return JSON.stringify( chunk.data ); - } else { - const { dependencies, snippet } = block.contextualise( chunk.expression ); - dependencies.forEach( dependency => { - if ( !~allDependencies.indexOf( dependency ) ) allDependencies.push( dependency ); - }); + 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 + ); + dependencies.forEach(dependency => { + if (!~allDependencies.indexOf(dependency)) + allDependencies.push(dependency); + }); - return `( ${snippet} )`; - } - }).join( ' + ' ) - ); + return `( ${snippet} )`; + } + }) + .join(' + '); local.dynamicAttributes.push({ name: attribute.name, @@ -69,4 +75,4 @@ export default function visitAttribute ( generator: DomGenerator, block: Block, dependencies: allDependencies }); } -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts index 9c30f24dc7..d84c2bcfb9 100644 --- a/src/generators/dom/visitors/Component/Binding.ts +++ b/src/generators/dom/visitors/Component/Binding.ts @@ -6,26 +6,40 @@ 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 ) { - const { name } = flattenReference( attribute.value ); - const { snippet, contexts, dependencies } = block.contextualise( attribute.value ); +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 + ); - 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 ); + contexts.forEach(context => { + if (!~local.allUsedContexts.indexOf(context)) + local.allUsedContexts.push(context); }); - const contextual = block.contexts.has( name ); + const contextual = block.contexts.has(name); let obj; let prop; - if ( contextual ) { - 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}✂]'`; + if (contextual) { + 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}✂]'`; obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`; } else { obj = 'state'; @@ -39,14 +53,22 @@ 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; - const updating = block.getUniqueName( `${local.name}_updating` ); - block.addVariable( updating, 'false' ); + const updating = block.getUniqueName(`${local.name}_updating`); + block.addVariable(updating, 'false'); - local.create.addBlock( deindent` + local.create.addBlock(deindent` ${block.component}._bindings.push( function () { if ( ${local.name}._torndown ) return; ${local.name}.observe( '${attribute.name}', function ( value ) { @@ -54,15 +76,19 @@ 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( '||' )} ) { + local.update.addBlock(deindent` + if ( !${updating} && ${dependencies + .map(dependency => `'${dependency}' in changed`) + .join('||')} ) { ${updating} = true; ${local.name}._set({ ${attribute.name}: ${snippet} }); ${updating} = false; } - ` ); -} \ No newline at end of file + `); +} diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 106a08a92c..e25088cc2e 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -10,13 +10,13 @@ import Block from '../../Block'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; -function stringifyProps ( props: string[] ) { - if ( !props.length ) return '{}'; +function stringifyProps(props: string[]) { + if (!props.length) return '{}'; - const joined = props.join( ', ' ); - if ( joined.length > 40 ) { + const joined = props.join(', '); + if (joined.length > 40) { // make larger data objects readable - return `{\n\t${props.join( ',\n\t' )}\n}`; + return `{\n\t${props.join(',\n\t')}\n}`; } return `{ ${joined} }`; @@ -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; @@ -61,115 +68,139 @@ export default function visitComponent ( generator: DomGenerator, block: Block, generator.hasComponents = true; node.attributes - .sort( ( a, b ) => order[ a.type ] - order[ b.type ] ) - .forEach( attribute => { - visitors[ attribute.type ]( generator, block, childState, node, attribute, local ); + .sort((a, b) => order[a.type] - order[b.type]) + .forEach(attribute => { + visitors[attribute.type]( + generator, + block, + childState, + node, + attribute, + local + ); }); - if ( local.allUsedContexts.length ) { - const initialProps = local.allUsedContexts.map( contextName => { - if ( contextName === 'state' ) return `state: state`; + if (local.allUsedContexts.length) { + const initialProps = local.allUsedContexts + .map(contextName => { + if (contextName === 'state') return `state: state`; - const listName = block.listNames.get( contextName ); - const indexName = block.indexNames.get( contextName ); + const listName = block.listNames.get(contextName); + const indexName = block.indexNames.get(contextName); - return `${listName}: ${listName},\n${indexName}: ${indexName}`; - }).join( ',\n' ); + return `${listName}: ${listName},\n${indexName}: ${indexName}`; + }) + .join(',\n'); - const updates = local.allUsedContexts.map( contextName => { - if ( contextName === 'state' ) return `${name}._context.state = state;`; + 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 ); + const listName = block.listNames.get(contextName); + const indexName = block.indexNames.get(contextName); - return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; - }).join( '\n' ); + return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; + }) + .join('\n'); - local.create.addBlock( deindent` + local.create.addBlock(deindent` ${name}._context = { ${initialProps} }; - ` ); + `); - local.update.addBlock( updates ); + local.update.addBlock(updates); } const componentInitProperties = [ - `target: ${!isTopLevel ? state.parentNode: 'null'}`, + `target: ${!isTopLevel ? state.parentNode : 'null'}`, `_root: ${block.component}._root` ]; // Component has children, put them in a separate {{yield}} block - if ( hasChildren ) { - const params = block.params.join( ', ' ); + if (hasChildren) { + const params = block.params.join(', '); const childBlock = node._block; - node.children.forEach( child => { - visit( generator, childBlock, childState, child ); + node.children.forEach(child => { + visit(generator, childBlock, childState, child); }); - const yieldFragment = block.getUniqueName( `${name}_yield_fragment` ); + const yieldFragment = block.getUniqueName(`${name}_yield_fragment`); block.builders.create.addLine( `var ${yieldFragment} = ${childBlock.name}( ${params}, ${block.component} );` ); - if ( childBlock.hasUpdateMethod ) { + if (childBlock.hasUpdateMethod) { block.builders.update.addLine( `${yieldFragment}.update( changed, ${params} );` ); } - block.builders.destroy.addLine( - `${yieldFragment}.destroy();` - ); + block.builders.destroy.addLine(`${yieldFragment}.destroy();`); - componentInitProperties.push( `_yield: ${yieldFragment}`); + 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}` ); + .concat(local.dynamicAttributes) + .map(attribute => `${attribute.name}: ${attribute.value}`); - const initialPropString = stringifyProps( initialProps ); + const initialPropString = stringifyProps(initialProps); - if ( local.bindings.length ) { - const initialData = block.getUniqueName( `${name}_initial_data` ); + if (local.bindings.length) { + const initialData = block.getUniqueName(`${name}_initial_data`); - statements.push( `var ${initialData} = ${initialPropString};` ); + statements.push(`var ${initialData} = ${initialPropString};`); - local.bindings.forEach( binding => { - statements.push( `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` ); + local.bindings.forEach(binding => { + statements.push( + `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` + ); }); - componentInitProperties.push( `data: ${initialData}` ); - } else if ( initialProps.length ) { - componentInitProperties.push( `data: ${initialPropString}` ); + componentInitProperties.push(`data: ${initialData}`); + } else if (initialProps.length) { + componentInitProperties.push(`data: ${initialPropString}`); } } - 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' )} + local.create.addBlockAtStart(deindent` + ${statements.join('\n')} var ${name} = new ${expression}({ ${componentInitProperties.join(',\n')} }); - ` ); + `); - if ( isTopLevel ) { - block.builders.mount.addLine( `${name}._fragment.mount( ${block.target}, anchor );` ); + if (isTopLevel) { + block.builders.mount.addLine( + `${name}._fragment.mount( ${block.target}, anchor );` + ); } - if ( local.dynamicAttributes.length ) { - const updates = local.dynamicAttributes.map( attribute => { - if ( attribute.dependencies.length ) { + 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}; `; } @@ -178,18 +209,19 @@ export default function visitComponent ( generator: DomGenerator, block: Block, return `${name}_changes.${attribute.name} = ${attribute.value};`; }); - local.update.addBlock( deindent` + local.update.addBlock(deindent` var ${name}_changes = {}; - ${updates.join( '\n' )} + ${updates.join('\n')} if ( Object.keys( ${name}_changes ).length ) ${name}.set( ${name}_changes ); - ` ); + `); } - if ( isTopLevel ) block.builders.unmount.addLine( `${name}._fragment.unmount();` ); - block.builders.destroy.addLine( `${name}.destroy( false );` ); + if (isTopLevel) + block.builders.unmount.addLine(`${name}._fragment.unmount();`); + block.builders.destroy.addLine(`${name}.destroy( false );`); - block.builders.create.addBlock( local.create ); - if ( !local.update.isEmpty() ) block.builders.update.addBlock( local.update ); + block.builders.create.addBlock(local.create); + if (!local.update.isEmpty()) block.builders.update.addBlock(local.update); } diff --git a/src/generators/dom/visitors/Component/EventHandler.ts b/src/generators/dom/visitors/Component/EventHandler.ts index 41f2b42b5a..ddc37da085 100644 --- a/src/generators/dom/visitors/Component/EventHandler.ts +++ b/src/generators/dom/visitors/Component/EventHandler.ts @@ -4,36 +4,49 @@ 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.addSourcemapLocations(attribute.expression); + generator.code.prependRight( + attribute.expression.start, + `${block.component}.` + ); const usedContexts: string[] = []; - attribute.expression.arguments.forEach( ( arg: Node ) => { - const { contexts } = block.contextualise( arg, null, true ); + attribute.expression.arguments.forEach((arg: Node) => { + const { contexts } = block.contextualise(arg, null, true); - contexts.forEach( context => { - if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); - if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context ); + contexts.forEach(context => { + if (!~usedContexts.indexOf(context)) usedContexts.push(context); + if (!~local.allUsedContexts.indexOf(context)) + local.allUsedContexts.push(context); }); }); // TODO hoist event handlers? can do `this.__component.method(...)` - const declarations = usedContexts.map( name => { - if ( name === 'state' ) return 'var state = this._context.state;'; + const declarations = usedContexts.map(name => { + if (name === 'state') return 'var state = this._context.state;'; - const listName = block.listNames.get( name ); - const indexName = block.indexNames.get( name ); + const listName = block.listNames.get(name); + const indexName = block.indexNames.get(name); return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`; }); - const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`; + const handlerBody = + (declarations.length ? declarations.join('\n') + '\n\n' : '') + + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`; - local.create.addBlock( deindent` + local.create.addBlock(deindent` ${local.name}.on( '${attribute.name}', function ( event ) { ${handlerBody} }); - ` ); -} \ No newline at end of file + `); +} diff --git a/src/generators/dom/visitors/Component/Ref.ts b/src/generators/dom/visitors/Component/Ref.ts index 480af0d11c..b966e81c71 100644 --- a/src/generators/dom/visitors/Component/Ref.ts +++ b/src/generators/dom/visitors/Component/Ref.ts @@ -4,14 +4,21 @@ 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( `${block.component}.refs.${attribute.name} = ${local.name};` ); - block.builders.destroy.addLine( deindent` + block.builders.destroy.addLine(deindent` if ( ${block.component}.refs.${attribute.name} === ${local.name} ) ${block.component}.refs.${attribute.name} = null; - ` ); -} \ No newline at end of file + `); +} diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 4f5eeee27e..4e0358b85a 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -5,72 +5,98 @@ import Block from '../Block'; import { Node } from '../../../interfaces'; import { State } from '../interfaces'; -export default function visitEachBlock ( generator: DomGenerator, block: Block, state: State, node: Node ) { - const each_block = generator.getUniqueName( `each_block` ); +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 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 mountOrIntro = node._block.hasIntroMethod ? 'intro' : 'mount'; - const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro }; - - const { snippet } = block.contextualise( node.expression ); - - block.builders.create.addLine( `var ${each_block_value} = ${snippet};` ); - - if ( node.key ) { - keyed( generator, block, state, node, snippet, vars ); + const vars = { + each_block, + create_each_block, + each_block_value, + iterations, + i, + params, + anchor, + mountOrIntro + }; + + const { snippet } = block.contextualise(node.expression); + + block.builders.create.addLine(`var ${each_block_value} = ${snippet};`); + + if (node.key) { + keyed(generator, block, state, node, snippet, vars); } else { - unkeyed( generator, block, state, node, snippet, vars ); + unkeyed(generator, block, state, node, snippet, vars); } const isToplevel = !state.parentNode; - if ( node.needsAnchor ) { - block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true ); - } else if ( node.next ) { + if (node.needsAnchor) { + block.addElement( + anchor, + `${generator.helper('createComment')}()`, + state.parentNode, + true + ); + } else if (node.next) { node.next.usedAsAnchor = true; } - if ( node.else ) { - const each_block_else = generator.getUniqueName( `${each_block}_else` ); + if (node.else) { + const each_block_else = generator.getUniqueName(`${each_block}_else`); - block.builders.create.addLine( `var ${each_block_else} = null;` ); + block.builders.create.addLine(`var ${each_block_else} = null;`); // TODO neaten this up... will end up with an empty line in the block - block.builders.create.addBlock( deindent` + block.builders.create.addBlock(deindent` if ( !${each_block_value}.length ) { - ${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` + 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 ); } - ` ); + `); const parentNode = state.parentNode || `${anchor}.parentNode`; - if ( node.else._block.hasUpdateMethod ) { - block.builders.update.addBlock( deindent` + if (node.else._block.hasUpdateMethod) { + block.builders.update.addBlock(deindent` 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(); ${each_block_else}.destroy(); ${each_block_else} = null; } - ` ); + `); } else { - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` if ( ${each_block_value}.length ) { if ( ${each_block_else} ) { ${each_block_else}.unmount(); @@ -78,48 +104,70 @@ 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} ); } - ` ); + `); } block.builders.unmount.addLine( `if ( ${each_block_else} ) ${each_block_else}.unmount()` ); - block.builders.destroy.addBlock( deindent` + block.builders.destroy.addBlock(deindent` if ( ${each_block_else} ) ${each_block_else}.destroy( false ); - ` ); + `); } - node.children.forEach( ( child: Node ) => { - visit( generator, node._block, node._state, child ); + node.children.forEach((child: Node) => { + visit(generator, node._block, node._state, child); }); - if ( node.else ) { - node.else.children.forEach( ( child: Node ) => { - visit( generator, node.else._block, node.else._state, child ); + if (node.else) { + node.else.children.forEach((child: Node) => { + visit(generator, node.else._block, node.else._state, child); }); } } -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` ); - const head = block.getUniqueName( `${each_block}_head` ); - 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 +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`); + const head = block.getUniqueName(`${each_block}_head`); + 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 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.first = node._block.getUniqueName('first'); + node._block.addElement( + node._block.first, + `${generator.helper('createComment')}()`, + null, + true + ); } - block.builders.create.addBlock( deindent` + block.builders.create.addBlock(deindent` var ${lookup} = Object.create( null ); var ${head}; @@ -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}; @@ -136,25 +185,25 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node if ( ${i} === 0 ) ${head} = ${iteration}; } - ` ); + `); - if ( !state.parentNode ) { - block.builders.mount.addBlock( deindent` + if (!state.parentNode) { + block.builders.mount.addBlock(deindent` var ${iteration} = ${head}; while ( ${iteration} ) { ${iteration}.${mountOrIntro}( ${block.target}, anchor ); ${iteration} = ${iteration}.next; } - ` ); + `); } const dynamic = node._block.hasUpdateMethod; const parentNode = state.parentNode || `${anchor}.parentNode`; let destroy; - if ( node._block.hasOutroMethod ) { - const fn = block.getUniqueName( `${each_block}_outro` ); - block.builders.create.addBlock( deindent` + if (node._block.hasOutroMethod) { + const fn = block.getUniqueName(`${each_block}_outro`); + block.builders.create.addBlock(deindent` function ${fn} ( iteration ) { iteration.outro( function () { iteration.unmount(); @@ -162,7 +211,7 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node ${lookup}[iteration.key] = null; }); } - ` ); + `); destroy = deindent` while ( ${expected} ) { @@ -177,14 +226,14 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node } `; } else { - const fn = block.getUniqueName( `${each_block}_destroy` ); - block.builders.create.addBlock( deindent` + const fn = block.getUniqueName(`${each_block}_destroy`); + block.builders.create.addBlock(deindent` function ${fn} ( iteration ) { iteration.unmount(); iteration.destroy(); ${lookup}[iteration.key] = null; } - ` ); + `); destroy = deindent` while ( ${expected} ) { @@ -201,7 +250,7 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node `; } - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` var ${each_block_value} = ${snippet}; var ${expected} = ${head}; @@ -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}; } @@ -265,87 +316,103 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node ${destroy} ${head} = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${node.key}]; - ` ); + `); - if ( !state.parentNode ) { - block.builders.unmount.addBlock( deindent` + if (!state.parentNode) { + block.builders.unmount.addBlock(deindent` var ${iteration} = ${head}; while ( ${iteration} ) { ${iteration}.unmount(); ${iteration} = ${iteration}.next; } - ` ); + `); } - block.builders.destroy.addBlock( deindent` + block.builders.destroy.addBlock(deindent` var ${iteration} = ${head}; while ( ${iteration} ) { ${iteration}.destroy( false ); ${iteration} = ${iteration}.next; } - ` ); + `); } -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` +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 );`} } - ` ); + `); - if ( !state.parentNode ) { - block.builders.mount.addBlock( deindent` + if (!state.parentNode) { + block.builders.mount.addBlock(deindent` for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { ${iterations}[${i}].${mountOrIntro}( ${block.target}, anchor ); } - ` ); + `); } - const dependencies = block.findDependencies( node.expression ); - const allDependencies = new Set( node._block.dependencies ); - dependencies.forEach( dependency => { - allDependencies.add( dependency ); + const dependencies = block.findDependencies(node.expression); + const allDependencies = new Set(node._block.dependencies); + dependencies.forEach(dependency => { + allDependencies.add(dependency); }); // TODO do this for keyed blocks as well - const condition = Array.from( allDependencies ) - .map( dependency => `'${dependency}' in changed` ) - .join( ' || ' ); + const condition = Array.from(allDependencies) + .map(dependency => `'${dependency}' in changed`) + .join(' || '); const parentNode = state.parentNode || `${anchor}.parentNode`; - if ( condition !== '' ) { - const forLoopBody = node._block.hasUpdateMethod ? - node._block.hasIntroMethod ? - deindent` + if (condition !== '') { + 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} ); `; const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`; - const outro = block.getUniqueName( 'outro' ); - const destroy = node._block.hasOutroMethod ? - deindent` + const outro = block.getUniqueName('outro'); + 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(); @@ -366,7 +433,7 @@ function unkeyed ( generator: DomGenerator, block: Block, state: State, node: No ${iterations}.length = ${each_block_value}.length; `; - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` var ${each_block_value} = ${snippet}; if ( ${condition} ) { @@ -376,16 +443,16 @@ function unkeyed ( generator: DomGenerator, block: Block, state: State, node: No ${destroy} } - ` ); + `); } - block.builders.unmount.addBlock( deindent` + block.builders.unmount.addBlock(deindent` for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { ${iterations}[${i}].unmount(); } - ` ); + `); block.builders.destroy.addBlock( - `${generator.helper( 'destroyEach' )}( ${iterations}, false, 0 );` + `${generator.helper('destroyEach')}( ${iterations}, false, 0 );` ); } diff --git a/src/generators/dom/visitors/Element/Attribute.ts b/src/generators/dom/visitors/Element/Attribute.ts index 7252eb6886..8270852e11 100644 --- a/src/generators/dom/visitors/Element/Attribute.ts +++ b/src/generators/dom/visitors/Element/Attribute.ts @@ -6,64 +6,85 @@ 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; + let metadata = state.namespace ? null : attributeLookup[name]; + 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 ) { + if (isDynamic) { let value; - if ( attribute.value.length === 1 ) { + if (attribute.value.length === 1) { // single {{tag}} — may be a non-string - const { snippet } = block.contextualise( attribute.value[0].expression ); + const { snippet } = block.contextualise(attribute.value[0].expression); value = snippet; } else { // '{{foo}} {{bar}}' — treat as string concatenation - 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( ' + ' ) - ); + 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(' + '); } - const last = block.getUniqueName( `${state.parentNode}_${name.replace( /[^a-zA-Z_$]/g, '_')}_value` ); - block.addVariable( last ); + 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 ) { + if (isSelectValueAttribute) { // annoying special case - 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 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` if ( ${option}.__value === ${last} ) { ${option}.selected = true; break; @@ -77,53 +98,62 @@ export default function visitAttribute ( generator: DomGenerator, block: Block, } `; - block.builders.create.addLine( deindent` + block.builders.create.addLine(deindent` ${last} = ${value} ${updater} - ` ); - } else if ( propertyName ) { - block.builders.create.addLine( `${state.parentNode}.${propertyName} = ${last} = ${value};` ); + `); + } else if (propertyName) { + 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` + block.builders.update.addBlock(deindent` if ( ${last} !== ( ${last} = ${value} ) ) { ${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} );`; - - - block.builders.create.addLine( statement ); + `); + } 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); // special case – autofocus. has to be handled in a bit of a weird way - if ( attribute.value === true && name === 'autofocus' ) { + if (attribute.value === true && name === 'autofocus') { block.autofocus = state.parentNode; } // special case — xmlns - if ( name === 'xmlns' ) { + if (name === 'xmlns') { // TODO this attribute must be static – enforce at compile time state.namespace = attribute.value[0].data; } } - if ( isIndirectlyBoundValue ) { + if (isIndirectlyBoundValue) { const updateValue = `${state.parentNode}.value = ${state.parentNode}.__value;`; - block.builders.create.addLine( updateValue ); - if ( isDynamic ) block.builders.update.addLine( updateValue ); + block.builders.create.addLine(updateValue); + if (isDynamic) block.builders.update.addLine(updateValue); } -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts index 04173a3cd7..e32c8cbf0b 100644 --- a/src/generators/dom/visitors/Element/Binding.ts +++ b/src/generators/dom/visitors/Element/Binding.ts @@ -7,51 +7,88 @@ import Block from '../../Block'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; -function getObject ( node ) { +function getObject(node) { // TODO validation should ensure this is an Identifier or a MemberExpression - while ( node.type === 'MemberExpression' ) node = node.object; + while (node.type === 'MemberExpression') node = node.object; return 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 ]; - - if ( dependencies.length > 1 ) throw new Error( 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' ); +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]; + + 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 ); + contexts.forEach(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 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 ); - - let setter = getSetter({ block, name, snippet, context: '_svelte', attribute, dependencies, value }); + 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 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 + ); + + 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` ); + const lock = block.alias(`${state.parentNode}_updating`); let updateCondition = `!${lock}`; - block.addVariable( lock, 'false' ); + block.addVariable(lock, 'false'); // <select> special case - if ( node.name === 'select' ) { - if ( !isMultipleSelect ) { + if (node.name === 'select') { + if (!isMultipleSelect) { setter = `var selectedOption = ${state.parentNode}.querySelector(':checked') || ${state.parentNode}.options[0];\n${setter}`; } - const value = block.getUniqueName( 'value' ); - const i = block.alias( 'i' ); - const option = block.getUniqueName( 'option' ); + const value = block.getUniqueName('value'); + 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,20 +102,18 @@ export default function visitBinding ( generator: DomGenerator, block: Block, st ${ifStatement} } `; - } - - // <input type='checkbox|radio' bind:group='selected'> special case - else if ( attribute.name === 'group' ) { - if ( type === 'radio' ) { + } else if (attribute.name === 'group') { + // <input type='checkbox|radio' bind:group='selected'> special case + if (type === 'radio') { setter = deindent` if ( !${state.parentNode}.checked ) return; ${setter} `; } - 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,15 +124,15 @@ 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` ); - block.addVariable( frame ); + if (attribute.name === 'currentTime') { + const frame = block.getUniqueName(`${state.parentNode}_animationframe`); + block.addVariable(frame); setter = deindent` cancelAnimationFrame( ${frame} ); if ( !${state.parentNode}.paused ) ${frame} = requestAnimationFrame( ${handler} ); @@ -105,108 +140,132 @@ 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' ); + const last = block.getUniqueName(`${state.parentNode}_paused_value`); + block.addVariable(last, 'true'); updateCondition = `${last} !== ( ${last} = ${snippet} )`; updateElement = `${state.parentNode}[ ${last} ? 'pause' : 'play' ]();`; } } - block.builders.create.addBlock( deindent` + block.builders.create.addBlock(deindent` function ${handler} () { ${lock} = true; ${setter} ${lock} = false; } - ${generator.helper( 'addEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} ); - ` ); + ${generator.helper( + 'addEventListener' + )}( ${state.parentNode}, '${eventName}', ${handler} ); + `); - if ( node.name !== 'audio' && node.name !== 'video' ) node.initialUpdate = updateElement; + if (node.name !== 'audio' && node.name !== 'video') + node.initialUpdate = updateElement; - if ( updateCondition !== null ) { + if (updateCondition !== null) { // audio/video duration is read-only, it never updates - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` if ( ${updateCondition} ) { ${updateElement} } - ` ); + `); } - block.builders.destroy.addLine( deindent` - ${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} ); - ` ); + block.builders.destroy.addLine(deindent` + ${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} );` ); + 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} );` + ); } } -function getBindingEventName ( node: Node, attribute: Node ) { - if ( node.name === 'input' ) { - const typeAttribute = node.attributes.find( ( attr: Node ) => attr.type === 'Attribute' && attr.name === 'type' ); +function getBindingEventName(node: Node, attribute: Node) { + if (node.name === 'input') { + 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'; } - if ( node.name === 'textarea' ) return 'input'; - if ( attribute.name === 'currentTime' ) return 'timeupdate'; - if ( attribute.name === 'duration' ) return 'durationchange'; - if ( attribute.name === 'paused' ) return 'pause'; + if (node.name === 'textarea') return 'input'; + if (attribute.name === 'currentTime') return 'timeupdate'; + if (attribute.name === 'duration') return 'durationchange'; + if (attribute.name === 'paused') return 'pause'; 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 ) { + if (isMultipleSelect) { return `[].map.call( ${state.parentNode}.querySelectorAll(':checked'), function ( option ) { return option.__value; })`; } // <select bind:value='selected> - if ( node.name === 'select' ) { + if (node.name === 'select') { return 'selectedOption && selectedOption.__value'; } // <input type='checkbox' bind:group='foo'> - if ( attribute.name === 'group' ) { - if ( type === 'checkbox' ) { - return `${generator.helper( 'getBindingGroupValue' )}( ${block.component}._bindingGroups[${bindingGroup}] )`; + if (attribute.name === 'group') { + if (type === 'checkbox') { + return `${generator.helper( + 'getBindingGroupValue' + )}( ${block.component}._bindingGroups[${bindingGroup}] )`; } return `${state.parentNode}.__value`; } // <input type='range|number' bind:value> - if ( type === 'range' || type === 'number' ) { - return `${generator.helper( 'toNumber' )}( ${state.parentNode}.${attribute.name} )`; + if (type === 'range' || type === 'number') { + return `${generator.helper( + 'toNumber' + )}( ${state.parentNode}.${attribute.name} )`; } // everything else return `${state.parentNode}.${attribute.name}`; } -function getBindingGroup ( generator: DomGenerator, value: Node ) { - const { parts } = flattenReference( value ); // TODO handle cases involving computed member expressions - const keypath = parts.join( '.' ); +function getBindingGroup(generator: DomGenerator, value: Node) { + const { parts } = flattenReference(value); // TODO handle cases involving computed member expressions + const keypath = parts.join('.'); // TODO handle contextual bindings — `keypath` should include unique ID of // each block that provides context - let index = generator.bindingGroups.indexOf( keypath ); - if ( index === -1 ) { + let index = generator.bindingGroups.indexOf(keypath); + if (index === -1) { index = generator.bindingGroups.length; - generator.bindingGroups.push( keypath ); + generator.bindingGroups.push(keypath); } return index; diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index b122634845..12ad94448a 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -30,87 +30,109 @@ const visitors = { Ref: visitRef }; -export default function visitElement ( generator: DomGenerator, block: Block, state: State, node: Node ) { - if ( node.name in meta ) { - return meta[ node.name ]( generator, block, 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); } - if ( generator.components.has( node.name ) || node.name === ':Self' ) { - return visitComponent( generator, block, state, node ); + if (generator.components.has(node.name) || node.name === ':Self') { + return visitComponent(generator, block, state, node); } const childState = node._state; const name = childState.parentNode; - block.builders.create.addLine( `var ${name} = ${getRenderStatement( generator, childState.namespace, node.name )};` ); - block.mount( name, state.parentNode ); + 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}', '' );` ); + if (generator.cssId && (!generator.cascade || state.isTopLevel)) { + block.builders.create.addLine( + `${generator.helper( + 'setAttribute' + )}( ${name}, '${generator.cssId}', '' );` + ); } - function visitAttributesAndAddProps () { + function visitAttributesAndAddProps() { let intro; let outro; node.attributes - .sort( ( a: Node, b: Node ) => order[ a.type ] - order[ b.type ] ) - .forEach( ( attribute: Node ) => { - if ( attribute.type === 'Transition' ) { - if ( attribute.intro ) intro = attribute; - if ( attribute.outro ) outro = attribute; + .sort((a: Node, b: Node) => order[a.type] - order[b.type]) + .forEach((attribute: Node) => { + if (attribute.type === 'Transition') { + if (attribute.intro) intro = attribute; + if (attribute.outro) outro = attribute; return; } - visitors[ attribute.type ]( generator, block, childState, node, attribute ); + visitors[attribute.type](generator, block, childState, node, attribute); }); - if ( intro || outro ) addTransitions( generator, block, childState, node, intro, outro ); + if (intro || outro) + addTransitions(generator, block, childState, node, intro, outro); - if ( childState.allUsedContexts.length || childState.usesComponent ) { + if (childState.allUsedContexts.length || childState.usesComponent) { const initialProps: string[] = []; const updates: string[] = []; - if ( childState.usesComponent ) { - initialProps.push( `component: ${block.component}` ); + if (childState.usesComponent) { + initialProps.push(`component: ${block.component}`); } - childState.allUsedContexts.forEach( ( contextName: string ) => { - if ( contextName === 'state' ) return; + childState.allUsedContexts.forEach((contextName: string) => { + if (contextName === 'state') return; - const listName = block.listNames.get( contextName ); - const indexName = block.indexNames.get( contextName ); + 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 ) { - block.builders.create.addBlock( deindent` + if (initialProps.length) { + block.builders.create.addBlock(deindent` ${name}._svelte = { - ${initialProps.join( ',\n' )} + ${initialProps.join(',\n')} }; - ` ); + `); } - if ( updates.length ) { - block.builders.update.addBlock( updates.join( '\n' ) ); + if (updates.length) { + block.builders.update.addBlock(updates.join('\n')); } } } - if ( !state.parentNode ) { + 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' ) { - if ( node.name === 'textarea' ) { + if (node.name !== 'select') { + if (node.name === 'textarea') { // this is an egregious hack, but it's the easiest way to get <textarea> // children treated the same way as a value attribute - if ( node.children.length > 0 ) { + if (node.children.length > 0) { node.attributes.push({ type: 'Attribute', name: 'value', @@ -127,36 +149,47 @@ 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; } - node.children.forEach( ( child: Node ) => { - visit( generator, block, childState, child ); + node.children.forEach((child: Node) => { + visit(generator, block, childState, child); }); - if ( node.lateUpdate ) { - block.builders.update.addLine( node.lateUpdate ); + if (node.lateUpdate) { + block.builders.update.addLine(node.lateUpdate); } - if ( node.name === 'select' ) { + if (node.name === 'select') { visitAttributesAndAddProps(); } - if ( node.initialUpdate ) { - block.builders.create.addBlock( node.initialUpdate ); + if (node.initialUpdate) { + block.builders.create.addBlock(node.initialUpdate); } } -function getRenderStatement ( generator: DomGenerator, namespace: string, name: string ) { - if ( namespace === 'http://www.w3.org/2000/svg' ) { - return `${generator.helper( 'createSvgElement' )}( '${name}' )`; +function getRenderStatement( + generator: DomGenerator, + namespace: string, + name: string +) { + if (namespace === 'http://www.w3.org/2000/svg') { + return `${generator.helper('createSvgElement')}( '${name}' )`; } - if ( namespace ) { + if (namespace) { return `document.createElementNS( '${namespace}', '${name}' )`; } - return `${generator.helper( 'createElement' )}( '${name}' )`; + return `${generator.helper('createElement')}( '${name}' )`; } diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts index b244bdcae4..1227925bf7 100644 --- a/src/generators/dom/visitors/Element/EventHandler.ts +++ b/src/generators/dom/visitors/Element/EventHandler.ts @@ -5,49 +5,61 @@ 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 isCustomEvent = generator.events.has(name); const shouldHoist = !isCustomEvent && state.inEachBlock; - generator.addSourcemapLocations( attribute.expression ); + generator.addSourcemapLocations(attribute.expression); - const flattened = flattenReference( attribute.expression.callee ); - if ( flattened.name !== 'event' && flattened.name !== 'this' ) { + const flattened = flattenReference(attribute.expression.callee); + 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}.` ); - if ( shouldHoist ) state.usesComponent = true; // this feels a bit hacky but it works! + generator.code.prependRight( + attribute.expression.start, + `${block.component}.` + ); + if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works! } const context = shouldHoist ? null : state.parentNode; const usedContexts: string[] = []; - attribute.expression.arguments.forEach( ( arg: Node ) => { - const { contexts } = block.contextualise( arg, context, true ); + attribute.expression.arguments.forEach((arg: Node) => { + const { contexts } = block.contextualise(arg, context, true); - contexts.forEach( context => { - if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); - if ( !~state.allUsedContexts.indexOf( context ) ) state.allUsedContexts.push( context ); + contexts.forEach(context => { + if (!~usedContexts.indexOf(context)) usedContexts.push(context); + if (!~state.allUsedContexts.indexOf(context)) + state.allUsedContexts.push(context); }); }); const _this = context || 'this'; - const declarations = usedContexts.map( name => { - if ( name === 'state' ) { - if ( shouldHoist ) state.usesComponent = true; + const declarations = usedContexts.map(name => { + if (name === 'state') { + if (shouldHoist) state.usesComponent = true; return `var state = ${block.component}.get();`; } - const listName = block.listNames.get( name ); - const indexName = block.indexNames.get( name ); - const contextName = block.contexts.get( name ); + const listName = block.listNames.get(name); + const indexName = block.indexNames.get(name); + const contextName = block.contexts.get(name); return `var ${listName} = ${_this}._svelte.${listName}, ${indexName} = ${_this}._svelte.${indexName}, ${contextName} = ${listName}[${indexName}];`; }); // 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,37 +68,45 @@ 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>{ - render: () => handler - }); + if (shouldHoist) { + generator.blocks.push( + <Block>{ + render: () => handler + } + ); } else { - block.builders.create.addBlock( handler ); + block.builders.create.addBlock(handler); } - if ( isCustomEvent ) { - block.builders.destroy.addLine( deindent` + if (isCustomEvent) { + block.builders.destroy.addLine(deindent` ${handlerName}.teardown(); - ` ); + `); } else { - block.builders.create.addLine( deindent` - ${generator.helper( 'addEventListener' )}( ${state.parentNode}, '${name}', ${handlerName} ); - ` ); + block.builders.create.addLine(deindent` + ${generator.helper( + 'addEventListener' + )}( ${state.parentNode}, '${name}', ${handlerName} ); + `); - block.builders.destroy.addLine( deindent` - ${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${name}', ${handlerName} ); - ` ); + block.builders.destroy.addLine(deindent` + ${generator.helper( + 'removeEventListener' + )}( ${state.parentNode}, '${name}', ${handlerName} ); + `); } } diff --git a/src/generators/dom/visitors/Element/Ref.ts b/src/generators/dom/visitors/Element/Ref.ts index 10db528edf..6577114e6e 100644 --- a/src/generators/dom/visitors/Element/Ref.ts +++ b/src/generators/dom/visitors/Element/Ref.ts @@ -4,16 +4,22 @@ 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( `${block.component}.refs.${name} = ${state.parentNode};` ); - block.builders.destroy.addLine( deindent` + block.builders.destroy.addLine(deindent` if ( ${block.component}.refs.${name} === ${state.parentNode} ) ${block.component}.refs.${name} = null; - ` ); + `); generator.usesRefs = true; // so this component.refs object is created -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/Element/addTransitions.ts b/src/generators/dom/visitors/Element/addTransitions.ts index 80e02ad402..66d4a83cb9 100644 --- a/src/generators/dom/visitors/Element/addTransitions.ts +++ b/src/generators/dom/visitors/Element/addTransitions.ts @@ -4,77 +4,88 @@ 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 ) { - const wrapTransition = generator.helper( 'wrapTransition' ); +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 : '{}'; + if (intro === outro) { + const name = block.getUniqueName(`${state.name}_transition`); + const snippet = intro.expression + ? block.contextualise(intro.expression).snippet + : '{}'; - block.addVariable( name ); + block.addVariable(name); - const fn = `${generator.alias( 'template' )}.transitions.${intro.name}`; + const fn = `${generator.alias('template')}.transitions.${intro.name}`; - block.builders.intro.addBlock( deindent` + block.builders.intro.addBlock(deindent` ${block.component}._renderHooks.push( function () { if ( !${name} ) ${name} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, true, null ); ${name}.run( true, function () { ${block.component}.fire( 'intro.end', { node: ${state.name} }); }); }); - ` ); + `); - block.builders.outro.addBlock( deindent` + block.builders.outro.addBlock(deindent` ${name}.run( false, function () { ${block.component}.fire( 'outro.end', { node: ${state.name} }); - if ( --${block.alias( 'outros' )} === 0 ) ${block.alias( 'outrocallback' )}(); + if ( --${block.alias('outros')} === 0 ) ${block.alias('outrocallback')}(); ${name} = null; }); - ` ); - } - - else { - const introName = intro && block.getUniqueName( `${state.name}_intro` ); - const outroName = outro && block.getUniqueName( `${state.name}_outro` ); + `); + } 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 : '{}'; + if (intro) { + block.addVariable(introName); + const snippet = intro.expression + ? block.contextualise(intro.expression).snippet + : '{}'; - const fn = `${generator.alias( 'template' )}.transitions.${intro.name}`; // TODO add built-in transitions? + const fn = `${generator.alias('template')}.transitions.${intro.name}`; // TODO add built-in transitions? - if ( outro ) { - block.builders.intro.addBlock( deindent` + if (outro) { + block.builders.intro.addBlock(deindent` if ( ${introName} ) ${introName}.abort(); if ( ${outroName} ) ${outroName}.abort(); - ` ); + `); } - block.builders.intro.addBlock( deindent` + block.builders.intro.addBlock(deindent` ${block.component}._renderHooks.push( function () { ${introName} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, true, null ); ${introName}.run( true, function () { ${block.component}.fire( 'intro.end', { node: ${state.name} }); }); }); - ` ); + `); } - if ( outro ) { - block.addVariable( outroName ); - const snippet = outro.expression ? block.contextualise( outro.expression ).snippet : '{}'; + if (outro) { + block.addVariable(outroName); + const snippet = outro.expression + ? block.contextualise(outro.expression).snippet + : '{}'; - const fn = `${generator.alias( 'template' )}.transitions.${outro.name}`; + const fn = `${generator.alias('template')}.transitions.${outro.name}`; // TODO hide elements that have outro'd (unless they belong to a still-outroing // group) prior to their removal from the DOM - block.builders.outro.addBlock( deindent` + block.builders.outro.addBlock(deindent` ${outroName} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, false, null ); ${outroName}.run( false, function () { ${block.component}.fire( 'outro.end', { node: ${state.name} }); - if ( --${block.alias( 'outros' )} === 0 ) ${block.alias( 'outrocallback' )}(); + if ( --${block.alias('outros')} === 0 ) ${block.alias('outrocallback')}(); }); - ` ); + `); } } -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/Element/getStaticAttributeValue.ts b/src/generators/dom/visitors/Element/getStaticAttributeValue.ts index bc24de7d2f..42b5ea6ce9 100644 --- a/src/generators/dom/visitors/Element/getStaticAttributeValue.ts +++ b/src/generators/dom/visitors/Element/getStaticAttributeValue.ts @@ -1,13 +1,15 @@ import { Node } from '../../../../interfaces'; -export default function getStaticAttributeValue ( node: Node, name: string ) { - const attribute = node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === name ); - if ( !attribute ) return null; +export default function getStaticAttributeValue(node: Node, name: string) { + 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' ) { + if (attribute.value.length !== 1 || attribute.value[0].type !== 'Text') { // TODO catch this in validation phase, give a more useful error (with location etc) - throw new Error( `'${name} must be a static attribute` ); + throw new Error(`'${name} must be a static attribute`); } return attribute.value[0].data; -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/Element/lookup.ts b/src/generators/dom/visitors/Element/lookup.ts index de48507fea..5b9a42e0f1 100644 --- a/src/generators/dom/visitors/Element/lookup.ts +++ b/src/generators/dom/visitors/Element/lookup.ts @@ -1,122 +1,235 @@ // source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes const lookup = { - accept: { appliesTo: [ 'form', 'input' ] }, - 'accept-charset': { propertyName: 'acceptCharset', appliesTo: [ 'form' ] }, + accept: { appliesTo: ['form', 'input'] }, + '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' ] }, - allowfullscreen: { propertyName: 'allowFullscreen', appliesTo: [ 'iframe' ] }, - alt: { appliesTo: [ 'applet', 'area', 'img', 'input' ] }, - async: { appliesTo: [ 'script' ] }, - autocomplete: { appliesTo: [ 'form', 'input' ] }, - 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' ] }, - border: { appliesTo: [ 'img', 'object', 'table' ] }, - buffered: { appliesTo: [ 'audio', 'video' ] }, - challenge: { appliesTo: [ 'keygen' ] }, - charset: { appliesTo: [ 'meta', 'script' ] }, - checked: { appliesTo: [ 'command', 'input' ] }, - cite: { appliesTo: [ 'blockquote', 'del', 'ins', 'q' ] }, + action: { appliesTo: ['form'] }, + 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'] }, + autocomplete: { appliesTo: ['form', 'input'] }, + 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' + ] + }, + border: { appliesTo: ['img', 'object', 'table'] }, + buffered: { appliesTo: ['audio', 'video'] }, + challenge: { appliesTo: ['keygen'] }, + charset: { appliesTo: ['meta', 'script'] }, + checked: { appliesTo: ['command', 'input'] }, + cite: { appliesTo: ['blockquote', 'del', 'ins', 'q'] }, class: { propertyName: 'className' }, - code: { appliesTo: [ 'applet' ] }, - codebase: { propertyName: 'codeBase', appliesTo: [ 'applet' ] }, - color: { appliesTo: [ 'basefont', 'font', 'hr' ] }, - cols: { appliesTo: [ 'textarea' ] }, - colspan: { propertyName: 'colSpan', appliesTo: [ 'td', 'th' ] }, - content: { appliesTo: [ 'meta' ] }, + code: { appliesTo: ['applet'] }, + codebase: { propertyName: 'codeBase', appliesTo: ['applet'] }, + color: { appliesTo: ['basefont', 'font', 'hr'] }, + cols: { appliesTo: ['textarea'] }, + colspan: { propertyName: 'colSpan', appliesTo: ['td', 'th'] }, + content: { appliesTo: ['meta'] }, contenteditable: { propertyName: 'contentEditable' }, contextmenu: {}, - controls: { appliesTo: [ 'audio', 'video' ] }, - coords: { appliesTo: [ 'area' ] }, - data: { appliesTo: [ 'object' ] }, - datetime: { propertyName: 'dateTime', appliesTo: [ 'del', 'ins', 'time' ] }, - default: { appliesTo: [ 'track' ] }, - defer: { appliesTo: [ 'script' ] }, + controls: { appliesTo: ['audio', 'video'] }, + coords: { appliesTo: ['area'] }, + data: { appliesTo: ['object'] }, + datetime: { propertyName: 'dateTime', appliesTo: ['del', 'ins', 'time'] }, + default: { appliesTo: ['track'] }, + defer: { appliesTo: ['script'] }, dir: {}, - dirname: { propertyName: 'dirName', appliesTo: [ 'input', 'textarea' ] }, - disabled: { appliesTo: [ 'button', 'command', 'fieldset', 'input', 'keygen', 'optgroup', 'option', 'select', 'textarea' ] }, - download: { appliesTo: [ 'a', 'area' ] }, + dirname: { propertyName: 'dirName', appliesTo: ['input', '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' ] }, - formaction: { appliesTo: [ 'input', 'button' ] }, - headers: { appliesTo: [ 'td', 'th' ] }, - height: { appliesTo: [ 'canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video' ] }, + enctype: { appliesTo: ['form'] }, + for: { propertyName: 'htmlFor', appliesTo: ['label', 'output'] }, + 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'] + }, hidden: {}, - high: { appliesTo: [ 'meter' ] }, - href: { appliesTo: [ 'a', 'area', 'base', 'link' ] }, - hreflang: { appliesTo: [ 'a', 'area', 'link' ] }, - 'http-equiv': { propertyName: 'httpEquiv', appliesTo: [ 'meta' ] }, - icon: { appliesTo: [ 'command' ] }, + high: { appliesTo: ['meter'] }, + href: { appliesTo: ['a', 'area', 'base', 'link'] }, + hreflang: { appliesTo: ['a', 'area', 'link'] }, + 'http-equiv': { propertyName: 'httpEquiv', appliesTo: ['meta'] }, + icon: { appliesTo: ['command'] }, id: {}, - ismap: { propertyName: 'isMap', appliesTo: [ 'img' ] }, + ismap: { propertyName: 'isMap', appliesTo: ['img'] }, itemprop: {}, - keytype: { appliesTo: [ 'keygen' ] }, - kind: { appliesTo: [ 'track' ] }, - label: { appliesTo: [ 'track' ] }, + keytype: { appliesTo: ['keygen'] }, + kind: { appliesTo: ['track'] }, + label: { appliesTo: ['track'] }, lang: {}, - language: { appliesTo: [ 'script' ] }, - loop: { appliesTo: [ 'audio', 'bgsound', 'marquee', 'video' ] }, - low: { appliesTo: [ 'meter' ] }, - manifest: { appliesTo: [ 'html' ] }, - max: { appliesTo: [ 'input', 'meter', 'progress' ] }, - maxlength: { propertyName: 'maxLength', appliesTo: [ 'input', 'textarea' ] }, - media: { appliesTo: [ 'a', 'area', 'link', 'source', 'style' ] }, - method: { appliesTo: [ 'form' ] }, - 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' ] }, - novalidate: { propertyName: 'noValidate', appliesTo: [ 'form' ] }, - open: { appliesTo: [ 'details' ] }, - optimum: { appliesTo: [ 'meter' ] }, - pattern: { appliesTo: [ 'input' ] }, - ping: { appliesTo: [ 'a', 'area' ] }, - placeholder: { appliesTo: [ 'input', 'textarea' ] }, - poster: { appliesTo: [ 'video' ] }, - preload: { appliesTo: [ 'audio', 'video' ] }, - radiogroup: { appliesTo: [ 'command' ] }, - readonly: { propertyName: 'readOnly', appliesTo: [ 'input', 'textarea' ] }, - rel: { appliesTo: [ 'a', 'area', 'link' ] }, - required: { appliesTo: [ 'input', 'select', 'textarea' ] }, - reversed: { appliesTo: [ 'ol' ] }, - rows: { appliesTo: [ 'textarea' ] }, - rowspan: { propertyName: 'rowSpan', appliesTo: [ 'td', 'th' ] }, - sandbox: { appliesTo: [ 'iframe' ] }, - scope: { appliesTo: [ 'th' ] }, - scoped: { appliesTo: [ 'style' ] }, - seamless: { appliesTo: [ 'iframe' ] }, - selected: { appliesTo: [ 'option' ] }, - shape: { appliesTo: [ 'a', 'area' ] }, - size: { appliesTo: [ 'input', 'select' ] }, - sizes: { appliesTo: [ 'link', 'img', 'source' ] }, - span: { appliesTo: [ 'col', 'colgroup' ] }, + language: { appliesTo: ['script'] }, + loop: { appliesTo: ['audio', 'bgsound', 'marquee', 'video'] }, + low: { appliesTo: ['meter'] }, + manifest: { appliesTo: ['html'] }, + max: { appliesTo: ['input', 'meter', 'progress'] }, + maxlength: { propertyName: 'maxLength', appliesTo: ['input', 'textarea'] }, + media: { appliesTo: ['a', 'area', 'link', 'source', 'style'] }, + method: { appliesTo: ['form'] }, + 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' + ] + }, + novalidate: { propertyName: 'noValidate', appliesTo: ['form'] }, + open: { appliesTo: ['details'] }, + optimum: { appliesTo: ['meter'] }, + pattern: { appliesTo: ['input'] }, + ping: { appliesTo: ['a', 'area'] }, + placeholder: { appliesTo: ['input', 'textarea'] }, + poster: { appliesTo: ['video'] }, + preload: { appliesTo: ['audio', 'video'] }, + radiogroup: { appliesTo: ['command'] }, + readonly: { propertyName: 'readOnly', appliesTo: ['input', 'textarea'] }, + rel: { appliesTo: ['a', 'area', 'link'] }, + required: { appliesTo: ['input', 'select', 'textarea'] }, + reversed: { appliesTo: ['ol'] }, + rows: { appliesTo: ['textarea'] }, + rowspan: { propertyName: 'rowSpan', appliesTo: ['td', 'th'] }, + sandbox: { appliesTo: ['iframe'] }, + scope: { appliesTo: ['th'] }, + scoped: { appliesTo: ['style'] }, + seamless: { appliesTo: ['iframe'] }, + selected: { appliesTo: ['option'] }, + shape: { appliesTo: ['a', 'area'] }, + size: { appliesTo: ['input', 'select'] }, + sizes: { appliesTo: ['link', 'img', 'source'] }, + span: { appliesTo: ['col', 'colgroup'] }, spellcheck: {}, - src: { appliesTo: [ 'audio', 'embed', 'iframe', 'img', 'input', 'script', 'source', 'track', 'video' ] }, - srcdoc: { appliesTo: [ 'iframe' ] }, - srclang: { appliesTo: [ 'track' ] }, - srcset: { appliesTo: [ 'img' ] }, - start: { appliesTo: [ 'ol' ] }, - step: { appliesTo: [ 'input' ] }, + src: { + appliesTo: [ + 'audio', + 'embed', + 'iframe', + 'img', + 'input', + 'script', + 'source', + 'track', + 'video' + ] + }, + srcdoc: { appliesTo: ['iframe'] }, + srclang: { appliesTo: ['track'] }, + srcset: { appliesTo: ['img'] }, + start: { appliesTo: ['ol'] }, + step: { appliesTo: ['input'] }, style: { propertyName: 'style.cssText' }, - summary: { appliesTo: [ 'table' ] }, + summary: { appliesTo: ['table'] }, tabindex: { propertyName: 'tabIndex' }, - target: { appliesTo: [ 'a', 'area', 'base', 'form' ] }, + target: { appliesTo: ['a', 'area', 'base', 'form'] }, title: {}, - 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' ] }, - wrap: { appliesTo: [ 'textarea' ] } + 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'] + }, + wrap: { appliesTo: ['textarea'] } }; -Object.keys( lookup ).forEach( name => { - const metadata = lookup[ name ]; - if ( !metadata.propertyName ) metadata.propertyName = name; +Object.keys(lookup).forEach(name => { + const metadata = lookup[name]; + if (!metadata.propertyName) metadata.propertyName = name; }); export default lookup; diff --git a/src/generators/dom/visitors/Element/meta/Window.ts b/src/generators/dom/visitors/Element/meta/Window.ts index bdf2231bff..68df181635 100644 --- a/src/generators/dom/visitors/Element/meta/Window.ts +++ b/src/generators/dom/visitors/Element/meta/Window.ts @@ -22,65 +22,74 @@ 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 = {}; - node.attributes.forEach( ( attribute: Node ) => { - if ( attribute.type === 'EventHandler' ) { + node.attributes.forEach((attribute: Node) => { + if (attribute.type === 'EventHandler') { // TODO verify that it's a valid callee (i.e. built-in or declared method) - generator.addSourcemapLocations( attribute.expression ); + generator.addSourcemapLocations(attribute.expression); let usesState = false; - attribute.expression.arguments.forEach( ( arg: Node ) => { - const { contexts } = block.contextualise( arg, null, true ); - if ( contexts.length ) usesState = true; + attribute.expression.arguments.forEach((arg: Node) => { + const { contexts } = block.contextualise(arg, null, true); + if (contexts.length) usesState = true; }); - const flattened = flattenReference( attribute.expression.callee ); - if ( flattened.name !== 'event' && flattened.name !== 'this' ) { + 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}` ); + const handlerName = block.getUniqueName(`onwindow${attribute.name}`); const handlerBody = deindent` ${usesState && `var state = ${block.component}.get();`} [✂${attribute.expression.start}-${attribute.expression.end}✂]; `; - block.builders.create.addBlock( deindent` + block.builders.create.addBlock(deindent` function ${handlerName} ( event ) { ${handlerBody} }; window.addEventListener( '${attribute.name}', ${handlerName} ); - ` ); + `); - block.builders.destroy.addBlock( deindent` + block.builders.destroy.addBlock(deindent` window.removeEventListener( '${attribute.name}', ${handlerName} ); - ` ); + `); } - if ( attribute.type === 'Binding' ) { + if (attribute.type === 'Binding') { // in dev mode, throw if read-only values are written to - if ( readonly.has( attribute.name ) ) { - generator.readonly.add( attribute.value.name ); + if (readonly.has(attribute.name)) { + generator.readonly.add(attribute.value.name); } - bindings[ attribute.name ] = attribute.value.name; + bindings[attribute.name] = attribute.value.name; // bind:online is a special case, we need to listen for two separate events - if ( attribute.name === 'online' ) return; + if (attribute.name === 'online') return; - const associatedEvent = associatedEvents[ attribute.name ]; + const associatedEvent = associatedEvents[attribute.name]; - if ( !associatedEvent ) { - throw new Error( `Cannot bind to ${attribute.name} on <:Window>` ); + if (!associatedEvent) { + throw new Error(`Cannot bind to ${attribute.name} on <:Window>`); } - if ( !events[ associatedEvent ] ) events[ associatedEvent ] = []; - events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` ); + if (!events[associatedEvent]) events[associatedEvent] = []; + events[associatedEvent].push( + `${attribute.value.name}: this.${attribute.name}` + ); // add initial value generator.metaBindings.push( @@ -89,14 +98,15 @@ export default function visitWindow ( generator: DomGenerator, block: Block, nod } }); - const lock = block.getUniqueName( `window_updating` ); + const lock = block.getUniqueName(`window_updating`); - Object.keys( events ).forEach( event => { - const handlerName = block.getUniqueName( `onwindow${event}` ); - const props = events[ event ].join( ',\n' ); + Object.keys(events).forEach(event => { + const handlerName = block.getUniqueName(`onwindow${event}`); + const props = events[event].join(',\n'); - if ( event === 'scroll' ) { // TODO other bidirectional bindings... - block.addVariable( lock, 'false' ); + if (event === 'scroll') { + // TODO other bidirectional bindings... + block.addVariable(lock, 'false'); } const handlerBody = deindent` @@ -111,63 +121,74 @@ export default function visitWindow ( generator: DomGenerator, block: Block, nod ${event === 'scroll' && `${lock} = false;`} `; - block.builders.create.addBlock( deindent` + block.builders.create.addBlock(deindent` function ${handlerName} ( event ) { ${handlerBody} }; window.addEventListener( '${event}', ${handlerName} ); - ` ); + `); - block.builders.destroy.addBlock( deindent` + block.builders.destroy.addBlock(deindent` window.removeEventListener( '${event}', ${handlerName} ); - ` ); + `); }); // special case... might need to abstract this out if we add more special cases - if ( bindings.scrollX && bindings.scrollY ) { - const observerCallback = block.getUniqueName( `scrollobserver` ); + if (bindings.scrollX && bindings.scrollY) { + const observerCallback = block.getUniqueName(`scrollobserver`); - block.builders.create.addBlock( deindent` + 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} );` ); - } else if ( bindings.scrollX || bindings.scrollY ) { + 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.builders.create.addBlock(deindent` + ${block.component}.observe( '${bindings.scrollX || + bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) { if ( ${lock} ) return; - window.scrollTo( ${isX ? 'x, window.scrollY' : 'window.scrollX, y' } ); + window.scrollTo( ${isX ? 'x, window.scrollY' : 'window.scrollX, y'} ); }); - ` ); + `); } // another special case. (I'm starting to think these are all special cases.) - if ( bindings.online ) { - const handlerName = block.getUniqueName( `onlinestatuschanged` ); - block.builders.create.addBlock( deindent` + if (bindings.online) { + const handlerName = block.getUniqueName(`onlinestatuschanged`); + block.builders.create.addBlock(deindent` function ${handlerName} ( event ) { ${block.component}.set({ ${bindings.online}: navigator.onLine }); }; window.addEventListener( 'online', ${handlerName} ); window.addEventListener( 'offline', ${handlerName} ); - ` ); + `); // add initial value generator.metaBindings.push( `this._state.${bindings.online} = navigator.onLine;` ); - block.builders.destroy.addBlock( deindent` + block.builders.destroy.addBlock(deindent` window.removeEventListener( 'online', ${handlerName} ); window.removeEventListener( 'offline', ${handlerName} ); - ` ); + `); } -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index 48e7b3b145..dbb40b67f4 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -5,28 +5,37 @@ import Block from '../Block'; import { Node } from '../../../interfaces'; import { State } from '../interfaces'; -function isElseIf ( node: Node ) { - return node && node.children.length === 1 && node.children[0].type === 'IfBlock'; +function isElseIf(node: Node) { + return ( + node && node.children.length === 1 && node.children[0].type === 'IfBlock' + ); } -function isElseBranch ( branch ) { +function isElseBranch(branch) { return branch.block && !branch.condition; } -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 - }]; +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 ); + visitChildren(generator, block, state, node); - if ( isElseIf( node.else ) ) { + if (isElseIf(node.else)) { branches.push( - ...getBranches( generator, block, state, node.else.children[0] ) + ...getBranches(generator, block, state, node.else.children[0]) ); } else { branches.push({ @@ -37,28 +46,40 @@ function getBranches ( generator: DomGenerator, block: Block, state: State, node hasOutroMethod: node.else ? node.else._block.hasOutroMethod : false }); - if ( node.else ) { - visitChildren( generator, block, state, node.else ); + if (node.else) { + visitChildren(generator, block, state, node.else); } } return branches; } -function visitChildren ( generator: DomGenerator, block: Block, state: State, node: Node ) { - node.children.forEach( ( child: Node ) => { - visit( generator, node._block, node._state, child ); +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 ) { - const name = generator.getUniqueName( `if_block` ); - 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 ); - - const hasElse = isElseBranch( branches[ branches.length - 1 ] ); +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 params = block.params.join(', '); + + const branches = getBranches(generator, block, state, node); + + const hasElse = isElseBranch(branches[branches.length - 1]); const if_name = hasElse ? '' : `if ( ${name} ) `; const dynamic = branches[0].hasUpdateMethod; // can use [0] as proxy for all, since they necessarily have the same value @@ -66,42 +87,67 @@ export default function visitIfBlock ( generator: DomGenerator, block: Block, st const vars = { name, anchor, params, if_name, hasElse }; - if ( node.else ) { - if ( hasOutros ) { - compoundWithOutros( generator, block, state, node, branches, dynamic, vars ); + if (node.else) { + if (hasOutros) { + compoundWithOutros( + generator, + block, + state, + node, + branches, + dynamic, + vars + ); } else { - compound( generator, block, state, node, branches, dynamic, vars ); + compound(generator, block, state, node, branches, dynamic, vars); } } else { - simple( generator, block, state, node, branches[0], dynamic, vars ); + simple(generator, block, state, node, branches[0], dynamic, vars); } - if ( node.needsAnchor ) { - block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true ); - } else if ( node.next ) { + if (node.needsAnchor) { + 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 } ) { - block.builders.create.addBlock( deindent` +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} ); - ` ); + `); const isTopLevel = !state.parentNode; const mountOrIntro = branch.hasIntroMethod ? 'intro' : 'mount'; - if ( isTopLevel ) { - block.builders.mount.addLine( `if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );` ); + if (isTopLevel) { + 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,83 +155,93 @@ 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; `; - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` if ( ${branch.condition} ) { ${enter} } else if ( ${name} ) { ${exit} } - ` ); + `); - 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 } ) { - const get_block = block.getUniqueName( `get_block` ); - const current_block = block.getUniqueName( `current_block` ); +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` + block.builders.create.addBlock(deindent` function ${get_block} ( ${params} ) { - ${branches.map( ({ condition, block }) => { - return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`; - } ).join( '\n' )} + ${branches + .map(({ condition, block }) => { + return `${condition ? `if ( ${condition} ) ` : ''}return ${block};`; + }) + .join('\n')} } var ${current_block} = ${get_block}( ${params} ); var ${name} = ${current_block_and}${current_block}( ${params}, ${block.component} ); - ` ); + `); const isTopLevel = !state.parentNode; const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount'; - if ( isTopLevel ) { - block.builders.mount.addLine( `${if_name}${name}.${mountOrIntro}( ${block.target}, anchor );` ); + if (isTopLevel) { + 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`; @@ -199,20 +255,20 @@ function compound ( generator: DomGenerator, block: Block, state: State, node: N ${if_name}${name}.${mountOrIntro}( ${parentNode}, ${anchor} ); `; - if ( dynamic ) { - block.builders.update.addBlock( deindent` + if (dynamic) { + block.builders.update.addBlock(deindent` if ( ${current_block} === ( ${current_block} = ${get_block}( ${params} ) ) && ${name} ) { ${name}.update( changed, ${params} ); } else { ${changeBlock} } - ` ); + `); } else { - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` if ( ${current_block} !== ( ${current_block} = ${get_block}( ${params} ) ) ) { ${changeBlock} } - ` ); + `); } block.builders.destroy.addLine( @@ -225,52 +281,70 @@ 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 } ) { - 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} ) `; - - block.addVariable( current_block_index ); - block.addVariable( name ); - - block.builders.create.addBlock( deindent` +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} ) `; + + block.addVariable(current_block_index); + block.addVariable(name); + + block.builders.create.addBlock(deindent` var ${if_block_creators} = [ - ${branches.map( branch => branch.block ).join( ',\n' )} + ${branches.map(branch => branch.block).join(',\n')} ]; 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')} } - ` ); + `); - if ( hasElse ) { - block.builders.create.addBlock( deindent` + if (hasElse) { + block.builders.create.addBlock(deindent` ${current_block_index} = ${get_block}( ${params} ); ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} ); - ` ); + `); } else { - block.builders.create.addBlock( deindent` + block.builders.create.addBlock(deindent` if ( ~( ${current_block_index} = ${get_block}( ${params} ) ) ) { ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} ); } - ` ); + `); } const isTopLevel = !state.parentNode; 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 );` ); + if (isTopLevel) { + 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} } @@ -306,8 +380,8 @@ function compoundWithOutros ( generator: DomGenerator, block: Block, state: Stat } `; - if ( dynamic ) { - block.builders.update.addBlock( deindent` + if (dynamic) { + block.builders.update.addBlock(deindent` var ${previous_block_index} = ${current_block_index}; ${current_block_index} = ${get_block}( state ); if ( ${current_block_index} === ${previous_block_index} ) { @@ -315,21 +389,21 @@ function compoundWithOutros ( generator: DomGenerator, block: Block, state: Stat } else { ${changeBlock} } - ` ); + `); } else { - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` var ${previous_block_index} = ${current_block_index}; ${current_block_index} = ${get_block}( state ); if ( ${current_block_index} !== ${previous_block_index} ) { ${changeBlock} } - ` ); + `); } - block.builders.destroy.addLine( deindent` + block.builders.destroy.addLine(deindent` ${if_current_block_index}{ ${if_blocks}[ ${current_block_index} ].unmount(); ${if_blocks}[ ${current_block_index} ].destroy(); } - ` ); + `); } diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index ed0f4c656b..21604778f5 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -4,18 +4,28 @@ 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 value = block.getUniqueName(`${name}_value`); - const { snippet } = block.contextualise( node.expression ); + const { snippet } = block.contextualise(node.expression); - block.addVariable( value ); - block.addElement( name, `${generator.helper( 'createText' )}( ${value} = ${snippet} )`, state.parentNode, true ); + block.addVariable(value); + block.addElement( + name, + `${generator.helper('createText')}( ${value} = ${snippet} )`, + state.parentNode, + true + ); - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` if ( ${value} !== ( ${value} = ${snippet} ) ) { ${name}.data = ${value}; } - ` ); -} \ No newline at end of file + `); +} diff --git a/src/generators/dom/visitors/RawMustacheTag.ts b/src/generators/dom/visitors/RawMustacheTag.ts index a66c71f938..04c3c835d2 100644 --- a/src/generators/dom/visitors/RawMustacheTag.ts +++ b/src/generators/dom/visitors/RawMustacheTag.ts @@ -4,37 +4,54 @@ 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` ); - const after = block.getUniqueName( `${name}_after` ); + const value = block.getUniqueName(`${name}_value`); + const after = block.getUniqueName(`${name}_after`); - const { snippet } = block.contextualise( node.expression ); + const { snippet } = block.contextualise(node.expression); // 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};` ); + 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 ); + if (isToplevel) { + block.builders.mount.addLine(mountStatement); } else { - block.builders.create.addLine( mountStatement ); + block.builders.create.addLine(mountStatement); } - block.builders.update.addBlock( deindent` + block.builders.update.addBlock(deindent` if ( ${value} !== ( ${value} = ${snippet} ) ) { ${detachStatement} ${mountStatement} } - ` ); + `); - block.builders.detachRaw.addBlock( detachStatement ); -} \ No newline at end of file + block.builders.detachRaw.addBlock(detachStatement); +} diff --git a/src/generators/dom/visitors/Text.ts b/src/generators/dom/visitors/Text.ts index d84ffefd68..b8a3850142 100644 --- a/src/generators/dom/visitors/Text.ts +++ b/src/generators/dom/visitors/Text.ts @@ -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 ) { - if ( !node._state.shouldCreate ) return; - block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, node.usedAsAnchor ); -} \ No newline at end of file +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 + ); +} diff --git a/src/generators/dom/visitors/YieldTag.ts b/src/generators/dom/visitors/YieldTag.ts index 5a665b041f..f3d7111941 100644 --- a/src/generators/dom/visitors/YieldTag.ts +++ b/src/generators/dom/visitors/YieldTag.ts @@ -2,14 +2,18 @@ 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( + (state.parentNode ? block.builders.create : block.builders.mount).addLine( `if ( ${block.component}._yield ) ${block.component}._yield.mount( ${parentNode}, null );` ); block.builders.destroy.addLine( `if ( ${block.component}._yield ) ${block.component}._yield.unmount();` ); -} \ No newline at end of file +} diff --git a/src/generators/dom/visitors/shared/binding/getSetter.ts b/src/generators/dom/visitors/shared/binding/getSetter.ts index 11028915e4..2216311b87 100644 --- a/src/generators/dom/visitors/shared/binding/getSetter.ts +++ b/src/generators/dom/visitors/shared/binding/getSetter.ts @@ -1,15 +1,25 @@ 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 ) : ''; - - if ( block.contexts.has( name ) ) { +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]; - const computed = isComputed( attribute.value ); + const computed = isComputed(attribute.value); return deindent` - var list = this.${context}.${block.listNames.get( name )}; - var index = this.${context}.${block.indexNames.get( name )}; + var list = this.${context}.${block.listNames.get(name)}; + var index = this.${context}.${block.indexNames.get(name)}; ${computed && `var state = ${block.component}.get();`} list[index]${tail} = ${value}; @@ -17,8 +27,8 @@ export default function getSetter ({ block, name, snippet, context, attribute, d `; } - if ( attribute.value.type === 'MemberExpression' ) { - const alias = block.alias( name ); + if (attribute.value.type === 'MemberExpression') { + const alias = block.alias(name); return deindent` var state = ${block.component}.get(); @@ -30,19 +40,19 @@ export default function getSetter ({ block, name, snippet, context, attribute, d return `${block.component}._set({ ${name}: ${value} });`; } -function getTailSnippet ( node ) { +function getTailSnippet(node) { const end = node.end; - while ( node.type === 'MemberExpression' ) node = node.object; + while (node.type === 'MemberExpression') node = node.object; const start = node.end; return `[✂${start}-${end}✂]`; } -function isComputed ( node ) { - while ( node.type === 'MemberExpression' ) { - if ( node.computed ) return true; +function isComputed(node) { + while (node.type === 'MemberExpression') { + if (node.computed) return true; node = node.object; } return false; -} \ No newline at end of file +} diff --git a/src/generators/server-side-rendering/Block.ts b/src/generators/server-side-rendering/Block.ts index d73fde6c6e..a8ebc029f5 100644 --- a/src/generators/server-side-rendering/Block.ts +++ b/src/generators/server-side-rendering/Block.ts @@ -15,33 +15,39 @@ export default class Block { indexes: Map<string, string>; contextDependencies: Map<string, string[]>; - constructor ( options: BlockOptions ) { - Object.assign( this, options ); + constructor(options: BlockOptions) { + Object.assign(this, options); } - addBinding ( binding: Node, name: string ) { - const conditions = [ `!( '${binding.name}' in state )`].concat( // TODO handle contextual bindings... - this.conditions.map( c => `(${c})` ) + addBinding(binding: Node, name: string) { + const conditions = [`!( '${binding.name}' in state )`].concat( + // TODO handle contextual bindings... + this.conditions.map(c => `(${c})`) ); - const { keypath } = flattenReference( binding.value ); + const { keypath } = flattenReference(binding.value); - this.generator.bindings.push( deindent` - if ( ${conditions.join( '&&' )} ) { + this.generator.bindings.push(deindent` + if ( ${conditions.join('&&')} ) { tmp = ${name}.data(); if ( '${keypath}' in tmp ) { state.${binding.name} = tmp.${keypath}; settled = false; } } - ` ); + `); } - child ( options: BlockOptions ) { - return new Block( Object.assign( {}, this, options, { parent: this } ) ); + child(options: BlockOptions) { + return new Block(Object.assign({}, this, options, { parent: this })); } - contextualise ( expression: Node, context?: string, isEventHandler?: boolean ) { - return this.generator.contextualise( this, expression, context, isEventHandler ); + contextualise(expression: Node, context?: string, isEventHandler?: boolean) { + return this.generator.contextualise( + this, + expression, + context, + isEventHandler + ); } -} \ No newline at end of file +} diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 90c4e6cdf3..74e3d4b9dd 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -9,25 +9,34 @@ export class SsrGenerator extends Generator { renderCode: string; elementDepth: number; - constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) { - super( parsed, source, name, options ); + constructor( + parsed: Parsed, + source: string, + name: string, + options: CompileOptions + ) { + super(parsed, source, name, options); this.bindings = []; this.renderCode = ''; this.elementDepth = 0; } - append ( code: string ) { + append(code: string) { this.renderCode += code; } } -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'; - const generator = new SsrGenerator( parsed, source, name, options ); + const generator = new SsrGenerator(parsed, source, name, options); - const { computations, hasJs, templateProperties } = generator.parseJs( true ); + const { computations, hasJs, templateProperties } = generator.parseJs(true); // create main render() function const mainBlock = new Block({ @@ -37,8 +46,8 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp conditions: [] }); - parsed.html.children.forEach( ( node: Node ) => { - visit( generator, mainBlock, node ); + parsed.html.children.forEach((node: Node) => { + visit(generator, mainBlock, node); }); const result = deindent` @@ -46,27 +55,37 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp var ${name} = {}; - ${name}.filename = ${JSON.stringify( options.filename )}; + ${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; while ( !settled ) { settled = true; - ${generator.bindings.join( '\n\n' )} + ${generator.bindings.join('\n\n')} } `} @@ -76,15 +95,17 @@ 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 )}, + css: ${JSON.stringify(generator.css)}, map: null // TODO }); `} - ${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 => { - const { name } = prop.key; - const expression = generator.importedComponents.get( name ) || `${generator.alias( 'template' )}.components.${name}`; - return `addComponent( ${expression} );`; - }) - } + ${templateProperties.components.value.properties.map(prop => { + const { name } = prop.key; + const expression = + generator.importedComponents.get(name) || + `${generator.alias('template')}.components.${name}`; + return `addComponent( ${expression} );`; + })} `} return { @@ -125,5 +146,5 @@ export default function ssr ( parsed: Parsed, source: string, options: CompileOp } `; - return generator.generate( result, options, { name, format } ); + return generator.generate(result, options, { name, format }); } diff --git a/src/generators/server-side-rendering/visit.ts b/src/generators/server-side-rendering/visit.ts index 8668a8a922..0c2bba8d69 100644 --- a/src/generators/server-side-rendering/visit.ts +++ b/src/generators/server-side-rendering/visit.ts @@ -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 ) { - const visitor = visitors[ node.type ]; - visitor( generator, block, node ); -} \ No newline at end of file +export default function visit( + generator: SsrGenerator, + block: Block, + node: Node +) { + const visitor = visitors[node.type]; + visitor(generator, block, node); +} diff --git a/src/generators/server-side-rendering/visitors/Comment.ts b/src/generators/server-side-rendering/visitors/Comment.ts index 4d3477d7ef..ad10d20aa5 100644 --- a/src/generators/server-side-rendering/visitors/Comment.ts +++ b/src/generators/server-side-rendering/visitors/Comment.ts @@ -1,3 +1,3 @@ -export default function visitComment () { +export default function visitComment() { // do nothing } diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index 557b6553c1..05523a29a8 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -4,11 +4,15 @@ import { SsrGenerator } from '../index'; import Block from '../Block'; import { Node } from '../../../interfaces'; -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' ) { - const { snippet } = block.contextualise( chunk.expression ); +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') { + const { snippet } = block.contextualise(chunk.expression); return '${__escape( ' + snippet + ')}'; } } @@ -16,65 +20,70 @@ export default function visitComponent ( generator: SsrGenerator, block: Block, const attributes: Node[] = []; const bindings: Node[] = []; - node.attributes.forEach( ( attribute: Node ) => { - if ( attribute.type === 'Attribute' ) { - attributes.push( attribute ); - } else if ( attribute.type === 'Binding' ) { - bindings.push( attribute ); + node.attributes.forEach((attribute: Node) => { + if (attribute.type === 'Attribute') { + attributes.push(attribute); + } else if (attribute.type === 'Binding') { + bindings.push(attribute); } }); const props = attributes - .map( attribute => { + .map(attribute => { let value; - if ( attribute.value === true ) { + if (attribute.value === true) { value = `true`; - } else if ( attribute.value.length === 0 ) { + } else if (attribute.value.length === 0) { value = `''`; - } else if ( attribute.value.length === 1 ) { + } else if (attribute.value.length === 1) { const chunk = attribute.value[0]; - if ( chunk.type === 'Text' ) { - value = isNaN( chunk.data ) ? JSON.stringify( chunk.data ) : chunk.data; + if (chunk.type === 'Text') { + value = isNaN(chunk.data) ? JSON.stringify(chunk.data) : chunk.data; } else { - const { snippet } = block.contextualise( chunk.expression ); + const { snippet } = block.contextualise(chunk.expression); value = snippet; } } else { - value = '`' + attribute.value.map( stringify ).join( '' ) + '`'; + value = '`' + attribute.value.map(stringify).join('') + '`'; } return `${attribute.name}: ${value}`; }) - .concat( bindings.map( binding => { - const { name, keypath } = flattenReference( binding.value ); - const value = block.contexts.has( name ) ? keypath : `state.${keypath}`; - return `${binding.name}: ${value}`; - })) - .join( ', ' ); + .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 ); + bindings.forEach(binding => { + block.addBinding(binding, expression); }); let open = `\${${expression}.render({${props}}`; - if ( node.children.length ) { + if (node.children.length) { open += `, { yield: () => \``; } - generator.append( open ); + generator.append(open); generator.elementDepth += 1; - node.children.forEach( ( child: Node ) => { - visit( generator, block, child ); + node.children.forEach((child: Node) => { + visit(generator, block, child); }); generator.elementDepth -= 1; const close = node.children.length ? `\` })}` : ')}'; - generator.append( close ); + generator.append(close); } diff --git a/src/generators/server-side-rendering/visitors/EachBlock.ts b/src/generators/server-side-rendering/visitors/EachBlock.ts index a08ffd2668..5da589f185 100644 --- a/src/generators/server-side-rendering/visitors/EachBlock.ts +++ b/src/generators/server-side-rendering/visitors/EachBlock.ts @@ -3,22 +3,28 @@ import { SsrGenerator } from '../index'; import Block from '../Block'; import { Node } from '../../../interfaces'; -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} => \``; - generator.append( open ); +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} => \``; + generator.append(open); // TODO should this be the generator's job? It's duplicated between // here and the equivalent DOM compiler visitor - const contexts = new Map( block.contexts ); - contexts.set( node.context, node.context ); + const contexts = new Map(block.contexts); + contexts.set(node.context, node.context); - const indexes = new Map( block.indexes ); - if ( node.index ) indexes.set( node.index, node.context ); + const indexes = new Map(block.indexes); + if (node.index) indexes.set(node.index, node.context); - const contextDependencies = new Map( block.contextDependencies ); - contextDependencies.set( node.context, dependencies ); + const contextDependencies = new Map(block.contextDependencies); + contextDependencies.set(node.context, dependencies); const childBlock = block.child({ contexts, @@ -26,10 +32,10 @@ export default function visitEachBlock ( generator: SsrGenerator, block: Block, contextDependencies }); - node.children.forEach( ( child: Node ) => { - visit( generator, childBlock, child ); + node.children.forEach((child: Node) => { + visit(generator, childBlock, child); }); const close = `\` ).join( '' )}`; - generator.append( close ); -} \ No newline at end of file + generator.append(close); +} diff --git a/src/generators/server-side-rendering/visitors/Element.ts b/src/generators/server-side-rendering/visitors/Element.ts index 2c351ed680..a01f8eabd4 100644 --- a/src/generators/server-side-rendering/visitors/Element.ts +++ b/src/generators/server-side-rendering/visitors/Element.ts @@ -10,67 +10,73 @@ const meta = { ':Window': visitWindow }; -function stringifyAttributeValue ( block: Block, chunks: Node[] ) { - return chunks.map( ( chunk: Node ) => { - if ( chunk.type === 'Text' ) { - return chunk.data; - } +function stringifyAttributeValue(block: Block, chunks: Node[]) { + return chunks + .map((chunk: Node) => { + if (chunk.type === 'Text') { + return chunk.data; + } - const { snippet } = block.contextualise( chunk.expression ); - return '${' + snippet + '}'; - }).join( '' ) + const { snippet } = block.contextualise(chunk.expression); + return '${' + snippet + '}'; + }) + .join(''); } -export default function visitElement ( generator: SsrGenerator, block: Block, node: Node ) { - if ( node.name in meta ) { - return meta[ node.name ]( generator, block, node ); +export default function visitElement( + generator: SsrGenerator, + block: Block, + node: Node +) { + if (node.name in meta) { + return meta[node.name](generator, block, node); } - if ( generator.components.has( node.name ) || node.name === ':Self' ) { - visitComponent( generator, block, node ); + if (generator.components.has(node.name) || node.name === ':Self') { + visitComponent(generator, block, node); return; } let openingTag = `<${node.name}`; let textareaContents; // awkward special case - node.attributes.forEach( ( attribute: Node ) => { - if ( attribute.type !== 'Attribute' ) return; + node.attributes.forEach((attribute: Node) => { + if (attribute.type !== 'Attribute') return; - if ( attribute.name === 'value' && node.name === 'textarea' ) { - textareaContents = stringifyAttributeValue( block, attribute.value ); + if (attribute.name === 'value' && node.name === 'textarea') { + textareaContents = stringifyAttributeValue(block, attribute.value); } else { let str = ` ${attribute.name}`; - if ( attribute.value !== true ) { - str += `="${stringifyAttributeValue( block, attribute.value )}"`; + if (attribute.value !== true) { + str += `="${stringifyAttributeValue(block, attribute.value)}"`; } openingTag += str; } }); - if ( generator.cssId && ( !generator.cascade || generator.elementDepth === 0 ) ) { + if (generator.cssId && (!generator.cascade || generator.elementDepth === 0)) { openingTag += ` ${generator.cssId}`; } openingTag += '>'; - generator.append( openingTag ); + generator.append(openingTag); - if ( node.name === 'textarea' && textareaContents !== undefined ) { - generator.append( textareaContents ); + if (node.name === 'textarea' && textareaContents !== undefined) { + generator.append(textareaContents); } else { generator.elementDepth += 1; - node.children.forEach( ( child: Node ) => { - visit( generator, block, child ); + node.children.forEach((child: Node) => { + visit(generator, block, child); }); generator.elementDepth -= 1; } - if ( !isVoidElementName( node.name ) ) { - generator.append( `</${node.name}>` ); + if (!isVoidElementName(node.name)) { + generator.append(`</${node.name}>`); } -} \ No newline at end of file +} diff --git a/src/generators/server-side-rendering/visitors/IfBlock.ts b/src/generators/server-side-rendering/visitors/IfBlock.ts index 42cd2f861b..a110b08bba 100644 --- a/src/generators/server-side-rendering/visitors/IfBlock.ts +++ b/src/generators/server-side-rendering/visitors/IfBlock.ts @@ -3,26 +3,30 @@ import { SsrGenerator } from '../index'; import Block from '../Block'; import { Node } from '../../../interfaces'; -export default function visitIfBlock ( generator: SsrGenerator, block: Block, node: Node ) { - const { snippet } = block.contextualise( node.expression ); +export default function visitIfBlock( + generator: SsrGenerator, + block: Block, + node: Node +) { + const { snippet } = block.contextualise(node.expression); - generator.append( '${ ' + snippet + ' ? `' ); + generator.append('${ ' + snippet + ' ? `'); const childBlock = block.child({ - conditions: block.conditions.concat( snippet ) + conditions: block.conditions.concat(snippet) }); - node.children.forEach( ( child: Node ) => { - visit( generator, childBlock, child ); + node.children.forEach((child: Node) => { + visit(generator, childBlock, child); }); - generator.append( '` : `' ); + generator.append('` : `'); - if ( node.else ) { - node.else.children.forEach( ( child: Node ) => { - visit( generator, childBlock, child ); + if (node.else) { + node.else.children.forEach((child: Node) => { + visit(generator, childBlock, child); }); } - generator.append( '` }' ); -} \ No newline at end of file + generator.append('` }'); +} diff --git a/src/generators/server-side-rendering/visitors/MustacheTag.ts b/src/generators/server-side-rendering/visitors/MustacheTag.ts index 78c4646588..db6c496692 100644 --- a/src/generators/server-side-rendering/visitors/MustacheTag.ts +++ b/src/generators/server-side-rendering/visitors/MustacheTag.ts @@ -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 ) { - const { snippet } = block.contextualise( node.expression ); - generator.append( '${__escape( ' + snippet + ' )}' ); -} \ No newline at end of file +export default function visitMustacheTag( + generator: SsrGenerator, + block: Block, + node: Node +) { + const { snippet } = block.contextualise(node.expression); + generator.append('${__escape( ' + snippet + ' )}'); +} diff --git a/src/generators/server-side-rendering/visitors/RawMustacheTag.ts b/src/generators/server-side-rendering/visitors/RawMustacheTag.ts index 9dfaf171e6..8112349d04 100644 --- a/src/generators/server-side-rendering/visitors/RawMustacheTag.ts +++ b/src/generators/server-side-rendering/visitors/RawMustacheTag.ts @@ -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 ) { - const { snippet } = block.contextualise( node.expression ); - generator.append( '${' + snippet + '}' ); -} \ No newline at end of file +export default function visitRawMustacheTag( + generator: SsrGenerator, + block: Block, + node: Node +) { + const { snippet } = block.contextualise(node.expression); + generator.append('${' + snippet + '}'); +} diff --git a/src/generators/server-side-rendering/visitors/Text.ts b/src/generators/server-side-rendering/visitors/Text.ts index 3d119ff6be..c35bd9ced9 100644 --- a/src/generators/server-side-rendering/visitors/Text.ts +++ b/src/generators/server-side-rendering/visitors/Text.ts @@ -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 ) { - generator.append( node.data.replace( /\${/g, '\\${' ) ); -} \ No newline at end of file +export default function visitText( + generator: SsrGenerator, + block: Block, + node: Node +) { + generator.append(node.data.replace(/\${/g, '\\${')); +} diff --git a/src/generators/server-side-rendering/visitors/YieldTag.ts b/src/generators/server-side-rendering/visitors/YieldTag.ts index 16dd83ddbc..03c83655ab 100644 --- a/src/generators/server-side-rendering/visitors/YieldTag.ts +++ b/src/generators/server-side-rendering/visitors/YieldTag.ts @@ -1,5 +1,5 @@ import { SsrGenerator } from '../index'; -export default function visitYieldTag ( generator: SsrGenerator ) { - generator.append( `\${options && options.yield ? options.yield() : ''}` ); -} \ No newline at end of file +export default function visitYieldTag(generator: SsrGenerator) { + generator.append(`\${options && options.yield ? options.yield() : ''}`); +} diff --git a/src/generators/server-side-rendering/visitors/meta/Window.ts b/src/generators/server-side-rendering/visitors/meta/Window.ts index 4b4bf67ccc..733bf2b5c0 100644 --- a/src/generators/server-side-rendering/visitors/meta/Window.ts +++ b/src/generators/server-side-rendering/visitors/meta/Window.ts @@ -1,3 +1,3 @@ -export default function visitWindow () { +export default function visitWindow() { // noop -} \ No newline at end of file +} diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index 4eff632244..468a866649 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -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; @@ -11,75 +15,73 @@ export default function processCss ( parsed: Parsed, code: MagicString, cascade: const keyframes = new Map(); - function walkKeyframes ( node: Node ) { - if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) { - node.expression.children.forEach( ( expression: Node ) => { - if ( expression.type === 'Identifier' ) { - if ( expression.name.startsWith( '-global-' ) ) { - code.remove( expression.start, expression.start + 8 ); + function walkKeyframes(node: Node) { + if (node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes') { + node.expression.children.forEach((expression: Node) => { + if (expression.type === 'Identifier') { + if (expression.name.startsWith('-global-')) { + code.remove(expression.start, expression.start + 8); } else { const newName = `svelte-${parsed.hash}-${expression.name}`; - code.overwrite( expression.start, expression.end, newName ); - keyframes.set( expression.name, newName ); + code.overwrite(expression.start, expression.end, newName); + keyframes.set(expression.name, newName); } } }); - } else if ( node.children ) { - node.children.forEach( walkKeyframes ); - } else if ( node.block ) { - walkKeyframes( node.block ); + } else if (node.children) { + node.children.forEach(walkKeyframes); + } else if (node.block) { + walkKeyframes(node.block); } } - parsed.css.children.forEach( walkKeyframes ); + parsed.css.children.forEach(walkKeyframes); - function transform ( rule: Node ) { - rule.selector.children.forEach( ( selector: Node ) => { - if ( cascade ) { + function transform(rule: Node) { + rule.selector.children.forEach((selector: Node) => { + if (cascade) { // TODO disable cascading (without :global(...)) in v2 const start = selector.start - offset; const end = selector.end - offset; - const selectorString = css.slice( start, end ); + const selectorString = css.slice(start, end); const firstToken = selector.children[0]; let transformed; - if ( firstToken.type === 'TypeSelector' ) { + if (firstToken.type === 'TypeSelector') { const insert = firstToken.end - offset; - const head = css.slice( start, insert ); - const tail = css.slice( insert, end ); + const head = css.slice(start, insert); + const tail = css.slice(insert, end); transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`; } else { transformed = `${attr}${selectorString}, ${attr} ${selectorString}`; } - code.overwrite( selector.start, selector.end, transformed ); - } - - else { + code.overwrite(selector.start, selector.end, transformed); + } else { let shouldTransform = true; let c = selector.start; - selector.children.forEach( ( child: Node ) => { - if ( child.type === 'WhiteSpace' || child.type === 'Combinator' ) { - code.appendLeft( c, attr ); + selector.children.forEach((child: Node) => { + if (child.type === 'WhiteSpace' || child.type === 'Combinator') { + code.appendLeft(c, attr); shouldTransform = true; return; } - if ( !shouldTransform ) return; + if (!shouldTransform) return; - if ( child.type === 'PseudoClassSelector' ) { + if (child.type === 'PseudoClassSelector') { // `:global(xyz)` > xyz - if ( child.name === 'global' ) { + if (child.name === 'global') { const first = child.children[0]; const last = child.children[child.children.length - 1]; - code.remove( child.start, first.start ).remove( last.end, child.end ); + code.remove(child.start, first.start).remove(last.end, child.end); } else { - code.prependRight( c, attr ); + code.prependRight(c, attr); } shouldTransform = false; @@ -88,21 +90,21 @@ export default function processCss ( parsed: Parsed, code: MagicString, cascade: c = child.end; }); - if ( shouldTransform ) { - code.appendLeft( c, attr ); + if (shouldTransform) { + code.appendLeft(c, attr); } } }); - rule.block.children.forEach( ( block: Node ) => { - if ( block.type === 'Declaration' ) { + rule.block.children.forEach((block: Node) => { + if (block.type === 'Declaration') { const property = block.property.toLowerCase(); - if ( property === 'animation' || property === 'animation-name' ) { - block.value.children.forEach( ( block: Node ) => { - if ( block.type === 'Identifier' ) { + if (property === 'animation' || property === 'animation-name') { + block.value.children.forEach((block: Node) => { + if (block.type === 'Identifier') { const name = block.name; - if ( keyframes.has( name ) ) { - code.overwrite( block.start, block.end, keyframes.get( name ) ); + if (keyframes.has(name)) { + code.overwrite(block.start, block.end, keyframes.get(name)); } } }); @@ -111,28 +113,31 @@ 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' ) { + function walk(node: Node) { + if (node.type === 'Rule') { + transform(node); + } else if ( + node.type === 'Atrule' && + node.name.toLowerCase() === 'keyframes' + ) { // these have already been processed - } else if ( node.children ) { - node.children.forEach( walk ); - } else if ( node.block ) { - walk( node.block ); + } else if (node.children) { + node.children.forEach(walk); + } else if (node.block) { + walk(node.block); } } - parsed.css.children.forEach( walk ); + parsed.css.children.forEach(walk); // 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; - code.remove( start, end ); + code.remove(start, end); } - return code.slice( parsed.css.content.start, parsed.css.content.end ); + return code.slice(parsed.css.content.start, parsed.css.content.end); } diff --git a/src/generators/shared/utils/getGlobals.ts b/src/generators/shared/utils/getGlobals.ts index 3ba58cf29a..f79e002471 100644 --- a/src/generators/shared/utils/getGlobals.ts +++ b/src/generators/shared/utils/getGlobals.ts @@ -1,33 +1,35 @@ -import { Declaration, Options } from './getIntro'; +import { Declaration, Options } from './getIntro'; export type Globals = (id: string) => any; -export default function getGlobals ( imports: Declaration[], options: Options ) { +export default function getGlobals(imports: Declaration[], options: Options) { const { globals, onerror, onwarn } = options; - const globalFn = getGlobalFn( globals ); - - return imports.map( x => { - let name = globalFn( x.source.value ); - - if ( !name ) { - if ( x.name.startsWith( '__import' ) ) { - const error = new Error( `Could not determine name for imported module '${x.source.value}' – use options.globals` ); - if ( onerror ) { - onerror( error ); + const globalFn = getGlobalFn(globals); + + return imports.map(x => { + let name = globalFn(x.source.value); + + if (!name) { + if (x.name.startsWith('__import')) { + 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 ) { - onwarn( warning ); + if (onwarn) { + onwarn(warning); } else { - console.warn( warning ); // eslint-disable-line no-console + console.warn(warning); // eslint-disable-line no-console } } @@ -38,10 +40,10 @@ export default function getGlobals ( imports: Declaration[], options: Options ) }); } -function getGlobalFn ( globals: any ): Globals { - if ( typeof globals === 'function' ) return globals; - if ( typeof globals === 'object' ) { - return id => globals[ id ]; +function getGlobalFn(globals: any): Globals { + if (typeof globals === 'function') return globals; + if (typeof globals === 'object') { + return id => globals[id]; } return () => undefined; diff --git a/src/generators/shared/utils/getIntro.ts b/src/generators/shared/utils/getIntro.ts index dab45cec20..aea5f47062 100644 --- a/src/generators/shared/utils/getIntro.ts +++ b/src/generators/shared/utils/getIntro.ts @@ -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,75 +20,96 @@ export interface 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 ); - if ( format === 'iife' ) return getIifeIntro( options, imports ); - if ( format === 'umd' ) return getUmdIntro( options, imports ); - if ( format === 'eval' ) return getEvalIntro( options, imports ); - - throw new Error( `Not implemented: ${format}` ); +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); + if (format === 'iife') return getIifeIntro(options, imports); + if (format === 'umd') return getUmdIntro(options, imports); + if (format === 'eval') return getEvalIntro(options, imports); + + throw new Error(`Not implemented: ${format}`); } -function getAmdIntro ( options: Options, imports: Declaration[] ) { - const sourceString = imports.length ? - `[ ${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ' )} ], ` : - ''; +function getAmdIntro(options: Options, imports: Declaration[]) { + 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[] ) { +function getCjsIntro(options: Options, imports: Declaration[]) { const requireBlock = imports - .map( declaration => `var ${declaration.name} = require( '${declaration.source.value}' );` ) - .join( '\n\n' ); + .map( + declaration => + `var ${declaration.name} = require( '${declaration.source.value}' );` + ) + .join('\n\n'); - if ( requireBlock ) { + if (requireBlock) { return `'use strict';\n\n${requireBlock}\n\n`; } return `'use strict';\n\n`; } -function getIifeIntro ( options: Options, imports: Declaration[] ) { - if ( !options.name ) { - throw new Error( `Missing required 'name' option for IIFE export` ); +function getIifeIntro(options: Options, imports: Declaration[]) { + if (!options.name) { + 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[] ) { - if ( !options.name ) { - throw new Error( `Missing required 'name' option for UMD export` ); +function getUmdIntro(options: Options, imports: Declaration[]) { + if (!options.name) { + throw new Error(`Missing required 'name' option for UMD export`); } 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 globalDeps = getGlobals( imports, options ); - - return deindent` + 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` (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[] ) { - return `(function (${paramString( imports )}) { 'use strict';\n\n`; +function getEvalIntro(options: Options, imports: Declaration[]) { + return `(function (${paramString(imports)}) { 'use strict';\n\n`; } -function paramString ( imports: Declaration[] ) { - return imports.length ? ` ${imports.map( dep => dep.name ).join( ', ' )} ` : ''; +function paramString(imports: Declaration[]) { + return imports.length ? ` ${imports.map(dep => dep.name).join(', ')} ` : ''; } -function removeExtension ( file: string ) { - const index = file.lastIndexOf( '.' ); - return ~index ? file.slice( 0, index ) : file; +function removeExtension(file: string) { + const index = file.lastIndexOf('.'); + return ~index ? file.slice(0, index) : file; } diff --git a/src/generators/shared/utils/getOutro.ts b/src/generators/shared/utils/getOutro.ts index b1cac890a3..f9d8f186bc 100644 --- a/src/generators/shared/utils/getOutro.ts +++ b/src/generators/shared/utils/getOutro.ts @@ -1,31 +1,36 @@ import getGlobals from './getGlobals'; -export default function getOutro ( format: string, name: string, options, imports ) { - if ( format === 'es' ) { +export default function getOutro( + format: string, + name: string, + options, + imports +) { + if (format === 'es') { return `export default ${name};`; } - if ( format === 'amd' ) { + if (format === 'amd') { return `return ${name};\n\n});`; } - if ( format === 'cjs' ) { + if (format === 'cjs') { return `module.exports = ${name};`; } - if ( format === 'iife' ) { - const globals = getGlobals( imports, options ); - return `return ${name};\n\n}(${globals.join( ', ' )}));`; + if (format === 'iife') { + const globals = getGlobals(imports, options); + return `return ${name};\n\n}(${globals.join(', ')}));`; } - if ( format === 'eval' ) { - const globals = getGlobals( imports, options ); - return `return ${name};\n\n}(${globals.join( ', ' )}));`; + if (format === 'eval') { + const globals = getGlobals(imports, options); + return `return ${name};\n\n}(${globals.join(', ')}));`; } - if ( format === 'umd' ) { + if (format === 'umd') { return `return ${name};\n\n})));`; } - throw new Error( `Not implemented: ${format}` ); + throw new Error(`Not implemented: ${format}`); } diff --git a/src/generators/shared/utils/walkHtml.ts b/src/generators/shared/utils/walkHtml.ts index ad5d826e1a..e12b36bbf2 100644 --- a/src/generators/shared/utils/walkHtml.ts +++ b/src/generators/shared/utils/walkHtml.ts @@ -1,20 +1,20 @@ import { Node } from '../../../interfaces'; -export default function walkHtml ( html: Node, visitors ) { - function visit ( node: Node ) { - const visitor = visitors[ node.type ]; - if ( !visitor ) throw new Error( `Not implemented: ${node.type}` ); +export default function walkHtml(html: Node, visitors) { + function visit(node: Node) { + const visitor = visitors[node.type]; + if (!visitor) throw new Error(`Not implemented: ${node.type}`); - if ( visitor.enter ) visitor.enter( node ); + if (visitor.enter) visitor.enter(node); - if ( node.children ) { - node.children.forEach( ( child: Node ) => { - visit( child ); + if (node.children) { + node.children.forEach((child: Node) => { + visit(child); }); } - if ( visitor.leave ) visitor.leave( node ); + if (visitor.leave) visitor.leave(node); } - visit( html ); + visit(html); } diff --git a/src/index.ts b/src/index.ts index 3d3b02a937..c392f5217d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,62 +6,65 @@ import { assign } from './shared/index.js'; import { version } from '../package.json'; import { Parsed, CompileOptions, Warning } from './interfaces'; -function normalizeOptions ( options: CompileOptions ) :CompileOptions { - return assign({ - generate: 'dom', +function normalizeOptions(options: CompileOptions): CompileOptions { + return assign( + { + generate: 'dom', - // a filename is necessary for sourcemap generation - filename: 'SvelteComponent.html', + // a filename is necessary for sourcemap generation + filename: 'SvelteComponent.html', - onwarn: ( warning: Warning ) => { - if ( warning.loc ) { - 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 + onwarn: (warning: Warning) => { + if (warning.loc) { + 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 + } + }, + + onerror: (error: Error) => { + throw error; } }, - - onerror: ( error: Error ) => { - throw error; - } - }, options ); + options + ); } -export function compile ( source: string, _options: CompileOptions ) { - const options = normalizeOptions( _options ); +export function compile(source: string, _options: CompileOptions) { + const options = normalizeOptions(_options); let parsed: Parsed; try { - parsed = parse( source, options ); - } catch ( err ) { - options.onerror( err ); + parsed = parse(source, options); + } catch (err) { + options.onerror(err); return; } - validate( parsed, source, options ); + validate(parsed, source, options); - const compiler = options.generate === 'ssr' - ? generateSSR - : generate; + const compiler = options.generate === 'ssr' ? generateSSR : generate; - return compiler( parsed, source, options ); + return compiler(parsed, source, options); } -export function create ( source: string, _options: CompileOptions = {} ) { +export function create(source: string, _options: CompileOptions = {}) { _options.format = 'eval'; - const compiled = compile( source, _options ); + const compiled = compile(source, _options); - if ( !compiled || !compiled.code ) { + if (!compiled || !compiled.code) { return; } try { - return (new Function( 'return ' + compiled.code ))(); - } catch ( err ) { - if ( _options.onerror ) { - _options.onerror( err ); + return new Function('return ' + compiled.code)(); + } catch (err) { + if (_options.onerror) { + _options.onerror(err); return; } else { throw err; diff --git a/src/interfaces.ts b/src/interfaces.ts index 0caa4d6ed1..fd12939860 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -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 -} \ No newline at end of file + onerror?: (error: Error) => void; + onwarn?: (warning: Warning) => void; +} diff --git a/src/parse/index.ts b/src/parse/index.ts index 262dc764d5..c3ae27c3e4 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -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 ) { - super( message, template, index, filename ); + 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,14 +33,14 @@ export class Parser { html: Node; css: Node; js: Node; - metaTags: {} + metaTags: {}; - constructor ( template: string, options: ParserOptions ) { - if ( typeof template !== 'string' ) { - throw new TypeError( 'Template must be a string' ); + constructor(template: string, options: ParserOptions) { + if (typeof template !== 'string') { + throw new TypeError('Template must be a string'); } - this.template = template.replace( /\s+$/, '' ); + this.template = template.replace(/\s+$/, ''); this.filename = options.filename; this.index = 0; @@ -52,36 +57,36 @@ export class Parser { this.css = null; this.js = null; - this.stack.push( this.html ); + this.stack.push(this.html); let state = fragment; - while ( this.index < this.template.length ) { - state = state( this ) || fragment; + while (this.index < this.template.length) { + state = state(this) || fragment; } - if ( this.stack.length > 1 ) { + if (this.stack.length > 1) { const current = this.current(); const type = current.type === 'Element' ? `<${current.name}>` : 'Block'; - this.error( `${type} was left open`, current.start ); + this.error(`${type} was left open`, current.start); } - if ( state !== fragment ) { - this.error( 'Unexpected end of input' ); + if (state !== fragment) { + this.error('Unexpected end of input'); } // trim unnecessary whitespace - while ( this.html.children.length ) { + while (this.html.children.length) { const firstChild = this.html.children[0]; this.html.start = firstChild.start; - if ( firstChild.type !== 'Text' ) break; + if (firstChild.type !== 'Text') break; const length = firstChild.data.length; - firstChild.data = trimStart( firstChild.data ); + firstChild.data = trimStart(firstChild.data); - if ( firstChild.data === '' ) { + if (firstChild.data === '') { this.html.children.shift(); } else { this.html.start += length - firstChild.data.length; @@ -89,16 +94,16 @@ export class Parser { } } - while ( this.html.children.length ) { - const lastChild = this.html.children[ this.html.children.length - 1 ]; + while (this.html.children.length) { + const lastChild = this.html.children[this.html.children.length - 1]; this.html.end = lastChild.end; - if ( lastChild.type !== 'Text' ) break; + if (lastChild.type !== 'Text') break; const length = lastChild.data.length; - lastChild.data = trimEnd( lastChild.data ); + lastChild.data = trimEnd(lastChild.data); - if ( lastChild.data === '' ) { + if (lastChild.data === '') { this.html.children.pop(); } else { this.html.end -= length - lastChild.data.length; @@ -107,82 +112,89 @@ export class Parser { } } - current () { - return this.stack[ this.stack.length - 1 ]; + current() { + return this.stack[this.stack.length - 1]; } - acornError ( err: Error ) { - this.error( err.message.replace( / \(\d+:\d+\)$/, '' ), err.pos ); + acornError(err: Error) { + this.error(err.message.replace(/ \(\d+:\d+\)$/, ''), err.pos); } - error ( message: string, index = this.index ) { - throw new ParseError( message, this.template, index, this.filename ); + error(message: string, index = this.index) { + throw new ParseError(message, this.template, index, this.filename); } - eat ( str: string, required?: boolean ) { - if ( this.match( str ) ) { + eat(str: string, required?: boolean) { + if (this.match(str)) { this.index += str.length; return true; } - if ( required ) { - this.error( `Expected ${str}` ); + if (required) { + this.error(`Expected ${str}`); } } - match ( str: string ) { - return this.template.slice( this.index, this.index + str.length ) === str; + match(str: string) { + return this.template.slice(this.index, this.index + str.length) === str; } - allowWhitespace () { - while ( this.index < this.template.length && whitespace.test( this.template[ this.index ] ) ) { + allowWhitespace() { + while ( + this.index < this.template.length && + whitespace.test(this.template[this.index]) + ) { this.index++; } } - read ( pattern: RegExp ) { - const match = pattern.exec( this.template.slice( this.index ) ); - if ( !match || match.index !== 0 ) return null; + read(pattern: RegExp) { + const match = pattern.exec(this.template.slice(this.index)); + if (!match || match.index !== 0) return null; this.index += match[0].length; return match[0]; } - readUntil ( pattern: RegExp ) { - if ( this.index >= this.template.length ) this.error( 'Unexpected end of input' ); + readUntil(pattern: RegExp) { + if (this.index >= this.template.length) + this.error('Unexpected end of input'); const start = this.index; - const match = pattern.exec( this.template.slice( start ) ); + const match = pattern.exec(this.template.slice(start)); - if ( match ) { + if (match) { const start = this.index; this.index = start + match.index; - return this.template.slice( start, this.index ); + return this.template.slice(start, this.index); } this.index = this.template.length; - return this.template.slice( start ); + return this.template.slice(start); } - remaining () { - return this.template.slice( this.index ); + remaining() { + return this.template.slice(this.index); } - requireWhitespace () { - if ( !whitespace.test( this.template[ this.index ] ) ) { - this.error( `Expected whitespace` ); + requireWhitespace() { + if (!whitespace.test(this.template[this.index])) { + this.error(`Expected whitespace`); } this.allowWhitespace(); } } -export default function parse ( template: string, options: ParserOptions = {} ) :Parsed { - const parser = new Parser( template, options ); +export default function parse( + template: string, + options: ParserOptions = {} +): Parsed { + const parser = new Parser(template, options); return { - hash: hash( parser.template ), + hash: hash(parser.template), html: parser.html, css: parser.css, js: parser.js diff --git a/src/parse/read/directives.ts b/src/parse/read/directives.ts index f4bc5d4f60..b318e5ac2e 100644 --- a/src/parse/read/directives.ts +++ b/src/parse/read/directives.ts @@ -2,61 +2,57 @@ import { parseExpressionAt } from 'acorn'; import spaces from '../../utils/spaces.js'; import { Parser } from '../index'; -function readExpression ( parser: Parser, start: number, quoteMark ) { +function readExpression(parser: Parser, start: number, quoteMark) { let str = ''; let escaped = false; - for ( let i = start; i < parser.template.length; i += 1 ) { + for (let i = start; i < parser.template.length; i += 1) { const char = parser.template[i]; - if ( quoteMark ) { - if ( char === quoteMark ) { - if ( escaped ) { + if (quoteMark) { + if (char === quoteMark) { + if (escaped) { str += quoteMark; } else { break; } - } else if ( escaped ) { + } else if (escaped) { str += '\\' + char; escaped = false; - } else if ( char === '\\' ) { + } else if (char === '\\') { escaped = true; } else { str += char; } - } - - else if ( /\s/.test( char ) ) { + } else if (/\s/.test(char)) { break; - } - - else { + } else { str += char; } } - const expression = parseExpressionAt( spaces( start ) + str, start ); + const expression = parseExpressionAt(spaces(start) + str, start); parser.index = expression.end; parser.allowWhitespace(); - if ( quoteMark ) parser.eat( quoteMark, true ); + if (quoteMark) parser.eat(quoteMark, true); 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; - const expression = readExpression( parser, expressionStart, quoteMark ); + const expression = readExpression(parser, expressionStart, quoteMark); - if ( expression.type !== 'CallExpression' ) { - parser.error( `Expected call expression`, expressionStart ); + if (expression.type !== 'CallExpression') { + parser.error(`Expected call expression`, expressionStart); } return { @@ -68,49 +64,49 @@ 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 - ); + if (parser.eat('=')) { + const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null; const a = parser.index; - if ( parser.eat( '{{' ) ) { + if (parser.eat('{{')) { let message = 'bound values should not be wrapped'; - const b = parser.template.indexOf( '}}', a ); - if ( b !== -1 ) { - const value = parser.template.slice( parser.index, b ); + const b = parser.template.indexOf('}}', a); + if (b !== -1) { + const value = parser.template.slice(parser.index, b); message += ` — use '${value}', not '{{${value}}}'`; } - parser.error( message, a ); + parser.error(message, a); } // this is a bit of a hack so that we can give Acorn something parseable let b; - if ( quoteMark ) { - b = parser.index = parser.template.indexOf( quoteMark, parser.index ); + if (quoteMark) { + b = parser.index = parser.template.indexOf(quoteMark, parser.index); } else { - parser.readUntil( /[\s\r\n\/>]/ ); + parser.readUntil(/[\s\r\n\/>]/); b = parser.index; } - const source = spaces( a ) + parser.template.slice( a, b ); - value = parseExpressionAt( source, a ); + const source = spaces(a) + parser.template.slice(a, b); + value = parseExpressionAt(source, a); - if ( value.type !== 'Identifier' && value.type !== 'MemberExpression' ) { - parser.error( `Cannot bind to rvalue`, value.start ); + if (value.type !== 'Identifier' && value.type !== 'MemberExpression') { + parser.error(`Cannot bind to rvalue`, value.start); } parser.allowWhitespace(); - if ( quoteMark ) { - parser.eat( quoteMark, true ); + if (quoteMark) { + parser.eat(quoteMark, true); } } else { // shorthand – bind:foo equivalent to bind:foo='foo' @@ -131,22 +127,23 @@ 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 - ); + if (parser.eat('=')) { + const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null; const expressionStart = parser.index; - expression = readExpression( parser, expressionStart, quoteMark ); + expression = readExpression(parser, expressionStart, quoteMark); - if ( expression.type !== 'ObjectExpression' ) { - parser.error( `Expected object expression`, expressionStart ); + if (expression.type !== 'ObjectExpression') { + parser.error(`Expected object expression`, expressionStart); } } @@ -159,4 +156,4 @@ export function readTransitionDirective ( parser: Parser, start: number, name: s outro: type === 'out' || type === 'transition', expression }; -} \ No newline at end of file +} diff --git a/src/parse/read/expression.ts b/src/parse/read/expression.ts index 75529e3ce2..6e40f004f9 100644 --- a/src/parse/read/expression.ts +++ b/src/parse/read/expression.ts @@ -1,25 +1,21 @@ 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 ) { +export default function readExpression(parser: Parser) { const start = parser.index; - const name = parser.readUntil( /\s*}}/ ); - if ( name && /^[a-z]+$/.test( name ) ) { + const name = parser.readUntil(/\s*}}/); + if (name && /^[a-z]+$/.test(name)) { const end = start + name.length; - if ( literals.has( name ) ) { + if (literals.has(name)) { return { type: 'Literal', start, end, - value: literals.get( name ), + value: literals.get(name), raw: name }; } @@ -35,11 +31,13 @@ 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; - } catch ( err ) { - parser.acornError( err ); + } catch (err) { + parser.acornError(err); } } diff --git a/src/parse/read/script.ts b/src/parse/read/script.ts index 3d2e3d3d4b..3f56229c2b 100644 --- a/src/parse/read/script.ts +++ b/src/parse/read/script.ts @@ -2,29 +2,30 @@ 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 ) { +export default function readScript(parser: Parser, start: number, attributes) { const scriptStart = parser.index; - const scriptEnd = parser.template.indexOf( scriptClosingTag, scriptStart ); + const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart); - if ( scriptEnd === -1 ) parser.error( `<script> must have a closing tag` ); + if (scriptEnd === -1) parser.error(`<script> must have a closing tag`); - const source = spaces( scriptStart ) + parser.template.slice( scriptStart, scriptEnd ); + const source = + spaces(scriptStart) + parser.template.slice(scriptStart, scriptEnd); parser.index = scriptEnd + scriptClosingTag.length; let ast; try { - ast = parse( source, { + ast = parse(source, { ecmaVersion: 8, sourceType: 'module' }); - } catch ( err ) { - parser.acornError( err ); + } catch (err) { + parser.acornError(err); } - if ( !ast.body.length ) return null; + if (!ast.body.length) return null; ast.start = scriptStart; return { diff --git a/src/parse/read/style.ts b/src/parse/read/style.ts index 0259905ea3..13696642a9 100644 --- a/src/parse/read/style.ts +++ b/src/parse/read/style.ts @@ -2,43 +2,43 @@ import parse from 'css-tree/lib/parser/index.js'; import walk from 'css-tree/lib/utils/walk.js'; import { Parser } from '../index'; -export default function readStyle ( parser: Parser, start: number, attributes ) { +export default function readStyle(parser: Parser, start: number, attributes) { const contentStart = parser.index; - const styles = parser.readUntil( /<\/style>/ ); + const styles = parser.readUntil(/<\/style>/); const contentEnd = parser.index; let ast; try { - ast = parse( styles, { + ast = parse(styles, { positions: true, offset: contentStart }); - } catch ( err ) { - if ( err.name === 'CssSyntaxError' ) { - parser.error( err.message, err.offset ); + } catch (err) { + if (err.name === 'CssSyntaxError') { + parser.error(err.message, err.offset); } else { throw err; } } // tidy up AST - walk.all( ast, node => { - if ( node.loc ) { + walk.all(ast, node => { + if (node.loc) { node.start = node.loc.start.offset; node.end = node.loc.end.offset; delete node.loc; } }); - parser.eat( '</style>', true ); + parser.eat('</style>', true); const end = parser.index; return { start, end, attributes, - children: JSON.parse( JSON.stringify( ast.children ) ), + children: JSON.parse(JSON.stringify(ast.children)), content: { start: contentStart, end: contentEnd, diff --git a/src/parse/state/fragment.ts b/src/parse/state/fragment.ts index 7cd2702a5a..c82b3e2f8a 100644 --- a/src/parse/state/fragment.ts +++ b/src/parse/state/fragment.ts @@ -3,12 +3,12 @@ import mustache from './mustache'; import text from './text'; import { Parser } from '../index'; -export default function fragment ( parser: Parser ) { - if ( parser.match( '<' ) ) { +export default function fragment(parser: Parser) { + if (parser.match('<')) { return tag; } - if ( parser.match( '{{' ) ) { + if (parser.match('{{')) { return mustache; } diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index afa439a45c..88bfc401f7 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -6,92 +6,93 @@ import { Node } from '../../interfaces'; const validIdentifier = /[a-zA-Z_$][a-zA-Z0-9_$]*/; -function trimWhitespace ( block, trimBefore, trimAfter ) { +function trimWhitespace(block, trimBefore, trimAfter) { const firstChild = block.children[0]; - const lastChild = block.children[ block.children.length - 1 ]; + const lastChild = block.children[block.children.length - 1]; - if ( firstChild.type === 'Text' && trimBefore ) { - firstChild.data = trimStart( firstChild.data ); - if ( !firstChild.data ) block.children.shift(); + if (firstChild.type === 'Text' && trimBefore) { + firstChild.data = trimStart(firstChild.data); + if (!firstChild.data) block.children.shift(); } - if ( lastChild.type === 'Text' && trimAfter ) { - lastChild.data = trimEnd( lastChild.data ); - if ( !lastChild.data ) block.children.pop(); + if (lastChild.type === 'Text' && trimAfter) { + lastChild.data = trimEnd(lastChild.data); + if (!lastChild.data) block.children.pop(); } - if ( block.else ) { - trimWhitespace( block.else, trimBefore, trimAfter ); + if (block.else) { + trimWhitespace(block.else, trimBefore, trimAfter); } - if ( firstChild.elseif ) { - trimWhitespace( firstChild, trimBefore, trimAfter ); + if (firstChild.elseif) { + trimWhitespace(firstChild, trimBefore, trimAfter); } } -export default function mustache ( parser: Parser ) { +export default function mustache(parser: Parser) { const start = parser.index; parser.index += 2; parser.allowWhitespace(); // {{/if}} or {{/each}} - if ( parser.eat( '/' ) ) { + if (parser.eat('/')) { let block = parser.current(); let expected; - if ( block.type === 'ElseBlock' ) { + if (block.type === 'ElseBlock') { block.end = start; parser.stack.pop(); block = parser.current(); } - if ( block.type === 'IfBlock' ) { + if (block.type === 'IfBlock') { expected = 'if'; - } else if ( block.type === 'EachBlock' ) { + } else if (block.type === 'EachBlock') { expected = 'each'; } else { - parser.error( `Unexpected block closing tag` ); + parser.error(`Unexpected block closing tag`); } - parser.eat( expected, true ); + parser.eat(expected, true); parser.allowWhitespace(); - parser.eat( '}}', true ); + parser.eat('}}', true); - while ( block.elseif ) { + while (block.elseif) { block.end = parser.index; parser.stack.pop(); block = parser.current(); - if ( block.else ) { + if (block.else) { block.else.end = start; } } // strip leading/trailing whitespace as necessary - if ( !block.children.length ) parser.error( `Empty block`, block.start ); + if (!block.children.length) parser.error(`Empty block`, block.start); - const charBefore = parser.template[ block.start - 1 ]; - const charAfter = parser.template[ parser.index ]; - const trimBefore = !charBefore || whitespace.test( charBefore ); - const trimAfter = !charAfter || whitespace.test( charAfter ); + const charBefore = parser.template[block.start - 1]; + const charAfter = parser.template[parser.index]; + const trimBefore = !charBefore || whitespace.test(charBefore); + const trimAfter = !charAfter || whitespace.test(charAfter); - trimWhitespace( block, trimBefore, trimAfter ); + trimWhitespace(block, trimBefore, trimAfter); 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(); - const expression = readExpression( parser ); + const expression = readExpression(parser); parser.allowWhitespace(); - parser.eat( '}}', true ); + parser.eat('}}', true); block.else = { start: parser.index, @@ -109,17 +110,17 @@ export default function mustache ( parser: Parser ) { ] }; - parser.stack.push( block.else.children[0] ); - } - - else if ( parser.eat( 'else' ) ) { + parser.stack.push(block.else.children[0]); + } 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' ); + if (block.type !== 'IfBlock' && block.type !== 'EachBlock') { + parser.error( + 'Cannot have an {{else}} block outside an {{#if ...}} or {{#each ...}} block' + ); } parser.allowWhitespace(); - parser.eat( '}}', true ); + parser.eat('}}', true); block.else = { start: parser.index, @@ -128,24 +129,22 @@ export default function mustache ( parser: Parser ) { children: [] }; - parser.stack.push( block.else ); - } - - // {{#if foo}} or {{#each foo}} - else if ( parser.eat( '#' ) ) { + parser.stack.push(block.else); + } else if (parser.eat('#')) { + // {{#if foo}} or {{#each foo}} let type; - if ( parser.eat( 'if' ) ) { + if (parser.eat('if')) { type = 'IfBlock'; - } else if ( parser.eat( 'each' ) ) { + } else if (parser.eat('each')) { type = 'EachBlock'; } else { - parser.error( `Expected if or each` ); + parser.error(`Expected if or each`); } parser.requireWhitespace(); - const expression = readExpression( parser ); + const expression = readExpression(parser); const block: Node = { start, @@ -158,54 +157,49 @@ export default function mustache ( parser: Parser ) { parser.allowWhitespace(); // {{#each}} blocks must declare a context – {{#each list as item}} - if ( type === 'EachBlock' ) { - parser.eat( 'as', true ); + if (type === 'EachBlock') { + parser.eat('as', true); parser.requireWhitespace(); - block.context = parser.read( validIdentifier ); // TODO check it's not a keyword - if ( !block.context ) parser.error( `Expected name` ); + block.context = parser.read(validIdentifier); // TODO check it's not a keyword + if (!block.context) parser.error(`Expected name`); parser.allowWhitespace(); - if ( parser.eat( ',' ) ) { + if (parser.eat(',')) { parser.allowWhitespace(); - block.index = parser.read( validIdentifier ); - if ( !block.index ) parser.error( `Expected name` ); + block.index = parser.read(validIdentifier); + if (!block.index) parser.error(`Expected name`); parser.allowWhitespace(); } - if ( parser.eat( '@' ) ) { - block.key = parser.read( validIdentifier ); - if ( !block.key ) parser.error( `Expected name` ); + if (parser.eat('@')) { + block.key = parser.read(validIdentifier); + if (!block.key) parser.error(`Expected name`); parser.allowWhitespace(); } } - parser.eat( '}}', true ); - - parser.current().children.push( block ); - parser.stack.push( block ); - } - - // {{yield}} - else if ( parser.eat( 'yield' ) ) { + parser.eat('}}', true); + parser.current().children.push(block); + parser.stack.push(block); + } else if (parser.eat('yield')) { + // {{yield}} parser.allowWhitespace(); - parser.eat( '}}', true ); + parser.eat('}}', true); parser.current().children.push({ start, end: parser.index, type: 'YieldTag' }); - } - - // {{{raw}}} mustache - else if ( parser.eat( '{' ) ) { - const expression = readExpression( parser ); + } else if (parser.eat('{')) { + // {{{raw}}} mustache + const expression = readExpression(parser); parser.allowWhitespace(); - parser.eat( '}}}', true ); + parser.eat('}}}', true); parser.current().children.push({ start, @@ -213,13 +207,11 @@ export default function mustache ( parser: Parser ) { type: 'RawMustacheTag', expression }); - } - - else { - const expression = readExpression( parser ); + } else { + const expression = readExpression(parser); parser.allowWhitespace(); - parser.eat( '}}', true ); + parser.eat('}}', true); parser.current().children.push({ start, diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 47b8b1c7d6..ec5aaca638 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -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'; @@ -16,60 +20,73 @@ const metaTags = { ':Window': true }; -const specials = new Map( [ - [ 'script', { - read: readScript, - property: 'js' - } ], - [ 'style', { - read: readStyle, - property: 'css' - } ] -] ); +const specials = new Map([ + [ + 'script', + { + read: readScript, + property: 'js' + } + ], + [ + 'style', + { + read: readStyle, + property: 'css' + } + ] +]); // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission -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( ' ' ) ) ], - [ 'rt', new Set( [ 'rt', 'rp' ] ) ], - [ 'rp', new Set( [ 'rt', 'rp' ] ) ], - [ 'optgroup', new Set( [ 'optgroup' ] ) ], - [ 'option', new Set( [ 'option', 'optgroup' ] ) ], - [ 'thead', new Set( [ 'tbody', 'tfoot' ] ) ], - [ 'tbody', new Set( [ 'tbody', 'tfoot' ] ) ], - [ 'tfoot', new Set( [ 'tbody' ] ) ], - [ 'tr', new Set( [ 'tr', 'tbody' ] ) ], - [ 'td', new Set( [ 'td', 'th', 'tr' ] ) ], - [ 'th', new Set( [ 'td', 'th', 'tr' ] ) ], -] ); - -function stripWhitespace ( element ) { - if ( element.children.length ) { +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( + ' ' + ) + ) + ], + ['rt', new Set(['rt', 'rp'])], + ['rp', new Set(['rt', 'rp'])], + ['optgroup', new Set(['optgroup'])], + ['option', new Set(['option', 'optgroup'])], + ['thead', new Set(['tbody', 'tfoot'])], + ['tbody', new Set(['tbody', 'tfoot'])], + ['tfoot', new Set(['tbody'])], + ['tr', new Set(['tr', 'tbody'])], + ['td', new Set(['td', 'th', 'tr'])], + ['th', new Set(['td', 'th', 'tr'])] +]); + +function stripWhitespace(element) { + if (element.children.length) { const firstChild = element.children[0]; - const lastChild = element.children[ element.children.length - 1 ]; + const lastChild = element.children[element.children.length - 1]; - if ( firstChild.type === 'Text' ) { - firstChild.data = trimStart( firstChild.data ); - if ( !firstChild.data ) element.children.shift(); + if (firstChild.type === 'Text') { + firstChild.data = trimStart(firstChild.data); + if (!firstChild.data) element.children.shift(); } - if ( lastChild.type === 'Text' ) { - lastChild.data = trimEnd( lastChild.data ); - if ( !lastChild.data ) element.children.pop(); + if (lastChild.type === 'Text') { + lastChild.data = trimEnd(lastChild.data); + if (!lastChild.data) element.children.pop(); } } } -export default function tag ( parser: Parser ) { +export default function tag(parser: Parser) { const start = parser.index++; let parent = parser.current(); - if ( parser.eat( '!--' ) ) { - const data = parser.readUntil( /-->/ ); - parser.eat( '-->' ); + if (parser.eat('!--')) { + const data = parser.readUntil(/-->/); + parser.eat('-->'); parser.current().children.push({ start, @@ -81,38 +98,48 @@ export default function tag ( parser: Parser ) { return null; } - const isClosingTag = parser.eat( '/' ); + const isClosingTag = parser.eat('/'); - const name = readTagName( parser ); + const name = readTagName(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 ); + 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( `A component can only have one <${name}> tag`, start ); + parser.error(`A component can only have one <${name}> tag`, start); } - parser.metaTags[ name ] = true; + parser.metaTags[name] = true; - if ( parser.stack.length > 1 ) { - parser.error( `<${name}> tags cannot be inside elements or blocks`, start ); + if (parser.stack.length > 1) { + parser.error(`<${name}> tags cannot be inside elements or blocks`, start); } } parser.allowWhitespace(); - if ( isClosingTag ) { - if ( isVoidElementName( name ) ) { - parser.error( `<${name}> is a void element and cannot have children, or a closing tag`, start ); + if (isClosingTag) { + if (isVoidElementName(name)) { + parser.error( + `<${name}> is a void element and cannot have children, or a closing tag`, + start + ); } - if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` ); + if (!parser.eat('>')) parser.error(`Expected '>'`); // close any elements that don't have their own closing tags, e.g. <div><p></div> - while ( parent.name !== name ) { - if ( parent.type !== 'Element' ) parser.error( `</${name}> attempted to close an element that was not open`, start ); + while (parent.name !== name) { + if (parent.type !== 'Element') + parser.error( + `</${name}> attempted to close an element that was not open`, + start + ); parent.end = start; parser.stack.pop(); @@ -121,17 +148,17 @@ export default function tag ( parser: Parser ) { } // strip leading/trailing whitespace as necessary - stripWhitespace( parent ); + stripWhitespace(parent); parent.end = parser.index; parser.stack.pop(); return null; - } else if ( disallowedContents.has( parent.name ) ) { + } else if (disallowedContents.has(parent.name)) { // can this be a child of the parent element, or does it implicitly // close it, like `<li>one<li>two`? - if ( disallowedContents.get( parent.name ).has( name ) ) { - stripWhitespace( parent ); + if (disallowedContents.get(parent.name).has(name)) { + stripWhitespace(parent); parent.end = start; parser.stack.pop(); @@ -142,24 +169,26 @@ export default function tag ( parser: Parser ) { const uniqueNames = new Set(); let attribute; - while ( attribute = readAttribute( parser, uniqueNames ) ) { - attributes.push( attribute ); + while ((attribute = readAttribute(parser, uniqueNames))) { + attributes.push(attribute); parser.allowWhitespace(); } parser.allowWhitespace(); // special cases – top-level <script> and <style> - if ( specials.has( name ) && parser.stack.length === 1 ) { - const special = specials.get( name ); + if (specials.has(name) && parser.stack.length === 1) { + const special = specials.get(name); - if ( parser[ special.property ] ) { + 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 ); - parser[ special.property ] = special.read( parser, start, attributes ); + parser.eat('>', true); + parser[special.property] = special.read(parser, start, attributes); return; } @@ -172,106 +201,118 @@ export default function tag ( parser: Parser ) { children: [] }; - parser.current().children.push( element ); + parser.current().children.push(element); - const selfClosing = parser.eat( '/' ) || isVoidElementName( name ); + const selfClosing = parser.eat('/') || isVoidElementName(name); - parser.eat( '>', true ); + parser.eat('>', true); - if ( selfClosing ) { + if (selfClosing) { element.end = parser.index; - } else if ( name === 'textarea' ) { + } else if (name === 'textarea') { // special case - element.children = readSequence( parser, () => parser.template.slice( parser.index, parser.index + 11 ) === '</textarea>' ); - parser.read( /<\/textarea>/ ); + element.children = readSequence( + parser, + () => + parser.template.slice(parser.index, parser.index + 11) === '</textarea>' + ); + parser.read(/<\/textarea>/); element.end = parser.index; } else { // don't push self-closing elements onto the stack - parser.stack.push( element ); + parser.stack.push(element); } return null; } -function readTagName ( parser: Parser ) { +function readTagName(parser: Parser) { const start = parser.index; - if ( parser.eat( SELF ) ) { + if (parser.eat(SELF)) { // check we're inside a block, otherwise this // will cause infinite recursion let i = parser.stack.length; let legal = false; - while ( i-- ) { + while (i--) { const fragment = parser.stack[i]; - if ( fragment.type === 'IfBlock' || fragment.type === 'EachBlock' ) { + if (fragment.type === 'IfBlock' || fragment.type === 'EachBlock') { legal = true; break; } } - if ( !legal ) { - parser.error( `<${SELF}> components can only exist inside if-blocks or each-blocks`, start ); + if (!legal) { + parser.error( + `<${SELF}> components can only exist inside if-blocks or each-blocks`, + start + ); } return SELF; } - const name = parser.readUntil( /(\s|\/|>)/ ); + const name = parser.readUntil(/(\s|\/|>)/); - if ( name in metaTags ) return name; + if (name in metaTags) return name; - if ( !validTagName.test( name ) ) { - parser.error( `Expected valid tag name`, start ); + if (!validTagName.test(name)) { + parser.error(`Expected valid tag name`, start); } return name; } -function readAttribute ( parser: Parser, uniqueNames ) { +function readAttribute(parser: Parser, uniqueNames) { const start = parser.index; - let name = parser.readUntil( /(\s|=|\/|>)/ ); - if ( !name ) return null; - if ( uniqueNames.has( name ) ) { - parser.error( 'Attributes need to be unique', start ); + let name = parser.readUntil(/(\s|=|\/|>)/); + if (!name) return null; + if (uniqueNames.has(name)) { + parser.error('Attributes need to be unique', start); } - uniqueNames.add( name ); + uniqueNames.add(name); parser.allowWhitespace(); - if ( /^on:/.test( name ) ) { - parser.eat( '=', true ); - return readEventHandlerDirective( parser, start, name.slice( 3 ) ); + if (/^on:/.test(name)) { + parser.eat('=', true); + return readEventHandlerDirective(parser, start, name.slice(3)); } - if ( /^bind:/.test( name ) ) { - return readBindingDirective( parser, start, name.slice( 5 ) ); + if (/^bind:/.test(name)) { + return readBindingDirective(parser, start, name.slice(5)); } - if ( /^ref:/.test( name ) ) { + if (/^ref:/.test(name)) { return { start, end: parser.index, type: 'Ref', - name: name.slice( 4 ) + name: name.slice(4) }; } - const match = /^(in|out|transition):/.exec( name ); - if ( match ) { - return readTransitionDirective( parser, start, name.slice( match[0].length ), match[1] ); + const match = /^(in|out|transition):/.exec(name); + if (match) { + return readTransitionDirective( + parser, + start, + name.slice(match[0].length), + match[1] + ); } let value; // :foo is shorthand for foo='{{foo}}' - if ( /^:\w+$/.test( name ) ) { - name = name.slice( 1 ); - value = getShorthandValue( start + 1, name ); + if (/^:\w+$/.test(name)) { + name = name.slice(1); + value = getShorthandValue(start + 1, name); } else { - value = parser.eat( '=' ) ? readAttributeValue( parser ) : true; + value = parser.eat('=') ? readAttributeValue(parser) : true; } return { @@ -283,42 +324,40 @@ function readAttribute ( parser: Parser, uniqueNames ) { }; } -function readAttributeValue ( parser: Parser ) { - const quoteMark = ( - parser.eat( `'` ) ? `'` : - parser.eat( `"` ) ? `"` : - null - ); +function readAttributeValue(parser: Parser) { + 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; + if (quoteMark) parser.index += 1; return value; } -function getShorthandValue ( start: number, name: string ) { +function getShorthandValue(start: number, name: string) { const end = start + name.length; - return [{ - type: 'AttributeShorthand', - start, - end, - expression: { - type: 'Identifier', + return [ + { + type: 'AttributeShorthand', start, end, - name + expression: { + type: 'Identifier', + start, + end, + name + } } - }]; + ]; } -function readSequence ( parser: Parser, done: () => boolean ) { +function readSequence(parser: Parser, done: () => boolean) { let currentChunk: Node = { start: parser.index, end: null, @@ -328,31 +367,30 @@ function readSequence ( parser: Parser, done: () => boolean ) { const chunks = []; - while ( parser.index < parser.template.length ) { + while (parser.index < parser.template.length) { const index = parser.index; - if ( done() ) { + if (done()) { currentChunk.end = parser.index; - if ( currentChunk.data ) chunks.push( currentChunk ); + if (currentChunk.data) chunks.push(currentChunk); - chunks.forEach( chunk => { - if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data ); + chunks.forEach(chunk => { + if (chunk.type === 'Text') + chunk.data = decodeCharacterReferences(chunk.data); }); return chunks; - } - - else if ( parser.eat( '{{' ) ) { - if ( currentChunk.data ) { + } else if (parser.eat('{{')) { + if (currentChunk.data) { currentChunk.end = index; - chunks.push( currentChunk ); + chunks.push(currentChunk); } - const expression = readExpression( parser ); + const expression = readExpression(parser); parser.allowWhitespace(); - if ( !parser.eat( '}}' ) ) { - parser.error( `Expected }}` ); + if (!parser.eat('}}')) { + parser.error(`Expected }}`); } chunks.push({ @@ -368,12 +406,10 @@ function readSequence ( parser: Parser, done: () => boolean ) { type: 'Text', data: '' }; - } - - else { - currentChunk.data += parser.template[ parser.index++ ]; + } else { + currentChunk.data += parser.template[parser.index++]; } } - parser.error( `Unexpected end of input` ); -} \ No newline at end of file + parser.error(`Unexpected end of input`); +} diff --git a/src/parse/state/text.ts b/src/parse/state/text.ts index cc46772556..a59e00e15f 100644 --- a/src/parse/state/text.ts +++ b/src/parse/state/text.ts @@ -1,20 +1,24 @@ import { decodeCharacterReferences } from '../utils/html'; import { Parser } from '../index'; -export default function text ( parser: Parser ) { +export default function text(parser: Parser) { const start = parser.index; let data = ''; - while ( parser.index < parser.template.length && !parser.match( '<' ) && !parser.match( '{{' ) ) { - data += parser.template[ parser.index++ ]; + while ( + parser.index < parser.template.length && + !parser.match('<') && + !parser.match('{{') + ) { + data += parser.template[parser.index++]; } parser.current().children.push({ start, end: parser.index, type: 'Text', - data: decodeCharacterReferences( data ) + data: decodeCharacterReferences(data) }); return null; diff --git a/src/parse/utils/entities.ts b/src/parse/utils/entities.ts index 4b3c924aff..7b43ced2c1 100644 --- a/src/parse/utils/entities.ts +++ b/src/parse/utils/entities.ts @@ -2031,4 +2031,4 @@ export default { wp: 8472, wr: 8768, xi: 958 -}; \ No newline at end of file +}; diff --git a/src/parse/utils/hash.ts b/src/parse/utils/hash.ts index 02f98dd20a..8f2221ee49 100644 --- a/src/parse/utils/hash.ts +++ b/src/parse/utils/hash.ts @@ -1,8 +1,8 @@ // https://github.com/darkskyapp/string-hash/blob/master/index.js -export default function hash ( str: string ) :number { +export default function hash(str: string): number { let hash = 5381; let i = str.length; - while ( i-- ) hash = ( hash * 33 ) ^ str.charCodeAt( i ); + while (i--) hash = (hash * 33) ^ str.charCodeAt(i); return hash >>> 0; } diff --git a/src/parse/utils/html.ts b/src/parse/utils/html.ts index 4b06db0723..3b26f3bc73 100644 --- a/src/parse/utils/html.ts +++ b/src/parse/utils/html.ts @@ -1,26 +1,62 @@ 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 ) => { +export function decodeCharacterReferences(html: string) { + return html.replace(entityPattern, (match, entity) => { let code; // Handle named entities - if ( entity[0] !== '#' ) { - code = htmlEntities[ entity ]; - } else if ( entity[1] === 'x' ) { - code = parseInt( entity.substring( 2 ), 16 ); + if (entity[0] !== '#') { + code = htmlEntities[entity]; + } else if (entity[1] === 'x') { + code = parseInt(entity.substring(2), 16); } else { - code = parseInt( entity.substring( 1 ), 10 ); + code = parseInt(entity.substring(1), 10); } - if ( !code ) { + if (!code) { return match; } - return String.fromCodePoint( validateCode( code ) ); + return String.fromCodePoint(validateCode(code)); }); } @@ -31,45 +67,45 @@ const NUL = 0; // to replace them ourselves // // Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters -function validateCode ( code: number ) { +function validateCode(code: number) { // line feed becomes generic whitespace - if ( code === 10 ) { + if (code === 10) { return 32; } // ASCII range. (Why someone would use HTML entities for ASCII characters I don't know, but...) - if ( code < 128 ) { + if (code < 128) { return code; } // code points 128-159 are dealt with leniently by browsers, but they're incorrect. We need // to correct the mistake or we'll end up with missing € signs and so on - if ( code <= 159 ) { - return windows1252[ code - 128 ]; + if (code <= 159) { + return windows1252[code - 128]; } // basic multilingual plane - if ( code < 55296 ) { + if (code < 55296) { return code; } // UTF-16 surrogate halves - if ( code <= 57343 ) { + if (code <= 57343) { return NUL; } // rest of the basic multilingual plane - if ( code <= 65535 ) { + if (code <= 65535) { return code; } // supplementary multilingual plane 0x10000 - 0x1ffff - if ( code >= 65536 && code <= 131071 ) { + if (code >= 65536 && code <= 131071) { return code; } // supplementary ideographic plane 0x20000 - 0x2ffff - if ( code >= 131072 && code <= 196607 ) { + if (code >= 131072 && code <= 196607) { return code; } diff --git a/src/server-side-rendering/register.js b/src/server-side-rendering/register.js index 3a1ca58030..442888e99e 100644 --- a/src/server-side-rendering/register.js +++ b/src/server-side-rendering/register.js @@ -2,16 +2,16 @@ import * as fs from 'fs'; import * as path from 'path'; import { compile } from '../index.ts'; -function capitalise ( name ) { - return name[0].toUpperCase() + name.slice( 1 ); +function capitalise(name) { + return name[0].toUpperCase() + name.slice(1); } -require.extensions[ '.html' ] = function ( module, filename ) { - const { code } = compile( fs.readFileSync( filename, 'utf-8' ), { +require.extensions['.html'] = function(module, filename) { + const { code } = compile(fs.readFileSync(filename, 'utf-8'), { filename, - name: capitalise( path.basename( filename ).replace( /\.html$/, '' ) ), + name: capitalise(path.basename(filename).replace(/\.html$/, '')), generate: 'ssr' }); - return module._compile( code, filename ); + return module._compile(code, filename); }; diff --git a/src/shared/_build.js b/src/shared/_build.js index 3d8f1f3ae6..3bbf0298ff 100644 --- a/src/shared/_build.js +++ b/src/shared/_build.js @@ -1,35 +1,38 @@ -const fs = require( 'fs' ); -const path = require( 'path' ); -const acorn = require( 'acorn' ); +const fs = require('fs'); +const path = require('path'); +const acorn = require('acorn'); const declarations = {}; -fs.readdirSync( __dirname ).forEach( file => { - if ( !/^[a-z]+\.js$/.test( file ) ) return; +fs.readdirSync(__dirname).forEach(file => { + if (!/^[a-z]+\.js$/.test(file)) return; - const source = fs.readFileSync( path.join( __dirname, file ), 'utf-8' ); - const ast = acorn.parse( source, { + const source = fs.readFileSync(path.join(__dirname, file), 'utf-8'); + const ast = acorn.parse(source, { ecmaVersion: 6, sourceType: 'module' }); - ast.body.forEach( node => { - if ( node.type !== 'ExportNamedDeclaration' ) return; + ast.body.forEach(node => { + if (node.type !== 'ExportNamedDeclaration') return; const declaration = node.declaration; - if ( !declaration ) return; + 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 ); + 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' )};` ); \ No newline at end of file +fs.writeFileSync( + 'src/generators/dom/shared.ts', + `// this file is auto-generated, do not edit it +export default ${JSON.stringify(declarations, null, '\t')};` +); diff --git a/src/shared/dom.js b/src/shared/dom.js index 97c227e7f7..11f178317b 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -1,68 +1,68 @@ -export function appendNode ( node, target ) { - target.appendChild( node ); +export function appendNode(node, target) { + target.appendChild(node); } -export function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +export function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -export function detachNode ( node ) { - node.parentNode.removeChild( node ); +export function detachNode(node) { + node.parentNode.removeChild(node); } -export function detachBetween ( before, after ) { - while ( before.nextSibling && before.nextSibling !== after ) { - before.parentNode.removeChild( before.nextSibling ); +export function detachBetween(before, after) { + while (before.nextSibling && before.nextSibling !== after) { + before.parentNode.removeChild(before.nextSibling); } } // TODO this is out of date -export function destroyEach ( iterations, detach, start ) { - for ( var i = start; i < iterations.length; i += 1 ) { - if ( iterations[i] ) iterations[i].destroy( detach ); +export function destroyEach(iterations, detach, start) { + for (var i = start; i < iterations.length; i += 1) { + if (iterations[i]) iterations[i].destroy(detach); } } -export function createElement ( name ) { - return document.createElement( name ); +export function createElement(name) { + return document.createElement(name); } -export function createSvgElement ( name ) { - return document.createElementNS( 'http://www.w3.org/2000/svg', name ); +export function createSvgElement(name) { + return document.createElementNS('http://www.w3.org/2000/svg', name); } -export function createText ( data ) { - return document.createTextNode( data ); +export function createText(data) { + return document.createTextNode(data); } -export function createComment () { - return document.createComment( '' ); +export function createComment() { + return document.createComment(''); } -export function addEventListener ( node, event, handler ) { - node.addEventListener( event, handler, false ); +export function addEventListener(node, event, handler) { + node.addEventListener(event, handler, false); } -export function removeEventListener ( node, event, handler ) { - node.removeEventListener( event, handler, false ); +export function removeEventListener(node, event, handler) { + node.removeEventListener(event, handler, false); } -export function setAttribute ( node, attribute, value ) { - node.setAttribute( attribute, value ); +export function setAttribute(node, attribute, value) { + node.setAttribute(attribute, value); } -export function setXlinkAttribute ( node, attribute, value ) { - node.setAttributeNS( 'http://www.w3.org/1999/xlink', attribute, value ); +export function setXlinkAttribute(node, attribute, value) { + node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); } -export function getBindingGroupValue ( group ) { +export function getBindingGroupValue(group) { var value = []; - for ( var i = 0; i < group.length; i += 1 ) { - if ( group[i].checked ) value.push( group[i].__value ); + for (var i = 0; i < group.length; i += 1) { + if (group[i].checked) value.push(group[i].__value); } return value; } -export function toNumber ( value ) { +export function toNumber(value) { return value === '' ? undefined : +value; -} \ No newline at end of file +} diff --git a/src/shared/index.js b/src/shared/index.js index d31bbffc17..b7b5ff7b64 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -3,109 +3,116 @@ export * from './dom.js'; export * from './transitions.js'; export * from './utils.js'; -export function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +export function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -export function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +export function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -export function get ( key ) { - return key ? this._state[ key ] : this._state; +export function get(key) { + return key ? this._state[key] : this._state; } -export function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +export function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -export function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +export function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -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 + "'"; +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 + "'"; - throw new Error( message ); + throw new Error(message); } - return observe.call( this, key, callback, options ); + return observe.call(this, key, callback, options); } -export function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +export function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -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" ); - return this.on( 'destroy', 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" + ); + return this.on('destroy', handler); } - return on.call( this, eventName, handler ); + return on.call(this, eventName, handler); } -export function set ( newState ) { - this._set( assign( {}, newState ) ); +export function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -export function _flush () { - if ( !this._renderHooks ) return; +export function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/src/shared/transitions.js b/src/shared/transitions.js index 1fe7aa28e6..ded3a35b4d 100644 --- a/src/shared/transitions.js +++ b/src/shared/transitions.js @@ -1,50 +1,60 @@ import { assign, noop } from './utils.js'; -export function linear ( t ) { +export function linear(t) { return t; } -export function generateKeyframes ( a, b, delta, duration, ease, fn, node, style ) { - var id = '__svelte' + ~~( Math.random() * 1e9 ); // TODO make this more robust +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'; + for (var p = 0; p <= 1; p += 16.666 / duration) { + var t = a + delta * ease(p); + keyframes += p * 100 + '%{' + fn(t) + '}\n'; } - keyframes += '100% {' + fn( b ) + '}\n}'; + keyframes += '100% {' + fn(b) + '}\n}'; style.textContent += keyframes; - document.head.appendChild( style ); + document.head.appendChild(style); - node.style.animation = ( node.style.animation || '' ).split( ',' ) - .filter( function ( anim ) { + 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 ) ); + return anim && (delta < 0 || !/__svelte/.test(anim)); }) - .concat( id + ' ' + duration + 'ms linear 1 forwards' ) - .join( ', ' ); + .concat(id + ' ' + duration + 'ms linear 1 forwards') + .join(', '); } -export function wrapTransition ( node, fn, params, intro, outgroup ) { - var obj = fn( node, params ); +export function wrapTransition(node, fn, params, intro, outgroup) { + var obj = fn(node, params); var duration = obj.duration || 300; var ease = obj.easing || linear; var cssText; // TODO share <style> tag between all transitions? - if ( obj.css ) { - var style = document.createElement( 'style' ); + if (obj.css) { + var style = document.createElement('style'); } - if ( intro ) { - if ( obj.css && obj.delay ) { + if (intro) { + if (obj.css && obj.delay) { cssText = node.style.cssText; - node.style.cssText += obj.css( 0 ); + node.style.cssText += obj.css(0); } - if ( obj.tick ) obj.tick( 0 ); + if (obj.tick) obj.tick(0); } return { @@ -52,58 +62,67 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) { running: false, program: null, pending: null, - run: function ( intro, callback ) { + run: function(intro, callback) { var program = { - start: window.performance.now() + ( obj.delay || 0 ), + start: window.performance.now() + (obj.delay || 0), intro: intro, callback: callback }; - if ( obj.delay ) { + if (obj.delay) { this.pending = program; } else { - this.start( program ); + this.start(program); } - if ( !this.running ) { + if (!this.running) { this.running = true; - transitionManager.add( this ); + transitionManager.add(this); } }, - start: function ( program ) { + start: function(program) { program.a = this.t; program.b = program.intro ? 1 : 0; program.delta = program.b - program.a; - program.duration = duration * Math.abs( program.b - program.a ); + program.duration = duration * Math.abs(program.b - program.a); program.end = program.start + program.duration; - if ( obj.css ) { - if ( obj.delay ) node.style.cssText = cssText; - generateKeyframes( program.a, program.b, program.delta, program.duration, ease, obj.css, node, style ); + if (obj.css) { + if (obj.delay) node.style.cssText = cssText; + generateKeyframes( + program.a, + program.b, + program.delta, + program.duration, + ease, + obj.css, + node, + style + ); } this.program = program; this.pending = null; }, - update: function ( now ) { + update: function(now) { var program = this.program; - if ( !program ) return; + if (!program) return; var p = now - program.start; - this.t = program.a + program.delta * ease( p / program.duration ); - if ( obj.tick ) obj.tick( this.t ); + this.t = program.a + program.delta * ease(p / program.duration); + if (obj.tick) obj.tick(this.t); }, - done: function () { + done: function() { this.t = this.program.b; - if ( obj.tick ) obj.tick( this.t ); - if ( obj.css ) document.head.removeChild( style ); + if (obj.tick) obj.tick(this.t); + if (obj.css) document.head.removeChild(style); this.program.callback(); this.program = null; this.running = !!this.pending; }, - abort: function () { - if ( obj.tick ) obj.tick( 1 ); - if ( obj.css ) document.head.removeChild( style ); + abort: function() { + if (obj.tick) obj.tick(1); + if (obj.css) document.head.removeChild(style); this.program = this.pending = null; this.running = false; } @@ -115,42 +134,42 @@ export var transitionManager = { transitions: [], bound: null, - add: function ( transition ) { - this.transitions.push( transition ); + add: function(transition) { + this.transitions.push(transition); - if ( !this.running ) { + if (!this.running) { this.running = true; this.next(); } }, - next: function () { + next: function() { this.running = false; var now = window.performance.now(); var i = this.transitions.length; - while ( i-- ) { + while (i--) { var transition = this.transitions[i]; - if ( transition.program && now >= transition.program.end ) { + if (transition.program && now >= transition.program.end) { transition.done(); } - if ( transition.pending && now >= transition.pending.start ) { - transition.start( transition.pending ); + if (transition.pending && now >= transition.pending.start) { + transition.start(transition.pending); } - if ( transition.running ) { - transition.update( now ); + if (transition.running) { + transition.update(now); this.running = true; - } else if ( !transition.pending ) { - this.transitions.splice( i, 1 ); + } else if (!transition.pending) { + this.transitions.splice(i, 1); } } - if ( this.running ) { - requestAnimationFrame( this.bound || ( this.bound = this.next.bind( this ) ) ); + if (this.running) { + requestAnimationFrame(this.bound || (this.bound = this.next.bind(this))); } } -}; \ No newline at end of file +}; diff --git a/src/shared/utils.js b/src/shared/utils.js index 9eb3ed2cee..4cf2f32a1d 100644 --- a/src/shared/utils.js +++ b/src/shared/utils.js @@ -1,10 +1,13 @@ -export function noop () {} +export function noop() {} -export function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +export function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; diff --git a/src/utils/CodeBuilder.ts b/src/utils/CodeBuilder.ts index 79c36a96c6..92c88ea483 100644 --- a/src/utils/CodeBuilder.ts +++ b/src/utils/CodeBuilder.ts @@ -4,26 +4,28 @@ enum ChunkType { } export default class CodeBuilder { - result: string - first: ChunkType - last: ChunkType - lastCondition: string + result: string; + first: ChunkType; + last: ChunkType; + lastCondition: string; - constructor ( str = '' ) { + 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; this.lastCondition = null; } - addConditionalLine ( condition: string, line: string ) { - if ( condition === this.lastCondition ) { + addConditionalLine(condition: string, line: string) { + if (condition === this.lastCondition) { this.result += `\n\t${line}`; } else { - if ( this.lastCondition ) { + if (this.lastCondition) { this.result += `\n}\n\n`; } @@ -34,69 +36,69 @@ export default class CodeBuilder { this.last = ChunkType.Block; } - addLine ( line: string ) { - if ( this.lastCondition ) { + addLine(line: string) { + if (this.lastCondition) { this.result += `\n}`; this.lastCondition = null; } - if ( this.last === ChunkType.Block ) { + if (this.last === ChunkType.Block) { this.result += `\n\n${line}`; - } else if ( this.last === ChunkType.Line ) { + } else if (this.last === ChunkType.Line) { this.result += `\n${line}`; } else { this.result += line; } this.last = ChunkType.Line; - if ( !this.first ) this.first = ChunkType.Line; + if (!this.first) this.first = ChunkType.Line; } - addLineAtStart ( line: string ) { - if ( this.first === ChunkType.Block ) { + addLineAtStart(line: string) { + if (this.first === ChunkType.Block) { this.result = `${line}\n\n${this.result}`; - } else if ( this.first === ChunkType.Line ) { + } else if (this.first === ChunkType.Line) { this.result = `${line}\n${this.result}`; } else { this.result += line; } this.first = ChunkType.Line; - if ( !this.last ) this.last = ChunkType.Line; + if (!this.last) this.last = ChunkType.Line; } - addBlock ( block: string ) { - if ( this.lastCondition ) { + addBlock(block: string) { + if (this.lastCondition) { this.result += `\n}`; this.lastCondition = null; } - if ( this.result ) { + if (this.result) { this.result += `\n\n${block}`; } else { this.result += block; } this.last = ChunkType.Block; - if ( !this.first ) this.first = ChunkType.Block; + if (!this.first) this.first = ChunkType.Block; } - addBlockAtStart ( block: string ) { - if ( this.result ) { + addBlockAtStart(block: string) { + if (this.result) { this.result = `${block}\n\n${this.result}`; } else { this.result += block; } this.first = ChunkType.Block; - if ( !this.last ) this.last = ChunkType.Block; + if (!this.last) this.last = ChunkType.Block; } - isEmpty () { + isEmpty() { return this.result === ''; } - toString () { - return this.result.trim() + ( this.lastCondition ? `\n}` : `` ); + toString() { + return this.result.trim() + (this.lastCondition ? `\n}` : ``); } } diff --git a/src/utils/CompileError.ts b/src/utils/CompileError.ts index 85d713dc79..1af9b39c2f 100644 --- a/src/utils/CompileError.ts +++ b/src/utils/CompileError.ts @@ -2,24 +2,30 @@ 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 ) { - super( message ); + constructor( + message: string, + template: string, + index: number, + filename: string + ) { + super(message); - const { line, column } = locate( template, index ); + const { line, column } = locate(template, index); this.loc = { line: line + 1, column }; this.pos = index; this.filename = filename; - this.frame = getCodeFrame( template, line, column ); + this.frame = getCodeFrame(template, line, column); } - toString () { - return `${this.message} (${this.loc.line}:${this.loc.column})\n${this.frame}`; + toString() { + return `${this.message} (${this.loc.line}:${this.loc.column})\n${this + .frame}`; } -} \ No newline at end of file +} diff --git a/src/utils/__test__.ts b/src/utils/__test__.ts index 99e90bb76a..9f1cc87768 100644 --- a/src/utils/__test__.ts +++ b/src/utils/__test__.ts @@ -2,25 +2,25 @@ import * as assert from 'assert'; import deindent from './deindent.js'; import CodeBuilder from './CodeBuilder'; -describe( 'deindent', () => { - it( 'deindents a simple string', () => { +describe('deindent', () => { + it('deindents a simple string', () => { const deindented = deindent` deindent me please `; - assert.equal( deindented, `deindent me please` ); + assert.equal(deindented, `deindent me please`); }); - it( 'deindents a multiline string', () => { + it('deindents a multiline string', () => { const deindented = deindent` deindent me please and me as well `; - assert.equal( deindented, `deindent me please\nand me as well` ); + assert.equal(deindented, `deindent me please\nand me as well`); }); - it( 'preserves indentation of inserted values', () => { + it('preserves indentation of inserted values', () => { const insert = deindent` line one line two @@ -32,45 +32,47 @@ describe( 'deindent', () => { after `; - assert.equal( deindented, `before\n\tline one\n\tline two\nafter` ); + assert.equal(deindented, `before\n\tline one\n\tline two\nafter`); }); }); -describe( 'CodeBuilder', () => { - it( 'creates an empty block', () => { +describe('CodeBuilder', () => { + it('creates an empty block', () => { const builder = new CodeBuilder(); - assert.equal( builder.toString(), '' ); + assert.equal(builder.toString(), ''); }); - it( 'creates a block with a line', () => { + it('creates a block with a line', () => { const builder = new CodeBuilder(); - builder.addLine( 'var answer = 42;' ); - assert.equal( builder.toString(), 'var answer = 42;' ); + builder.addLine('var answer = 42;'); + assert.equal(builder.toString(), 'var answer = 42;'); }); - it( 'creates a block with two lines', () => { + it('creates a block with two lines', () => { const builder = new CodeBuilder(); - builder.addLine( 'var problems = 99;' ); - builder.addLine( 'var answer = 42;' ); - assert.equal( builder.toString(), 'var problems = 99;\nvar answer = 42;' ); + builder.addLine('var problems = 99;'); + builder.addLine('var answer = 42;'); + assert.equal(builder.toString(), 'var problems = 99;\nvar answer = 42;'); }); - it( 'adds newlines around blocks', () => { + it('adds newlines around blocks', () => { const builder = new CodeBuilder(); - builder.addLine( '// line 1' ); - builder.addLine( '// line 2' ); - builder.addBlock( deindent` + builder.addLine('// line 1'); + builder.addLine('// line 2'); + builder.addBlock(deindent` if ( foo ) { bar(); } - ` ); - builder.addLine( '// line 3' ); - builder.addLine( '// line 4' ); + `); + builder.addLine('// line 3'); + builder.addLine('// line 4'); - assert.equal( builder.toString(), deindent` + assert.equal( + builder.toString(), + deindent` // line 1 // line 2 @@ -80,31 +82,34 @@ describe( 'CodeBuilder', () => { // line 3 // line 4 - ` ); + ` + ); }); - it( 'nests codebuilders with correct indentation', () => { + it('nests codebuilders with correct indentation', () => { const child = new CodeBuilder(); - child.addBlock( deindent` + child.addBlock(deindent` var obj = { answer: 42 }; - ` ); + `); const builder = new CodeBuilder(); - builder.addLine( '// line 1' ); - builder.addLine( '// line 2' ); - builder.addBlock( deindent` + builder.addLine('// line 1'); + builder.addLine('// line 2'); + builder.addBlock(deindent` if ( foo ) { ${child} } - ` ); - builder.addLine( '// line 3' ); - builder.addLine( '// line 4' ); + `); + builder.addLine('// line 3'); + builder.addLine('// line 4'); - assert.equal( builder.toString(), deindent` + assert.equal( + builder.toString(), + deindent` // line 1 // line 2 @@ -116,57 +121,70 @@ describe( 'CodeBuilder', () => { // line 3 // line 4 - ` ); + ` + ); }); - it( 'adds a line at start', () => { + it('adds a line at start', () => { const builder = new CodeBuilder(); - builder.addLine( '// second' ); - builder.addLineAtStart( '// first' ); + 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', () => { + it('adds a line at start before a block', () => { const builder = new CodeBuilder(); - builder.addBlock( '// second' ); - builder.addLineAtStart( '// first' ); + builder.addBlock('// second'); + builder.addLineAtStart('// first'); - assert.equal( builder.toString(), deindent` + assert.equal( + builder.toString(), + deindent` // first // second - ` ); + ` + ); }); - it( 'adds a block at start', () => { + it('adds a block at start', () => { const builder = new CodeBuilder(); - builder.addLine( '// second' ); - builder.addBlockAtStart( '// first' ); + 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', () => { + it('adds a block at start before a block', () => { const builder = new CodeBuilder(); - builder.addBlock( '// second' ); - builder.addBlockAtStart( '// first' ); + builder.addBlock('// second'); + builder.addBlockAtStart('// first'); - assert.equal( builder.toString(), deindent` + assert.equal( + builder.toString(), + deindent` // first // second - ` ); + ` + ); }); }); diff --git a/src/utils/annotateWithScopes.ts b/src/utils/annotateWithScopes.ts index 7989065f38..323523188e 100644 --- a/src/utils/annotateWithScopes.ts +++ b/src/utils/annotateWithScopes.ts @@ -1,41 +1,35 @@ import { walk } from 'estree-walker'; import { Node } from '../interfaces'; -export default function annotateWithScopes ( expression: Node ) { - let scope = new Scope( null, false ); - - walk( expression, { - enter ( node: Node ) { - if ( /Function/.test( node.type ) ) { - if ( node.type === 'FunctionDeclaration' ) { - scope.declarations.add( node.id.name ); +export default function annotateWithScopes(expression: Node) { + let scope = new Scope(null, false); + + walk(expression, { + enter(node: Node) { + if (/Function/.test(node.type)) { + if (node.type === 'FunctionDeclaration') { + scope.declarations.add(node.id.name); } else { - node._scope = scope = new Scope( scope, false ); - if ( node.id ) scope.declarations.add( node.id.name ); + node._scope = scope = new Scope(scope, false); + if (node.id) scope.declarations.add(node.id.name); } - node.params.forEach( ( param: Node ) => { - extractNames( param ).forEach( name => { - scope.declarations.add( name ); + node.params.forEach((param: Node) => { + extractNames(param).forEach(name => { + scope.declarations.add(name); }); }); - } - - else if ( /For(?:In|Of)Statement/.test( node.type ) ) { - node._scope = scope = new Scope( scope, true ); - } - - else if ( node.type === 'BlockStatement' ) { - node._scope = scope = new Scope( scope, true ); - } - - else if ( /(Function|Class|Variable)Declaration/.test( node.type ) ) { - scope.addDeclaration( node ); + } else if (/For(?:In|Of)Statement/.test(node.type)) { + node._scope = scope = new Scope(scope, true); + } else if (node.type === 'BlockStatement') { + node._scope = scope = new Scope(scope, true); + } else if (/(Function|Class|Variable)Declaration/.test(node.type)) { + scope.addDeclaration(node); } }, - leave ( node: Node ) { - if ( node._scope ) { + leave(node: Node) { + if (node._scope) { scope = scope.parent; } } @@ -45,63 +39,65 @@ 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 ) { + constructor(parent: Scope, block: boolean) { this.parent = parent; this.block = block; this.declarations = new Set(); } - addDeclaration ( node: Node ) { - if ( node.kind === 'var' && !this.block && this.parent ) { - this.parent.addDeclaration( node ); - } else if ( node.type === 'VariableDeclaration' ) { - node.declarations.forEach( ( declarator: Node ) => { - extractNames( declarator.id ).forEach( name => { - this.declarations.add( name ); + addDeclaration(node: Node) { + if (node.kind === 'var' && !this.block && this.parent) { + this.parent.addDeclaration(node); + } else if (node.type === 'VariableDeclaration') { + node.declarations.forEach((declarator: Node) => { + extractNames(declarator.id).forEach(name => { + this.declarations.add(name); }); }); } else { - this.declarations.add( node.id.name ); + this.declarations.add(node.id.name); } } - has ( name: string ) :boolean { - return this.declarations.has( name ) || this.parent && this.parent.has( name ); + has(name: string): boolean { + return ( + this.declarations.has(name) || (this.parent && this.parent.has(name)) + ); } } -function extractNames ( param: Node ) { +function extractNames(param: Node) { const names: string[] = []; - extractors[ param.type ]( names, param ); + extractors[param.type](names, param); return names; } const extractors = { - Identifier ( names: string[], param: Node ) { - names.push( param.name ); + Identifier(names: string[], param: Node) { + names.push(param.name); }, - ObjectPattern ( names: string[], param: Node ) { - param.properties.forEach( ( prop: Node ) => { - extractors[ prop.value.type ]( names, prop.value ); + ObjectPattern(names: string[], param: Node) { + param.properties.forEach((prop: Node) => { + extractors[prop.value.type](names, prop.value); }); }, - ArrayPattern ( names: string[], param: Node ) { - param.elements.forEach( ( element: Node ) => { - if ( element ) extractors[ element.type ]( names, element ); + ArrayPattern(names: string[], param: Node) { + param.elements.forEach((element: Node) => { + if (element) extractors[element.type](names, element); }); }, - RestElement ( names: string[], param: Node ) { - extractors[ param.argument.type ]( names, param.argument ); + RestElement(names: string[], param: Node) { + extractors[param.argument.type](names, param.argument); }, - AssignmentPattern ( names: string[], param: Node ) { - extractors[ param.left.type ]( names, param.left ); + AssignmentPattern(names: string[], param: Node) { + extractors[param.left.type](names, param.left); } }; diff --git a/src/utils/deindent.js b/src/utils/deindent.js index be20e42ef4..6c335ca6f0 100644 --- a/src/utils/deindent.js +++ b/src/utils/deindent.js @@ -1,40 +1,41 @@ const start = /\n(\t+)/; -export default function deindent ( strings, ...values ) { - const indentation = start.exec( strings[0] )[1]; - const pattern = new RegExp( `^${indentation}`, 'gm' ); +export default function deindent(strings, ...values) { + const indentation = start.exec(strings[0])[1]; + const pattern = new RegExp(`^${indentation}`, 'gm'); - let result = strings[0].replace( start, '' ).replace( pattern, '' ); + let result = strings[0].replace(start, '').replace(pattern, ''); - let trailingIndentation = getTrailingIndentation( result ); + let trailingIndentation = getTrailingIndentation(result); - for ( let i = 1; i < strings.length; i += 1 ) { - let expression = values[ i - 1 ]; - const string = strings[i].replace( pattern, '' ); + for (let i = 1; i < strings.length; i += 1) { + let expression = values[i - 1]; + const string = strings[i].replace(pattern, ''); - if ( Array.isArray( expression ) ) { - expression = expression.length ? expression.join( '\n' ) : null; + if (Array.isArray(expression)) { + expression = expression.length ? expression.join('\n') : null; } - if ( expression || expression === '' ) { - const value = String( expression ).replace( /\n/g, `\n${trailingIndentation}` ); + if (expression || expression === '') { + 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; + while (/\s/.test(result[c - 1])) c -= 1; + result = result.slice(0, c) + string; } - trailingIndentation = getTrailingIndentation( result ); + trailingIndentation = getTrailingIndentation(result); } - return result.trim().replace( /\t+$/gm, '' ); + return result.trim().replace(/\t+$/gm, ''); } -function getTrailingIndentation ( str ) { +function getTrailingIndentation(str) { let i = str.length; - while ( str[ i - 1 ] === ' ' || str[ i - 1 ] === '\t' ) i -= 1; - return str.slice( i, str.length ); + while (str[i - 1] === ' ' || str[i - 1] === '\t') i -= 1; + return str.slice(i, str.length); } diff --git a/src/utils/flattenReference.ts b/src/utils/flattenReference.ts index 90946f3a8b..b44fb6e27f 100644 --- a/src/utils/flattenReference.ts +++ b/src/utils/flattenReference.ts @@ -1,21 +1,23 @@ import { Node } from '../interfaces'; -export default function flatten ( node: Node ) { +export default function flatten(node: Node) { const parts = []; const propEnd = node.end; - while ( node.type === 'MemberExpression' ) { - if ( node.computed ) return null; - parts.unshift( node.property.name ); + while (node.type === 'MemberExpression') { + if (node.computed) return null; + parts.unshift(node.property.name); node = node.object; } const propStart = node.end; - const name = node.type === 'Identifier' ? node.name : node.type === 'ThisExpression' ? 'this' : null; + const name = node.type === 'Identifier' + ? node.name + : node.type === 'ThisExpression' ? 'this' : null; - if ( !name ) return null; + if (!name) return null; - parts.unshift( name ); + parts.unshift(name); return { name, parts, keypath: `${name}[✂${propStart}-${propEnd}✂]` }; } diff --git a/src/utils/getCodeFrame.ts b/src/utils/getCodeFrame.ts index b87e267ff2..eb237841ae 100644 --- a/src/utils/getCodeFrame.ts +++ b/src/utils/getCodeFrame.ts @@ -1,31 +1,36 @@ import spaces from './spaces.js'; -function tabsToSpaces ( str: string ) { - return str.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) ); +function tabsToSpaces(str: string) { + return str.replace(/^\t+/, match => match.split('\t').join(' ')); } -export default function getCodeFrame ( source: string, line: number, column: number ) { - const lines = source.split( '\n' ); +export default function getCodeFrame( + source: string, + line: number, + column: number +) { + const lines = source.split('\n'); - const frameStart = Math.max( 0, line - 2 ); - const frameEnd = Math.min( line + 3, lines.length ); + const frameStart = Math.max(0, line - 2); + const frameEnd = Math.min(line + 3, lines.length); - const digits = String( frameEnd + 1 ).length; + const digits = String(frameEnd + 1).length; return lines - .slice( frameStart, frameEnd ) - .map( ( str, i ) => { + .slice(frameStart, frameEnd) + .map((str, i) => { const isErrorLine = frameStart + i === line; - let lineNum = String( i + frameStart + 1 ); - while ( lineNum.length < digits ) lineNum = ` ${lineNum}`; + let lineNum = String(i + frameStart + 1); + while (lineNum.length < digits) lineNum = ` ${lineNum}`; - if ( isErrorLine ) { - const indicator = spaces( digits + 2 + tabsToSpaces( str.slice( 0, column ) ).length ) + '^'; - return `${lineNum}: ${tabsToSpaces( str )}\n${indicator}`; + if (isErrorLine) { + const indicator = + spaces(digits + 2 + tabsToSpaces(str.slice(0, column)).length) + '^'; + return `${lineNum}: ${tabsToSpaces(str)}\n${indicator}`; } - return `${lineNum}: ${tabsToSpaces( str )}`; + return `${lineNum}: ${tabsToSpaces(str)}`; }) - .join( '\n' ); + .join('\n'); } diff --git a/src/utils/globalWhitelist.ts b/src/utils/globalWhitelist.ts index 8472308174..9a684a4552 100644 --- a/src/utils/globalWhitelist.ts +++ b/src/utils/globalWhitelist.ts @@ -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' +]); diff --git a/src/utils/isReference.ts b/src/utils/isReference.ts index d486cf5c26..9a5f3688fb 100644 --- a/src/utils/isReference.ts +++ b/src/utils/isReference.ts @@ -1,29 +1,33 @@ import { Node } from '../interfaces'; -export default function isReference ( node: Node, parent: Node ): boolean { - if ( node.type === 'MemberExpression' ) { - return !node.computed && isReference( node.object, node ); +export default function isReference(node: Node, parent: Node): boolean { + if (node.type === 'MemberExpression') { + return !node.computed && isReference(node.object, node); } - if ( node.type === 'Identifier' ) { + if (node.type === 'Identifier') { // the only time we could have an identifier node without a parent is // if it's the entire body of a function without a block statement – // i.e. an arrow function expression like `a => a` - if ( !parent ) return true; + 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; + if (parent.type === 'MethodDefinition') return false; // disregard the `bar` in `export { foo as bar }` - if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return; + if (parent.type === 'ExportSpecifier' && node !== parent.local) return; return true; } diff --git a/src/utils/isVoidElementName.ts b/src/utils/isVoidElementName.ts index b640cafbe8..2396a0cde2 100644 --- a/src/utils/isVoidElementName.ts +++ b/src/utils/isVoidElementName.ts @@ -1,5 +1,5 @@ const voidElementNames = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/; -export default function isVoidElementName ( name: string ) { - return voidElementNames.test( name ) || name.toLowerCase() === '!doctype'; +export default function isVoidElementName(name: string) { + return voidElementNames.test(name) || name.toLowerCase() === '!doctype'; } diff --git a/src/utils/namespaces.ts b/src/utils/namespaces.ts index 595d0f0def..975f01a4d1 100644 --- a/src/utils/namespaces.ts +++ b/src/utils/namespaces.ts @@ -1,13 +1,23 @@ -export const html = 'http://www.w3.org/1999/xhtml'; +export const html = 'http://www.w3.org/1999/xhtml'; export const mathml = 'http://www.w3.org/1998/Math/MathML'; -export const svg = 'http://www.w3.org/2000/svg'; -export const xlink = 'http://www.w3.org/1999/xlink'; -export const xml = 'http://www.w3.org/XML/1998/namespace'; -export const xmlns = 'http://www.w3.org/2000/xmlns'; +export const svg = 'http://www.w3.org/2000/svg'; +export const xlink = 'http://www.w3.org/1999/xlink'; +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 }; diff --git a/src/utils/removeNode.ts b/src/utils/removeNode.ts index ec3a7152fa..3d237399a6 100644 --- a/src/utils/removeNode.ts +++ b/src/utils/removeNode.ts @@ -6,52 +6,52 @@ const keys = { }; const offsets = { - ObjectExpression: [ 1, -1 ], - Program: [ 0, 0 ] + ObjectExpression: [1, -1], + Program: [0, 0] }; -export function removeNode ( code, parent: Node, node: Node ) { - const key = keys[ parent.type ]; - const offset = offsets[ parent.type ]; - if ( !key || !offset ) throw new Error( `not implemented: ${parent.type}` ); +export function removeNode(code, parent: Node, node: Node) { + const key = keys[parent.type]; + const offset = offsets[parent.type]; + if (!key || !offset) throw new Error(`not implemented: ${parent.type}`); - const list = parent[ key ]; - const i = list.indexOf( node ); - if ( i === -1 ) throw new Error( 'node not in list' ); + const list = parent[key]; + const i = list.indexOf(node); + if (i === -1) throw new Error('node not in list'); let a; let b; - if ( list.length === 1 ) { + if (list.length === 1) { // remove everything, leave {} a = parent.start + offset[0]; b = parent.end + offset[1]; - } else if ( i === 0 ) { + } else if (i === 0) { // remove everything before second node, including comments a = parent.start + offset[0]; - while ( /\s/.test( code.original[a] ) ) a += 1; + while (/\s/.test(code.original[a])) a += 1; b = list[i].end; - while ( /[\s,]/.test( code.original[b] ) ) b += 1; + while (/[\s,]/.test(code.original[b])) b += 1; } else { // remove the end of the previous node to the end of this one - a = list[ i - 1 ].end; + a = list[i - 1].end; b = node.end; } - code.remove( a, b ); - list.splice( i, 1 ); + code.remove(a, b); + list.splice(i, 1); return; } -export function removeObjectKey ( code, node, key ) { - if ( node.type !== 'ObjectExpression' ) return; +export function removeObjectKey(code, node, key) { + if (node.type !== 'ObjectExpression') return; let i = node.properties.length; - while ( i-- ) { + while (i--) { const property = node.properties[i]; - if ( property.key.type === 'Identifier' && property.key.name === key ) { - removeNode( code, node, property ); + if (property.key.type === 'Identifier' && property.key.name === key) { + removeNode(code, node, property); } } } diff --git a/src/utils/reservedNames.ts b/src/utils/reservedNames.ts index cc44bbd873..a5ecd09fbd 100644 --- a/src/utils/reservedNames.ts +++ b/src/utils/reservedNames.ts @@ -1,6 +1,55 @@ -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' ); +reservedNames.add('state'); -export default reservedNames; \ No newline at end of file +export default reservedNames; diff --git a/src/utils/spaces.js b/src/utils/spaces.js index 26c818fd8d..b1c7a47498 100644 --- a/src/utils/spaces.js +++ b/src/utils/spaces.js @@ -1,5 +1,5 @@ -export default function spaces ( i ) { +export default function spaces(i) { let result = ''; - while ( i-- ) result += ' '; + while (i--) result += ' '; return result; } diff --git a/src/utils/trim.ts b/src/utils/trim.ts index 3c339a159b..387b368826 100644 --- a/src/utils/trim.ts +++ b/src/utils/trim.ts @@ -1,15 +1,15 @@ import { whitespace } from './patterns'; -export function trimStart ( str: string ) { +export function trimStart(str: string) { let i = 0; - while ( whitespace.test( str[i] ) ) i += 1; + while (whitespace.test(str[i])) i += 1; - return str.slice( i ); + return str.slice(i); } -export function trimEnd ( str: string ) { +export function trimEnd(str: string) { let i = str.length; - while ( whitespace.test( str[ i - 1 ] ) ) i -= 1; + while (whitespace.test(str[i - 1])) i -= 1; - return str.slice( 0, i ); + return str.slice(0, i); } diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index b091a90db3..b09cece0e4 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -6,51 +6,59 @@ 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 ) { +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 ); + 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 ( meta.has( node.name ) ) { - return meta.get( node.name )( validator, node ); + if (meta.has(node.name)) { + return meta.get(node.name)(validator, node); } elementDepth += 1; - validateElement( validator, node ); - } else if ( node.type === 'EachBlock' ) { - if ( validator.helpers.has( node.context ) ) { + validateElement(validator, node); + } else if (node.type === 'EachBlock') { + if (validator.helpers.has(node.context)) { let c = node.expression.end; // find start of context - while ( /\s/.test( validator.source[c] ) ) c += 1; + while (/\s/.test(validator.source[c])) c += 1; c += 2; - while ( /\s/.test( validator.source[c] ) ) c += 1; + while (/\s/.test(validator.source[c])) c += 1; - validator.warn( `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`, c ); + validator.warn( + `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`, + c + ); } } - if ( node.children ) { - node.children.forEach( visit ); + if (node.children) { + node.children.forEach(visit); } - if ( node.else ) { - visit( node.else ); + if (node.else) { + visit(node.else); } - if ( node.type === 'Element' ) { + if (node.type === 'Element') { elementDepth -= 1; } } - html.children.forEach( visit ); + html.children.forEach(visit); } diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts index 18c906b298..081fea6028 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -2,102 +2,144 @@ import validateEventHandler from './validateEventHandler'; 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 ); +export default function validateElement(validator: Validator, node: Node) { + const isComponent = + node.name === ':Self' || validator.components.has(node.name); let hasIntro: boolean; let hasOutro: boolean; let hasTransition: boolean; - node.attributes.forEach( ( attribute: Node ) => { - if ( !isComponent && attribute.type === 'Binding' ) { + node.attributes.forEach((attribute: Node) => { + if (!isComponent && attribute.type === 'Binding') { 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 ); + 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' ) { - validator.error( `'checked' 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 + ); } - if ( getType( validator, node ) !== 'checkbox' ) { - validator.error( `'checked' binding can only be used with <input type="checkbox">`, attribute.start ); + if (getType(validator, node) !== 'checkbox') { + validator.error( + `'checked' binding can only be used with <input type="checkbox">`, + attribute.start + ); } - } - - else if ( name === 'group' ) { - if ( node.name !== 'input' ) { - validator.error( `'group' is not a valid binding on <${node.name}> elements`, attribute.start ); + } else if (name === 'group') { + if (node.name !== 'input') { + validator.error( + `'group' is not a valid binding on <${node.name}> elements`, + attribute.start + ); } - const type = getType( validator, node ); + const type = getType(validator, node); - if ( type !== 'checkbox' && type !== 'radio' ) { - validator.error( `'checked' binding can only be used with <input type="checkbox"> or <input type="radio">`, attribute.start ); + 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' ) { - if ( node.name !== 'audio' && node.name !== 'video' ) { - validator.error( `'${name}' binding can only be used with <audio> or <video>`, 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 { - 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 === 'EventHandler') { + validateEventHandler(validator, attribute); + } 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 (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 ( ( 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 ((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 ( attribute.intro ) hasIntro = true; - if ( attribute.outro ) hasOutro = true; - if ( bidi ) hasTransition = true; + if (attribute.intro) hasIntro = true; + if (attribute.outro) hasOutro = true; + if (bidi) hasTransition = true; - if ( !validator.transitions.has( attribute.name ) ) { - validator.error( `Missing transition '${attribute.name}'`, attribute.start ); + if (!validator.transitions.has(attribute.name)) { + validator.error( + `Missing transition '${attribute.name}'`, + attribute.start + ); } - } - - 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 ); + } 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 + ); } } } }); } -function getType ( validator: Validator, node: Node ) { - const attribute = node.attributes.find( ( attribute: Node ) => attribute.name === 'type' ); - if ( !attribute ) return null; +function getType(validator: Validator, node: Node) { + const attribute = node.attributes.find( + (attribute: Node) => attribute.name === 'type' + ); + if (!attribute) return null; - if ( attribute.value === true ) { - validator.error( `'type' attribute must be specified`, attribute.start ); + if (attribute.value === true) { + validator.error(`'type' attribute must be specified`, attribute.start); } - if ( attribute.value.length > 1 || attribute.value[0].type !== 'Text' ) { - validator.error( `'type attribute cannot be dynamic`, attribute.start ); + if (attribute.value.length > 1 || attribute.value[0].type !== 'Text') { + validator.error(`'type attribute cannot be dynamic`, attribute.start); } return attribute.value[0].data; diff --git a/src/validate/html/validateEventHandler.ts b/src/validate/html/validateEventHandler.ts index 81ece1c148..85fdad08e6 100644 --- a/src/validate/html/validateEventHandler.ts +++ b/src/validate/html/validateEventHandler.ts @@ -3,35 +3,40 @@ 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' ) { - validator.error( `Expected a call expression`, start ); + if (type !== 'CallExpression') { + validator.error(`Expected a call expression`, start); } - const { name } = flattenReference( callee ); + 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 (name === 'this' || name === 'event') return; + if ( + (callee.type === 'Identifier' && validBuiltins.has(callee.name)) || + validator.methods.has(callee.name) + ) + return; - const validCallees = [ 'this.*', 'event.*' ] - .concat( - Array.from( validBuiltins ), - Array.from( validator.methods.keys() ) - ); + 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 ) ) { + if (callee.type === 'Identifier' && validator.helpers.has(callee.name)) { message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`; } - validator.warn( message, start ); + validator.warn(message, start); } diff --git a/src/validate/html/validateWindow.ts b/src/validate/html/validateWindow.ts index fadcac10a7..8889b849ff 100644 --- a/src/validate/html/validateWindow.ts +++ b/src/validate/html/validateWindow.ts @@ -14,37 +14,43 @@ const validBindings = [ 'scrollY' ]; -export default function validateWindow ( validator: Validator, node: Node ) { - node.attributes.forEach( ( attribute: Node ) => { - if ( attribute.type === 'Binding' ) { - if ( attribute.value.type !== 'Identifier' ) { - const { parts } = flattenReference( attribute.value ); +export default function validateWindow(validator: Validator, node: Node) { + node.attributes.forEach((attribute: Node) => { + if (attribute.type === 'Binding') { + if (attribute.value.type !== 'Identifier') { + 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 ) - ); + if (!~validBindings.indexOf(attribute.name)) { + 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 ); + if (match) { + 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' ) { - validateEventHandler( validator, attribute ); + } else if (attribute.type === 'EventHandler') { + validateEventHandler(validator, attribute); } }); -} \ No newline at end of file +} diff --git a/src/validate/index.ts b/src/validate/index.ts index 891132d23b..07338c9327 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -2,12 +2,17 @@ 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 ) { - super( message, template, index, filename ); + constructor( + message: string, + template: string, + index: number, + filename: string + ) { + super(message, template, index, filename); this.name = 'ValidationError'; } } @@ -27,7 +32,7 @@ export class Validator { helpers: Map<string, Node>; transitions: Map<string, Node>; - constructor ( parsed: Parsed, source: string, options: CompileOptions ) { + constructor(parsed: Parsed, source: string, options: CompileOptions) { this.source = source; this.filename = options !== undefined ? options.filename : undefined; @@ -43,15 +48,15 @@ export class Validator { this.transitions = new Map(); } - error ( message: string, pos: number ) { - throw new ValidationError( message, this.source, pos, this.filename ); + error(message: string, pos: number) { + throw new ValidationError(message, this.source, pos, this.filename); } - warn ( message: string, pos: number ) { - if ( !this.locator ) this.locator = getLocator( this.source ); - const { line, column } = this.locator( pos ); + warn(message: string, pos: number) { + if (!this.locator) this.locator = getLocator(this.source); + const { line, column } = this.locator(pos); - const frame = getCodeFrame( this.source, line, column ); + const frame = getCodeFrame(this.source, line, column); this.onwarn({ message, @@ -64,16 +69,20 @@ 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 { - if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) { - const error = new Error( `options.name must be a valid identifier` ); + if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) { + const error = new Error(`options.name must be a valid identifier`); throw error; } - if ( name && !/^[A-Z]/.test( name ) ) { + if (name && !/^[A-Z]/.test(name)) { const message = `options.name should be capitalised`; onwarn({ message, @@ -82,22 +91,22 @@ export default function validate ( parsed: Parsed, source: string, options: Comp }); } - const validator = new Validator( parsed, source, { + const validator = new Validator(parsed, source, { onwarn, name, filename }); - if ( parsed.js ) { - validateJs( validator, parsed.js ); + if (parsed.js) { + validateJs(validator, parsed.js); } - if ( parsed.html ) { - validateHtml( validator, parsed.html ); + if (parsed.html) { + validateHtml(validator, parsed.html); } - } catch ( err ) { - if ( onerror ) { - onerror( err ); + } catch (err) { + if (onerror) { + onerror(err); } else { throw err; } diff --git a/src/validate/js/index.ts b/src/validate/js/index.ts index f43922f81c..0b7547f64c 100644 --- a/src/validate/js/index.ts +++ b/src/validate/js/index.ts @@ -6,69 +6,89 @@ import namespaces from '../../utils/namespaces'; import { Validator } from '../'; import { Node } from '../../interfaces'; -const validPropList = Object.keys( propValidators ); +const validPropList = Object.keys(propValidators); -export default function validateJs ( validator: Validator, js: Node ) { - js.content.body.forEach( ( node: Node ) => { +export default function validateJs(validator: Validator, js: Node) { + js.content.body.forEach((node: Node) => { // check there are no named exports - if ( node.type === 'ExportNamedDeclaration' ) { - validator.error( `A component can only have a default export`, node.start ); + if (node.type === 'ExportNamedDeclaration') { + validator.error(`A component can only have a default export`, node.start); } - if ( node.type === 'ExportDefaultDeclaration' ) { - if ( node.declaration.type !== 'ObjectExpression' ) { - return validator.error( `Default export must be an object literal`, node.declaration.start ); + if (node.type === 'ExportDefaultDeclaration') { + if (node.declaration.type !== 'ObjectExpression') { + return validator.error( + `Default export must be an object literal`, + node.declaration.start + ); } - checkForComputedKeys( validator, node.declaration.properties ); - checkForDupes( validator, node.declaration.properties ); + checkForComputedKeys(validator, node.declaration.properties); + checkForDupes(validator, node.declaration.properties); const props = validator.properties; - node.declaration.properties.forEach( ( prop: Node ) => { - props.set( prop.key.name, prop ); + node.declaration.properties.forEach((prop: Node) => { + props.set(prop.key.name, prop); }); // 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 ); + if (props.has('oncreate') && props.has('onrender')) { + 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 ); + if (props.has('ondestroy') && props.has('onteardown')) { + validator.error( + 'Cannot have both ondestroy and onteardown', + props.get('onteardown').start + ); } // ensure all exported props are valid - node.declaration.properties.forEach( ( prop: Node ) => { - const propValidator = propValidators[ prop.key.name ]; + node.declaration.properties.forEach((prop: Node) => { + const propValidator = propValidators[prop.key.name]; - if ( propValidator ) { - propValidator( validator, prop ); + if (propValidator) { + propValidator(validator, prop); } else { - const match = fuzzymatch( prop.key.name, validPropList ); - if ( match ) { - 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 ); + const match = fuzzymatch(prop.key.name, validPropList); + if (match) { + 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 + ); } else { - validator.error( `Unexpected property '${prop.key.name}'`, prop.start ); + validator.error( + `Unexpected property '${prop.key.name}'`, + prop.start + ); } } }); - if ( props.has( 'namespace' ) ) { - const ns = props.get( 'namespace' ).value.value; - validator.namespace = namespaces[ ns ] || ns; + if (props.has('namespace')) { + const ns = props.get('namespace').value.value; + validator.namespace = namespaces[ns] || ns; } validator.defaultExport = node; } }); - [ 'components', 'methods', 'helpers', 'transitions' ].forEach( key => { - if ( validator.properties.has( key ) ) { - validator.properties.get( key ).value.properties.forEach( ( prop: Node ) => { - validator[ key ].set( prop.key.name, prop.value ); + ['components', 'methods', 'helpers', 'transitions'].forEach(key => { + if (validator.properties.has(key)) { + validator.properties.get(key).value.properties.forEach((prop: Node) => { + validator[key].set(prop.key.name, prop.value); }); } }); diff --git a/src/validate/js/propValidators/components.ts b/src/validate/js/propValidators/components.ts index 36077c52ed..a7ca5eb990 100644 --- a/src/validate/js/propValidators/components.ts +++ b/src/validate/js/propValidators/components.ts @@ -3,22 +3,28 @@ import checkForComputedKeys from '../utils/checkForComputedKeys'; import { Validator } from '../../'; 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 ); +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 + ); return; } - checkForDupes( validator, prop.value.properties ); - checkForComputedKeys( validator, prop.value.properties ); + checkForDupes(validator, prop.value.properties); + checkForComputedKeys(validator, prop.value.properties); - 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 ); + 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 + ); } - if ( !/^[A-Z]/.test( component.key.name ) ) { - validator.warn( `Component names should be capitalised`, component.start ); + if (!/^[A-Z]/.test(component.key.name)) { + validator.warn(`Component names should be capitalised`, component.start); } }); } diff --git a/src/validate/js/propValidators/computed.ts b/src/validate/js/propValidators/computed.ts index c72ef26e73..ec0156bd99 100644 --- a/src/validate/js/propValidators/computed.ts +++ b/src/validate/js/propValidators/computed.ts @@ -3,35 +3,53 @@ import checkForComputedKeys from '../utils/checkForComputedKeys'; import { Validator } from '../../'; import { Node } from '../../../interfaces'; -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 ); +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 + ); return; } - checkForDupes( validator, prop.value.properties ); - checkForComputedKeys( validator, prop.value.properties ); + checkForDupes(validator, prop.value.properties); + checkForComputedKeys(validator, prop.value.properties); - 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 ); + 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 + ); 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 ); + if (params.length === 0) { + 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'; - - if ( !valid ) { - validator.error( `Computed properties cannot use destructuring in function parameters`, param.start ); + params.forEach((param: Node) => { + 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 + ); } }); }); diff --git a/src/validate/js/propValidators/data.ts b/src/validate/js/propValidators/data.ts index 9727385a78..b49931cf21 100644 --- a/src/validate/js/propValidators/data.ts +++ b/src/validate/js/propValidators/data.ts @@ -1,14 +1,14 @@ import { Validator } from '../../'; import { Node } from '../../../interfaces'; -const disallowed = new Set([ 'Literal', 'ObjectExpression', 'ArrayExpression' ]); +const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']); -export default function data ( validator: Validator, prop: Node ) { - while ( prop.type === 'ParenthesizedExpression' ) prop = prop.expression; +export default function data(validator: Validator, prop: Node) { + while (prop.type === 'ParenthesizedExpression') prop = prop.expression; // TODO should we disallow references and expressions as well? - if ( disallowed.has( prop.value.type ) ) { - validator.error( `'data' must be a function`, prop.value.start ); + if (disallowed.has(prop.value.type)) { + validator.error(`'data' must be a function`, prop.value.start); } } diff --git a/src/validate/js/propValidators/events.ts b/src/validate/js/propValidators/events.ts index b1c7ecb842..1bbf75d1e6 100644 --- a/src/validate/js/propValidators/events.ts +++ b/src/validate/js/propValidators/events.ts @@ -3,12 +3,15 @@ import checkForComputedKeys from '../utils/checkForComputedKeys'; import { Validator } from '../../'; 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 ); +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 + ); return; } - checkForDupes( validator, prop.value.properties ); - checkForComputedKeys( validator, prop.value.properties ); + checkForDupes(validator, prop.value.properties); + checkForComputedKeys(validator, prop.value.properties); } diff --git a/src/validate/js/propValidators/helpers.ts b/src/validate/js/propValidators/helpers.ts index 6505eb9b5e..6dfc02013f 100644 --- a/src/validate/js/propValidators/helpers.ts +++ b/src/validate/js/propValidators/helpers.ts @@ -4,53 +4,67 @@ import { walk } from 'estree-walker'; import { Validator } from '../../'; 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 ); +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 + ); return; } - checkForDupes( validator, prop.value.properties ); - checkForComputedKeys( validator, prop.value.properties ); + checkForDupes(validator, prop.value.properties); + checkForComputedKeys(validator, prop.value.properties); - prop.value.properties.forEach( ( prop: Node ) => { - if ( !/FunctionExpression/.test( prop.value.type ) ) return; + prop.value.properties.forEach((prop: Node) => { + if (!/FunctionExpression/.test(prop.value.type)) return; let lexicalDepth = 0; let usesArguments = false; - walk( prop.value.body, { - enter ( node: Node ) { - if ( /^Function/.test( node.type ) ) { + walk(prop.value.body, { + 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 === '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 ); + 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 + ); } - else if ( node.type === 'Identifier' && node.name === 'arguments' ) { + 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') { usesArguments = true; } } }, - leave ( node: Node ) { - if ( /^Function/.test( node.type ) ) { + leave(node: Node) { + if (/^Function/.test(node.type)) { lexicalDepth -= 1; } } }); - if ( prop.value.params.length === 0 && !usesArguments ) { - validator.warn( `Helpers should be pure functions, with at least one argument`, prop.start ); + if (prop.value.params.length === 0 && !usesArguments) { + validator.warn( + `Helpers should be pure functions, with at least one argument`, + prop.start + ); } }); } diff --git a/src/validate/js/propValidators/methods.ts b/src/validate/js/propValidators/methods.ts index 144a2257ee..7d8547ef39 100644 --- a/src/validate/js/propValidators/methods.ts +++ b/src/validate/js/propValidators/methods.ts @@ -5,26 +5,36 @@ import usesThisOrArguments from '../utils/usesThisOrArguments'; import { Validator } from '../../'; import { Node } from '../../../interfaces'; -const builtin = new Set( [ 'set', 'get', 'on', 'fire', 'observe', 'destroy' ] ); +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 ); +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 + ); return; } - checkForAccessors( validator, prop.value.properties, 'Methods' ); - checkForDupes( validator, prop.value.properties ); - checkForComputedKeys( validator, prop.value.properties ); + checkForAccessors(validator, prop.value.properties, 'Methods'); + checkForDupes(validator, prop.value.properties); + checkForComputedKeys(validator, prop.value.properties); - prop.value.properties.forEach( ( prop: Node ) => { - if ( builtin.has( prop.key.name ) ) { - validator.error( `Cannot overwrite built-in method '${prop.key.name}'`, prop.start ); + prop.value.properties.forEach((prop: Node) => { + if (builtin.has(prop.key.name)) { + 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 ); + 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 + ); } } }); diff --git a/src/validate/js/propValidators/namespace.ts b/src/validate/js/propValidators/namespace.ts index 5b43bf72ea..30e3ead77f 100644 --- a/src/validate/js/propValidators/namespace.ts +++ b/src/validate/js/propValidators/namespace.ts @@ -3,21 +3,27 @@ import fuzzymatch from '../../utils/fuzzymatch'; import { Validator } from '../../'; import { Node } from '../../../interfaces'; -const valid = new Set( namespaces.validNamespaces ); +const valid = new Set(namespaces.validNamespaces); -export default function namespace ( validator: Validator, prop: Node ) { +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 ); + if (prop.value.type !== 'Literal' || typeof ns !== 'string') { + 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 ); + if (!valid.has(ns)) { + const match = fuzzymatch(ns, namespaces.validNamespaces); + if (match) { + validator.error( + `Invalid namespace '${ns}' (did you mean '${match}'?)`, + prop.start + ); } else { - validator.error( `Invalid namespace '${ns}'`, prop.start ); + validator.error(`Invalid namespace '${ns}'`, prop.start); } } } diff --git a/src/validate/js/propValidators/oncreate.ts b/src/validate/js/propValidators/oncreate.ts index 9ebf44a782..587a8b3679 100644 --- a/src/validate/js/propValidators/oncreate.ts +++ b/src/validate/js/propValidators/oncreate.ts @@ -2,10 +2,13 @@ import usesThisOrArguments from '../utils/usesThisOrArguments'; import { Validator } from '../../'; 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 ); +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 + ); } } } diff --git a/src/validate/js/propValidators/ondestroy.ts b/src/validate/js/propValidators/ondestroy.ts index 62155e563f..e12525e73f 100644 --- a/src/validate/js/propValidators/ondestroy.ts +++ b/src/validate/js/propValidators/ondestroy.ts @@ -2,10 +2,13 @@ import usesThisOrArguments from '../utils/usesThisOrArguments'; import { Validator } from '../../'; 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 ); +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 + ); } } } diff --git a/src/validate/js/propValidators/onrender.ts b/src/validate/js/propValidators/onrender.ts index 0cc58c8af4..fd6751a373 100644 --- a/src/validate/js/propValidators/onrender.ts +++ b/src/validate/js/propValidators/onrender.ts @@ -2,7 +2,10 @@ import oncreate from './oncreate'; 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 ); - oncreate( validator, prop ); +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 + ); + oncreate(validator, prop); } diff --git a/src/validate/js/propValidators/onteardown.ts b/src/validate/js/propValidators/onteardown.ts index 6c6afef7a0..cca770909c 100644 --- a/src/validate/js/propValidators/onteardown.ts +++ b/src/validate/js/propValidators/onteardown.ts @@ -2,7 +2,10 @@ import ondestroy from './ondestroy'; 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 ); - ondestroy( validator, prop ); +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 + ); + ondestroy(validator, prop); } diff --git a/src/validate/js/propValidators/transitions.ts b/src/validate/js/propValidators/transitions.ts index b50302d93e..6470beee6f 100644 --- a/src/validate/js/propValidators/transitions.ts +++ b/src/validate/js/propValidators/transitions.ts @@ -3,16 +3,19 @@ import checkForComputedKeys from '../utils/checkForComputedKeys'; import { Validator } from '../../'; 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 ); +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 + ); return; } - checkForDupes( validator, prop.value.properties ); - checkForComputedKeys( validator, prop.value.properties ); + checkForDupes(validator, prop.value.properties); + checkForComputedKeys(validator, prop.value.properties); - prop.value.properties.forEach( () => { + prop.value.properties.forEach(() => { // TODO probably some validation that can happen here... // checking for use of `this` etc? }); diff --git a/src/validate/js/utils/checkForAccessors.ts b/src/validate/js/utils/checkForAccessors.ts index 45ee1eb30d..6c00d3a23a 100644 --- a/src/validate/js/utils/checkForAccessors.ts +++ b/src/validate/js/utils/checkForAccessors.ts @@ -1,10 +1,14 @@ import { Validator } from '../../'; import { Node } from '../../../interfaces'; -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 ); +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); } }); } diff --git a/src/validate/js/utils/checkForComputedKeys.ts b/src/validate/js/utils/checkForComputedKeys.ts index ca0291e2a9..cf3892d02c 100644 --- a/src/validate/js/utils/checkForComputedKeys.ts +++ b/src/validate/js/utils/checkForComputedKeys.ts @@ -1,10 +1,13 @@ import { Validator } from '../../'; import { Node } from '../../../interfaces'; -export default function checkForComputedKeys ( validator: Validator, properties: Node[] ) { - properties.forEach( prop => { - if ( prop.key.computed ) { - validator.error( `Cannot use computed keys`, prop.start ); +export default function checkForComputedKeys( + validator: Validator, + properties: Node[] +) { + properties.forEach(prop => { + if (prop.key.computed) { + validator.error(`Cannot use computed keys`, prop.start); } }); } diff --git a/src/validate/js/utils/checkForDupes.ts b/src/validate/js/utils/checkForDupes.ts index db16a1133f..8565ab5388 100644 --- a/src/validate/js/utils/checkForDupes.ts +++ b/src/validate/js/utils/checkForDupes.ts @@ -1,14 +1,17 @@ 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 => { - if ( seen.has( prop.key.name ) ) { - validator.error( `Duplicate property '${prop.key.name}'`, prop.start ); + properties.forEach(prop => { + if (seen.has(prop.key.name)) { + validator.error(`Duplicate property '${prop.key.name}'`, prop.start); } - seen.add( prop.key.name ); + seen.add(prop.key.name); }); } diff --git a/src/validate/js/utils/usesThisOrArguments.ts b/src/validate/js/utils/usesThisOrArguments.ts index 2c56a57f6c..a9c181a67d 100644 --- a/src/validate/js/utils/usesThisOrArguments.ts +++ b/src/validate/js/utils/usesThisOrArguments.ts @@ -2,20 +2,28 @@ import { walk } from 'estree-walker'; import isReference from '../../../utils/isReference'; import { Node } from '../../../interfaces'; -export default function usesThisOrArguments ( node: Node ) { +export default function usesThisOrArguments(node: Node) { let result = false; - walk( node, { - enter ( node: Node, parent: Node ) { - if ( result || node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' ) { + walk(node, { + enter(node: Node, parent: Node) { + if ( + result || + node.type === 'FunctionExpression' || + node.type === 'FunctionDeclaration' + ) { return this.skip(); } - if ( node.type === 'ThisExpression' ) { + if (node.type === 'ThisExpression') { result = true; } - if ( node.type === 'Identifier' && isReference( node, parent ) && node.name === 'arguments' ) { + if ( + node.type === 'Identifier' && + isReference(node, parent) && + node.name === 'arguments' + ) { result = true; } } diff --git a/src/validate/utils/FuzzySet.ts b/src/validate/utils/FuzzySet.ts index bed3ee1c13..2d7ae95d23 100644 --- a/src/validate/utils/FuzzySet.ts +++ b/src/validate/utils/FuzzySet.ts @@ -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 = {}; @@ -14,7 +21,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp this.items = {}; // helper functions - function levenshtein ( str1, str2 ) { + function levenshtein(str1, str2) { const current = []; let prev; let value; @@ -40,10 +47,12 @@ 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'; + function _distance(str1, str2) { + 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) { @@ -55,7 +64,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp const _nonWordRe = /[^\w, ]+/; - function _iterateGrams (value, gramSize) { + function _iterateGrams(value, gramSize) { gramSize = gramSize || 2; const simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-'; const lenDiff = gramSize - simplified.length; @@ -72,7 +81,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp return results; } - function _gramCounter (value, gramSize) { + function _gramCounter(value, gramSize) { // return an object where key=gram, value=number of occurrences gramSize = gramSize || 2; const result = {}; @@ -90,7 +99,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp } // the main functions - this.get = function (value, defaultValue) { + this.get = function(value, defaultValue) { // check for value in set, returning defaultValue or null if none found const result = this._get(value); if (!result && typeof defaultValue !== 'undefined') { @@ -99,7 +108,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp return result; }; - this._get = function (value) { + this._get = function(value) { const normalizedValue = this._normalizeStr(value); const result = this.exactSet[normalizedValue]; @@ -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; @@ -118,7 +131,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp return null; }; - this.__get = function (value, gramSize) { + this.__get = function(value, gramSize) { const normalizedValue = this._normalizeStr(value); const matches = {}; const gramCounts = _gramCounter(normalizedValue, gramSize); @@ -146,10 +159,9 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp } } - function isEmptyObject ( obj ) { - for ( const prop in obj ) { - if ( obj.hasOwnProperty( prop ) ) - return false; + function isEmptyObject(obj) { + for (const prop in obj) { + if (obj.hasOwnProperty(prop)) return false; } return true; } @@ -165,9 +177,12 @@ 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) { + function sortDescending(a, b) { if (a[0] < b[0]) { return 1; } else 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); @@ -197,7 +215,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp return newResults; }; - this.add = function (value) { + this.add = function(value) { const normalizedValue = this._normalizeStr(value); if (normalizedValue in this.exactSet) { return false; @@ -209,7 +227,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp } }; - this._add = function (value, gramSize) { + this._add = function(value, gramSize) { const normalizedValue = this._normalizeStr(value); const items = this.items[gramSize] || []; const index = items.length; @@ -235,13 +253,14 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp this.exactSet[normalizedValue] = value; }; - this._normalizeStr = function (str) { - if (Object.prototype.toString.call(str) !== '[object String]') throw 'Must use a string as argument to FuzzySet functions'; + this._normalizeStr = function(str) { + if (Object.prototype.toString.call(str) !== '[object String]') + throw 'Must use a string as argument to FuzzySet functions'; return str.toLowerCase(); }; // return length of items in set - this.length = function () { + this.length = function() { let count = 0; let prop; @@ -254,7 +273,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp }; // return is set is empty - this.isEmpty = function () { + this.isEmpty = function() { for (const prop in this.exactSet) { if (this.exactSet.hasOwnProperty(prop)) { return false; @@ -264,7 +283,7 @@ export default function FuzzySet (arr, useLevenshtein, gramSizeLower, gramSizeUp }; // return list of values loaded into set - this.values = function () { + this.values = function() { const values = []; for (const prop in this.exactSet) { @@ -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) { diff --git a/src/validate/utils/fuzzymatch.ts b/src/validate/utils/fuzzymatch.ts index 1769871df9..5d70372191 100644 --- a/src/validate/utils/fuzzymatch.ts +++ b/src/validate/utils/fuzzymatch.ts @@ -1,10 +1,8 @@ import FuzzySet from './FuzzySet'; -export default function fuzzymatch ( name: string, names: string[] ) { - const set = new FuzzySet( names ); - const matches = set.get( name ); +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; -} \ No newline at end of file + return matches && matches[0] && matches[0][0] > 0.7 ? matches[0][1] : null; +} diff --git a/src/validate/utils/list.ts b/src/validate/utils/list.ts index c37b6dd541..785f006d19 100644 --- a/src/validate/utils/list.ts +++ b/src/validate/utils/list.ts @@ -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 ]}`; -} \ No newline at end of file +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 + ]}`; +} diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index b3197f189d..e8ad287400 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -1,121 +1,127 @@ -function noop () {} - -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function appendNode ( node, target ) { - target.appendChild( node ); +function appendNode(node, target) { + target.appendChild(node); } -function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -function detachNode ( node ) { - node.parentNode.removeChild( node ); +function detachNode(node) { + node.parentNode.removeChild(node); } -function createElement ( name ) { - return document.createElement( name ); +function createElement(name) { + return document.createElement(name); } -function createText ( data ) { - return document.createTextNode( data ); +function createText(data) { + return document.createTextNode(data); } -function setAttribute ( node, attribute, value ) { - node.setAttribute( attribute, value ); +function setAttribute(node, attribute, value) { + node.setAttribute(attribute, value); } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index fce7cbda6f..f491d92da1 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -1,97 +1,103 @@ -function noop () {} - -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 9a1b10e93f..4bfdc1e50b 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -1,130 +1,136 @@ -function noop () {} - -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function appendNode ( node, target ) { - target.appendChild( node ); +function appendNode(node, target) { + target.appendChild(node); } -function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -function detachNode ( node ) { - node.parentNode.removeChild( node ); +function detachNode(node) { + node.parentNode.removeChild(node); } -function detachBetween ( before, after ) { - while ( before.nextSibling && before.nextSibling !== after ) { - before.parentNode.removeChild( before.nextSibling ); +function detachBetween(before, after) { + while (before.nextSibling && before.nextSibling !== after) { + before.parentNode.removeChild(before.nextSibling); } } // TODO this is out of date -function destroyEach ( iterations, detach, start ) { - for ( var i = start; i < iterations.length; i += 1 ) { - if ( iterations[i] ) iterations[i].destroy( detach ); +function destroyEach(iterations, detach, start) { + for (var i = start; i < iterations.length; i += 1) { + if (iterations[i]) iterations[i].destroy(detach); } } -function createElement ( name ) { - return document.createElement( name ); +function createElement(name) { + return document.createElement(name); } -function createText ( data ) { - return document.createTextNode( data ); +function createText(data) { + return document.createTextNode(data); } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 3859406832..e79b8f0659 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -168,4 +168,4 @@ SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = functio this._torndown = true; }; -export default SvelteComponent; +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 804c128f60..128cc96c43 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -1,115 +1,121 @@ -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function appendNode ( node, target ) { - target.appendChild( node ); +function appendNode(node, target) { + target.appendChild(node); } -function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -function detachNode ( node ) { - node.parentNode.removeChild( node ); +function detachNode(node) { + node.parentNode.removeChild(node); } -function createElement ( name ) { - return document.createElement( name ); +function createElement(name) { + return document.createElement(name); } -function createText ( data ) { - return document.createTextNode( data ); +function createText(data) { + return document.createTextNode(data); } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index d03f5b63fd..bf5690896d 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -1,121 +1,127 @@ -function noop () {} - -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function appendNode ( node, target ) { - target.appendChild( node ); +function appendNode(node, target) { + target.appendChild(node); } -function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -function detachNode ( node ) { - node.parentNode.removeChild( node ); +function detachNode(node) { + node.parentNode.removeChild(node); } -function createElement ( name ) { - return document.createElement( name ); +function createElement(name) { + return document.createElement(name); } -function createText ( data ) { - return document.createTextNode( data ); +function createText(data) { + return document.createTextNode(data); } -function createComment () { - return document.createComment( '' ); +function createComment() { + return document.createComment(''); } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 730f56c546..03b2450bfc 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -1,121 +1,127 @@ -function noop () {} - -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function appendNode ( node, target ) { - target.appendChild( node ); +function appendNode(node, target) { + target.appendChild(node); } -function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -function detachNode ( node ) { - node.parentNode.removeChild( node ); +function detachNode(node) { + node.parentNode.removeChild(node); } -function createElement ( name ) { - return document.createElement( name ); +function createElement(name) { + return document.createElement(name); } -function createText ( data ) { - return document.createTextNode( data ); +function createText(data) { + return document.createTextNode(data); } -function createComment () { - return document.createComment( '' ); +function createComment() { + return document.createComment(''); } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index f66a1e1421..3a03995a16 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -1,109 +1,115 @@ import Imported from 'Imported.html'; -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -function detachNode ( node ) { - node.parentNode.removeChild( node ); +function detachNode(node) { + node.parentNode.removeChild(node); } -function createText ( data ) { - return document.createTextNode( data ); +function createText(data) { + return document.createTextNode(data); } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index 88472296ad..8a9044c8ca 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -1,97 +1,103 @@ -function noop () {} - -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index d39c029eed..30bf07a1e4 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -1,121 +1,127 @@ -function noop () {} - -function assign ( target ) { - var k, source, i = 1, len = arguments.length; - for ( ; i < len; i++ ) { +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { source = arguments[i]; - for ( k in source ) target[k] = source[k]; + for (k in source) target[k] = source[k]; } return target; } -function appendNode ( node, target ) { - target.appendChild( node ); +function appendNode(node, target) { + target.appendChild(node); } -function insertNode ( node, target, anchor ) { - target.insertBefore( node, anchor ); +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); } -function detachNode ( node ) { - node.parentNode.removeChild( node ); +function detachNode(node) { + node.parentNode.removeChild(node); } -function createElement ( name ) { - return document.createElement( name ); +function createElement(name) { + return document.createElement(name); } -function createText ( data ) { - return document.createTextNode( data ); +function createText(data) { + return document.createTextNode(data); } -function createComment () { - return document.createComment( '' ); +function createComment() { + return document.createComment(''); } -function differs ( a, b ) { - return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) ); +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers ( component, group, newState, oldState ) { - for ( var key in group ) { - if ( !( key in newState ) ) continue; +function dispatchObservers(component, group, newState, oldState) { + for (var key in group) { + if (!(key in newState)) continue; - var newValue = newState[ key ]; - var oldValue = oldState[ key ]; + var newValue = newState[key]; + var oldValue = oldState[key]; - if ( differs( newValue, oldValue ) ) { - var callbacks = group[ key ]; - if ( !callbacks ) continue; + if (differs(newValue, oldValue)) { + var callbacks = group[key]; + if (!callbacks) continue; - for ( var i = 0; i < callbacks.length; i += 1 ) { + for (var i = 0; i < callbacks.length; i += 1) { var callback = callbacks[i]; - if ( callback.__calling ) continue; + if (callback.__calling) continue; callback.__calling = true; - callback.call( component, newValue, oldValue ); + callback.call(component, newValue, oldValue); callback.__calling = false; } } } } -function get ( key ) { - return key ? this._state[ key ] : this._state; +function get(key) { + return key ? this._state[key] : this._state; } -function fire ( eventName, data ) { - var handlers = eventName in this._handlers && this._handlers[ eventName ].slice(); - if ( !handlers ) return; +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; - for ( var i = 0; i < handlers.length; i += 1 ) { - handlers[i].call( this, data ); + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); } } -function observe ( key, callback, options ) { - var group = ( options && options.defer ) ? this._observers.post : this._observers.pre; +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; - ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); + (group[key] || (group[key] = [])).push(callback); - if ( !options || options.init !== false ) { + if (!options || options.init !== false) { callback.__calling = true; - callback.call( this, this._state[ key ] ); + callback.call(this, this._state[key]); callback.__calling = false; } return { - cancel: function () { - var index = group[ key ].indexOf( callback ); - if ( ~index ) group[ key ].splice( index, 1 ); + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); } }; } -function on ( eventName, handler ) { - if ( eventName === 'teardown' ) return this.on( 'destroy', handler ); +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); - var handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] ); - handlers.push( handler ); + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); return { - cancel: function () { - var index = handlers.indexOf( handler ); - if ( ~index ) handlers.splice( index, 1 ); + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); } }; } -function set ( newState ) { - this._set( assign( {}, newState ) ); +function set(newState) { + this._set(assign({}, newState)); this._root._flush(); } -function _flush () { - if ( !this._renderHooks ) return; +function _flush() { + if (!this._renderHooks) return; - while ( this._renderHooks.length ) { + while (this._renderHooks.length) { this._renderHooks.pop()(); } } diff --git a/test/js/update.js b/test/js/update.js new file mode 100644 index 0000000000..4b51bf039d --- /dev/null +++ b/test/js/update.js @@ -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); +}); \ No newline at end of file