Merge branch 'master' into gh-371

pull/391/head
Rich Harris 8 years ago committed by GitHub
commit 3ead9bfc3f

@ -18,15 +18,15 @@ export default class Generator {
this.options = options; this.options = options;
this.imports = []; this.imports = [];
this.helpers = {}; this.helpers = new Set();
this.components = {}; this.components = new Set();
this.events = {}; this.events = new Set();
this.bindingGroups = []; this.bindingGroups = [];
// track which properties are needed, so we can provide useful info // track which properties are needed, so we can provide useful info
// in dev mode // in dev mode
this.expectedProperties = {}; this.expectedProperties = new Set();
this.elementDepth = 0; this.elementDepth = 0;
@ -37,11 +37,11 @@ export default class Generator {
// allow compiler to deconflict user's `import { get } from 'whatever'` and // allow compiler to deconflict user's `import { get } from 'whatever'` and
// Svelte's builtin `import { get, ... } from 'svelte/shared.js'`; // Svelte's builtin `import { get, ... } from 'svelte/shared.js'`;
this.importedNames = {}; this.importedNames = new Set();
this.aliases = {}; this.aliases = new Map();
this._callbacks = {}; this._callbacks = new Map();
} }
addSourcemapLocations ( node ) { addSourcemapLocations ( node ) {
@ -54,17 +54,17 @@ export default class Generator {
} }
alias ( name ) { alias ( name ) {
if ( !( name in this.aliases ) ) { if ( !( this.aliases.has( name ) ) ) {
let alias = name; let alias = name;
let i = 1; let i = 1;
while ( alias in this.importedNames ) { while ( alias in this.importedNames ) {
alias = `${name}$${i++}`; alias = `${name}$${i++}`;
} }
this.aliases[ name ] = alias; this.aliases.set( name, alias );
} }
return this.aliases[ name ]; return this.aliases.get( name );
} }
contextualise ( expression, isEventHandler ) { contextualise ( expression, isEventHandler ) {
@ -91,7 +91,7 @@ export default class Generator {
const { name } = flattenReference( node ); const { name } = flattenReference( node );
if ( scope.has( name ) ) return; if ( scope.has( name ) ) return;
if ( parent && parent.type === 'CallExpression' && node === parent.callee && helpers[ name ] ) { if ( parent && parent.type === 'CallExpression' && node === parent.callee && helpers.has( name ) ) {
code.prependRight( node.start, `${self.alias( 'template' )}.helpers.` ); code.prependRight( node.start, `${self.alias( 'template' )}.helpers.` );
} }
@ -99,19 +99,19 @@ export default class Generator {
// noop // noop
} }
else if ( name in contexts ) { else if ( contexts.has( name ) ) {
const context = contexts[ name ]; const context = contexts.get( name );
if ( context !== name ) { if ( context !== name ) {
// this is true for 'reserved' names like `root` and `component` // this is true for 'reserved' names like `root` and `component`
code.overwrite( node.start, node.start + name.length, context, true ); code.overwrite( node.start, node.start + name.length, context, true );
} }
dependencies.push( ...contextDependencies[ name ] ); dependencies.push( ...contextDependencies.get( name ) );
if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name ); if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name );
} }
else if ( indexes[ name ] ) { else if ( indexes.has( name ) ) {
const context = indexes[ name ]; const context = indexes.get( name );
if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context );
} }
@ -145,7 +145,7 @@ export default class Generator {
}); });
dependencies.forEach( name => { dependencies.forEach( name => {
this.expectedProperties[ name ] = true; this.expectedProperties.add( name );
}); });
return { return {
@ -157,7 +157,7 @@ export default class Generator {
} }
fire ( eventName, data ) { fire ( eventName, data ) {
const handlers = eventName in this._callbacks && this._callbacks[ eventName ].slice(); const handlers = this._callbacks.has( eventName ) && this._callbacks.get( eventName ).slice();
if ( !handlers ) return; if ( !handlers ) return;
for ( let i = 0; i < handlers.length; i += 1 ) { for ( let i = 0; i < handlers.length; i += 1 ) {
@ -287,7 +287,7 @@ export default class Generator {
} else { } else {
const { declarations } = annotateWithScopes( js ); const { declarations } = annotateWithScopes( js );
let template = 'template'; let template = 'template';
for ( let i = 1; template in declarations; template = `template$${i++}` ); for ( let i = 1; declarations.has( template ); template = `template$${i++}` );
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var ${template} = ` ); this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var ${template} = ` );
@ -312,7 +312,7 @@ export default class Generator {
[ 'helpers', 'events', 'components' ].forEach( key => { [ 'helpers', 'events', 'components' ].forEach( key => {
if ( templateProperties[ key ] ) { if ( templateProperties[ key ] ) {
templateProperties[ key ].value.properties.forEach( prop => { templateProperties[ key ].value.properties.forEach( prop => {
this[ key ][ prop.key.name ] = prop.value; this[ key ].add( prop.key.name );
}); });
} }
}); });
@ -354,8 +354,11 @@ export default class Generator {
} }
on ( eventName, handler ) { on ( eventName, handler ) {
const handlers = this._callbacks[ eventName ] || ( this._callbacks[ eventName ] = [] ); if ( this._callbacks.has( eventName ) ) {
handlers.push( handler ); this._callbacks.get( eventName ).push( handler );
} else {
this._callbacks.set( eventName, [ handler ] );
}
} }
pop () { pop () {

@ -7,15 +7,15 @@ export default function annotateWithScopes ( expression ) {
enter ( node ) { enter ( node ) {
if ( /Function/.test( node.type ) ) { if ( /Function/.test( node.type ) ) {
if ( node.type === 'FunctionDeclaration' ) { if ( node.type === 'FunctionDeclaration' ) {
scope.declarations[ node.id.name ] = true; scope.declarations.add( node.id.name );
} else { } else {
node._scope = scope = new Scope( scope, false ); node._scope = scope = new Scope( scope, false );
if ( node.id ) scope.declarations[ node.id.name ] = true; if ( node.id ) scope.declarations.add( node.id.name );
} }
node.params.forEach( param => { node.params.forEach( param => {
extractNames( param ).forEach( name => { extractNames( param ).forEach( name => {
scope.declarations[ name ] = true; scope.declarations.add( name );
}); });
}); });
} }
@ -47,7 +47,7 @@ class Scope {
constructor ( parent, block ) { constructor ( parent, block ) {
this.parent = parent; this.parent = parent;
this.block = block; this.block = block;
this.declarations = Object.create( null ); this.declarations = new Set();
} }
addDeclaration ( node ) { addDeclaration ( node ) {
@ -56,16 +56,16 @@ class Scope {
} else if ( node.type === 'VariableDeclaration' ) { } else if ( node.type === 'VariableDeclaration' ) {
node.declarations.forEach( declarator => { node.declarations.forEach( declarator => {
extractNames( declarator.id ).forEach( name => { extractNames( declarator.id ).forEach( name => {
this.declarations[ name ] = true; this.declarations.add( name );
}); });
}); });
} else { } else {
this.declarations[ node.id.name ] = true; this.declarations.add( node.id.name );
} }
} }
has ( name ) { has ( name ) {
return name in this.declarations || this.parent && this.parent.has( name ); return this.declarations.has( name ) || this.parent && this.parent.has( name );
} }
} }

@ -12,7 +12,7 @@ class DomGenerator extends Generator {
constructor ( parsed, source, name, names, visitors, options ) { constructor ( parsed, source, name, names, visitors, options ) {
super( parsed, source, name, names, visitors, options ); super( parsed, source, name, names, visitors, options );
this.renderers = []; this.renderers = [];
this.uses = {}; this.uses = new Set();
// allow compiler to deconflict user's `import { get } from 'whatever'` and // allow compiler to deconflict user's `import { get } from 'whatever'` and
// Svelte's builtin `import { get, ... } from 'svelte/shared.js'`; // Svelte's builtin `import { get, ... } from 'svelte/shared.js'`;
@ -24,7 +24,7 @@ class DomGenerator extends Generator {
metaBindings: new CodeBuilder() metaBindings: new CodeBuilder()
}; };
this.importedComponents = {}; this.importedComponents = new Map();
} }
addElement ( name, renderStatement, needsIdentifier = false ) { addElement ( name, renderStatement, needsIdentifier = false ) {
@ -144,7 +144,7 @@ class DomGenerator extends Generator {
name = `${name}Dev`; name = `${name}Dev`;
} }
this.uses[ name ] = true; this.uses.add( name );
return this.alias( name ); return this.alias( name );
} }
@ -184,15 +184,15 @@ export default function dom ( parsed, source, options, names ) {
templateProperties.components.value.properties.forEach( property => { templateProperties.components.value.properties.forEach( property => {
const key = property.key.name; const key = property.key.name;
const value = source.slice( property.value.start, property.value.end ); const value = source.slice( property.value.start, property.value.end );
if ( generator.importedNames[ value ] ) { if ( generator.importedNames.has( value ) ) {
generator.importedComponents[ key ] = value; generator.importedComponents.set( key, value );
} else { } else {
hasNonImportedComponent = true; hasNonImportedComponent = true;
} }
}); });
if ( hasNonImportedComponent ) { if ( hasNonImportedComponent ) {
// remove the specific components that were imported, as we'll refer to them directly // remove the specific components that were imported, as we'll refer to them directly
Object.keys( generator.importedComponents ).forEach( key => { Array.from( generator.importedComponents.keys() ).forEach( key => {
removeObjectKey( generator, templateProperties.components.value, key ); removeObjectKey( generator, templateProperties.components.value, key );
}); });
} else { } else {
@ -208,12 +208,12 @@ export default function dom ( parsed, source, options, names ) {
localElementDepth: 0, localElementDepth: 0,
key: null, key: null,
contexts: {}, contexts: new Map(),
indexes: {}, indexes: new Map(),
params: [ 'root' ], params: [ 'root' ],
indexNames: {}, indexNames: new Map(),
listNames: {}, listNames: new Map(),
builders: getBuilders(), builders: getBuilders(),
getUniqueName: generator.getUniqueNameMaker() getUniqueName: generator.getUniqueNameMaker()
@ -350,7 +350,7 @@ export default function dom ( parsed, source, options, names ) {
} }
if ( options.dev ) { if ( options.dev ) {
Object.keys( generator.expectedProperties ).forEach( prop => { generator.expectedProperties.forEach( prop => {
constructorBlock.addLine( constructorBlock.addLine(
`if ( !( '${prop}' in this._state ) ) throw new Error( "Component was created without expected data property '${prop}'" );` `if ( !( '${prop}' in this._state ) ) throw new Error( "Component was created without expected data property '${prop}'" );`
); );
@ -422,17 +422,17 @@ export default function dom ( parsed, source, options, names ) {
throw new Error( `Components with shared helpers must be compiled to ES2015 modules (format: 'es')` ); throw new Error( `Components with shared helpers must be compiled to ES2015 modules (format: 'es')` );
} }
const names = Object.keys( generator.uses ).map( name => { const names = Array.from( generator.uses ).map( name => {
return name !== generator.aliases[ name ] ? `${name} as ${generator.aliases[ name ]}` : name; return name !== generator.aliases.get( name ) ? `${name} as ${generator.aliases.get( name )}` : name;
}); });
builders.main.addLineAtStart( builders.main.addLineAtStart(
`import { ${names.join( ', ' )} } from ${JSON.stringify( sharedPath )}` `import { ${names.join( ', ' )} } from ${JSON.stringify( sharedPath )}`
); );
} else { } else {
Object.keys( generator.uses ).forEach( key => { generator.uses.forEach( key => {
const fn = shared[ key ]; // eslint-disable-line import/namespace const fn = shared[ key ]; // eslint-disable-line import/namespace
builders.main.addBlock( fn.toString().replace( /^function [^(]*/, 'function ' + generator.aliases[ key ] ) ); builders.main.addBlock( fn.toString().replace( /^function [^(]*/, 'function ' + generator.aliases.get( key ) ) );
}); });
} }

@ -32,8 +32,8 @@ export default {
const initialProps = local.allUsedContexts.map( contextName => { const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`; if ( contextName === 'root' ) return `root: root`;
const listName = generator.current.listNames[ contextName ]; const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames[ contextName ]; const indexName = generator.current.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`; return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' ); }).join( ',\n' );
@ -41,8 +41,8 @@ export default {
const updates = local.allUsedContexts.map( contextName => { const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}._context.root = root;`; if ( contextName === 'root' ) return `${name}._context.root = root;`;
const listName = generator.current.listNames[ contextName ]; const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames[ contextName ]; const indexName = generator.current.indexNames.get( contextName );
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`; return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
}).join( '\n' ); }).join( '\n' );
@ -106,7 +106,7 @@ export default {
componentInitProperties.push(`data: ${name}_initialData`); componentInitProperties.push(`data: ${name}_initialData`);
} }
const expression = node.name === ':Self' ? generator.name : generator.importedComponents[ 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.init.addBlockAtStart( deindent` local.init.addBlockAtStart( deindent`
${statements.join( '\n\n' )} ${statements.join( '\n\n' )}

@ -2,10 +2,7 @@ import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import getBuilders from '../utils/getBuilders.js'; import getBuilders from '../utils/getBuilders.js';
const reserved = { const reserved = new Set( [ 'component', 'root' ] );
component: true,
root: true
};
export default { export default {
enter ( generator, node ) { enter ( generator, node ) {
@ -171,28 +168,29 @@ export default {
generator.generateBlock( node.else, renderElse ); generator.generateBlock( node.else, renderElse );
} }
const indexNames = Object.assign( {}, generator.current.indexNames ); const indexNames = new Map( generator.current.indexNames );
const indexName = indexNames[ node.context ] = ( node.index || `${node.context}__index` ); const indexName = node.index || `${node.context}__index`;
indexNames.set( node.context, indexName );
const listNames = Object.assign( {}, generator.current.listNames ); const listNames = new Map( generator.current.listNames );
listNames[ node.context ] = listName; listNames.set( node.context, listName );
// ensure that contexts like `root` or `component` don't blow up the whole show // ensure that contexts like `root` or `component` don't blow up the whole show
let context = node.context; let context = node.context;
let c = 1; let c = 1;
while ( context in reserved || ~generator.current.params.indexOf( context ) ) { while ( reserved.has( context ) || ~generator.current.params.indexOf( context ) ) {
context = `${node.context}$${c++}`; context = `${node.context}$${c++}`;
} }
const contexts = Object.assign( {}, generator.current.contexts ); const contexts = new Map( generator.current.contexts );
contexts[ node.context ] = context; contexts.set( node.context, context );
const indexes = Object.assign( {}, generator.current.indexes ); const indexes = new Map( generator.current.indexes );
if ( node.index ) indexes[ indexName ] = node.context; if ( node.index ) indexes.set( indexName, node.context );
const contextDependencies = Object.assign( {}, generator.current.contextDependencies ); const contextDependencies = new Map( generator.current.contextDependencies );
contextDependencies[ node.context ] = dependencies; contextDependencies.set( node.context, dependencies );
const blockParams = generator.current.params.concat( listName, context, indexName ); const blockParams = generator.current.params.concat( listName, context, indexName );

@ -14,7 +14,8 @@ export default {
return meta[ node.name ].enter( generator, node ); return meta[ node.name ].enter( generator, node );
} }
const isComponent = node.name in generator.components || node.name === ':Self'; const isComponent = generator.components.has( node.name ) || node.name === ':Self';
if ( isComponent ) { if ( isComponent ) {
return Component.enter( generator, node ); return Component.enter( generator, node );
} }
@ -41,8 +42,8 @@ export default {
const initialProps = local.allUsedContexts.map( contextName => { const initialProps = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `root: root`; if ( contextName === 'root' ) return `root: root`;
const listName = generator.current.listNames[ contextName ]; const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames[ contextName ]; const indexName = generator.current.indexNames.get( contextName );
return `${listName}: ${listName},\n${indexName}: ${indexName}`; return `${listName}: ${listName},\n${indexName}: ${indexName}`;
}).join( ',\n' ); }).join( ',\n' );
@ -50,8 +51,8 @@ export default {
const updates = local.allUsedContexts.map( contextName => { const updates = local.allUsedContexts.map( contextName => {
if ( contextName === 'root' ) return `${name}.__svelte.root = root;`; if ( contextName === 'root' ) return `${name}.__svelte.root = root;`;
const listName = generator.current.listNames[ contextName ]; const listName = generator.current.listNames.get( contextName );
const indexName = generator.current.indexNames[ contextName ]; const indexName = generator.current.indexNames.get( contextName );
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`; return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`;
}).join( '\n' ); }).join( '\n' );
@ -114,7 +115,8 @@ export default {
return; return;
} }
const isComponent = node.name in generator.components; const isComponent = generator.components.has( node.name );
if ( isComponent ) { if ( isComponent ) {
return Component.leave( generator, node ); return Component.leave( generator, node );
} }

@ -94,8 +94,8 @@ export default function addComponentAttributes ( generator, node, local ) {
const declarations = usedContexts.map( name => { const declarations = usedContexts.map( name => {
if ( name === 'root' ) return 'var root = this._context.root;'; if ( name === 'root' ) return 'var root = this._context.root;';
const listName = generator.current.listNames[ name ]; const listName = generator.current.listNames.get( name );
const indexName = generator.current.indexNames[ name ]; const indexName = generator.current.indexNames.get( name );
return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`; return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`;
}); });

@ -12,14 +12,14 @@ export default function createBinding ( generator, node, attribute, current, loc
if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context ); if ( !~local.allUsedContexts.indexOf( context ) ) local.allUsedContexts.push( context );
}); });
const contextual = name in current.contexts; const contextual = current.contexts.has( name );
let obj; let obj;
let prop; let prop;
if ( contextual ) { if ( contextual ) {
obj = current.listNames[ name ]; obj = current.listNames.get( name );
prop = current.indexNames[ name ]; prop = current.indexNames.get( name );
} else if ( attribute.value.type === 'MemberExpression' ) { } else if ( attribute.value.type === 'MemberExpression' ) {
prop = `'[✂${attribute.value.property.start}-${attribute.value.property.end}✂]}'`; prop = `'[✂${attribute.value.property.start}-${attribute.value.property.end}✂]}'`;
obj = `root.[✂${attribute.value.object.start}-${attribute.value.object.end}✂]}`; obj = `root.[✂${attribute.value.object.start}-${attribute.value.object.end}✂]}`;

@ -171,8 +171,8 @@ export default function addElementAttributes ( generator, node, local ) {
const declarations = usedContexts.map( name => { const declarations = usedContexts.map( name => {
if ( name === 'root' ) return 'var root = this.__svelte.root;'; if ( name === 'root' ) return 'var root = this.__svelte.root;';
const listName = generator.current.listNames[ name ]; const listName = generator.current.listNames.get( name );
const indexName = generator.current.indexNames[ name ]; const indexName = generator.current.indexNames.get( name );
return `var ${listName} = this.__svelte.${listName}, ${indexName} = this.__svelte.${indexName}, ${name} = ${listName}[${indexName}]`; return `var ${listName} = this.__svelte.${listName}, ${indexName} = this.__svelte.${indexName}, ${name} = ${listName}[${indexName}]`;
}); });
@ -180,7 +180,7 @@ export default function addElementAttributes ( generator, node, local ) {
const handlerName = generator.current.getUniqueName( `${name}Handler` ); const handlerName = generator.current.getUniqueName( `${name}Handler` );
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}✂];`;
if ( name in generator.events ) { if ( generator.events.has( name ) ) {
local.init.addBlock( deindent` local.init.addBlock( deindent`
var ${handlerName} = ${generator.alias( 'template' )}.events.${name}.call( component, ${local.name}, function ( event ) { var ${handlerName} = ${generator.alias( 'template' )}.events.${name}.call( component, ${local.name}, function ( event ) {
${handlerBody} ${handlerBody}

@ -1,19 +1,19 @@
import deindent from '../../../../../utils/deindent.js'; import deindent from '../../../../../utils/deindent.js';
export default function getSetter ({ current, name, context, attribute, dependencies, snippet, value }) { export default function getSetter ({ current, name, context, attribute, dependencies, snippet, value }) {
if ( name in current.contexts ) { if ( current.contexts.has( name ) ) {
const prop = dependencies[0]; const prop = dependencies[0];
const tail = attribute.value.type === 'MemberExpression' ? getTailSnippet( attribute.value ) : ''; const tail = attribute.value.type === 'MemberExpression' ? getTailSnippet( attribute.value ) : '';
return deindent` return deindent`
var list = this.${context}.${current.listNames[ name ]}; var list = this.${context}.${current.listNames.get( name )};
var index = this.${context}.${current.indexNames[ name ]}; var index = this.${context}.${current.indexNames.get( name )};
list[index]${tail} = ${value}; list[index]${tail} = ${value};
component._set({ ${prop}: component.get( '${prop}' ) }); component._set({ ${prop}: component.get( '${prop}' ) });
`; `;
} }
if ( attribute.value.type === 'MemberExpression' ) { if ( attribute.value.type === 'MemberExpression' ) {
return deindent` return deindent`
var ${name} = component.get( '${name}' ); var ${name} = component.get( '${name}' );
@ -21,7 +21,7 @@ export default function getSetter ({ current, name, context, attribute, dependen
component._set({ ${name}: ${name} }); component._set({ ${name}: ${name} });
`; `;
} }
return `component._set({ ${name}: ${value} });`; return `component._set({ ${name}: ${value} });`;
} }
@ -31,4 +31,4 @@ function getTailSnippet ( node ) {
const start = node.end; const start = node.end;
return `[✂${start}-${end}✂]`; return `[✂${start}-${end}✂]`;
} }

@ -52,8 +52,8 @@ export default function ssr ( parsed, source, options, names ) {
// create main render() function // create main render() function
generator.push({ generator.push({
contexts: {}, contexts: new Map(),
indexes: {}, indexes: new Map(),
conditions: [] conditions: []
}); });

@ -45,7 +45,7 @@ export default {
}) })
.concat( bindings.map( binding => { .concat( bindings.map( binding => {
const { name, keypath } = flattenReference( binding.value ); const { name, keypath } = flattenReference( binding.value );
const value = name in generator.current.contexts ? keypath : `root.${keypath}`; const value = generator.current.contexts.has( name ) ? keypath : `root.${keypath}`;
return `${binding.name}: ${value}`; return `${binding.name}: ${value}`;
})) }))
.join( ', ' ); .join( ', ' );

@ -7,14 +7,14 @@ export default {
// TODO should this be the generator's job? It's duplicated between // TODO should this be the generator's job? It's duplicated between
// here and the equivalent DOM compiler visitor // here and the equivalent DOM compiler visitor
const contexts = Object.assign( {}, generator.current.contexts ); const contexts = new Map( generator.current.contexts );
contexts[ node.context ] = node.context; contexts.set( node.context, node.context );
const indexes = Object.assign( {}, generator.current.indexes ); const indexes = new Map( generator.current.indexes );
if ( node.index ) indexes[ node.index ] = node.context; if ( node.index ) indexes.set( node.index, node.context );
const contextDependencies = Object.assign( {}, generator.current.contextDependencies ); const contextDependencies = new Map( generator.current.contextDependencies );
contextDependencies[ node.context ] = dependencies; contextDependencies.set( node.context, dependencies );
generator.push({ generator.push({
contexts, contexts,

@ -12,7 +12,7 @@ export default {
return meta[ node.name ].enter( generator, node ); return meta[ node.name ].enter( generator, node );
} }
if ( node.name in generator.components || node.name === ':Self' ) { if ( generator.components.has( node.name ) || node.name === ':Self' ) {
Component.enter( generator, node ); Component.enter( generator, node );
return; return;
} }
@ -53,7 +53,7 @@ export default {
return; return;
} }
if ( node.name in generator.components || node.name === ':Self' ) { if ( generator.components.has( node.name ) || node.name === ':Self' ) {
Component.leave( generator, node ); Component.leave( generator, node );
return; return;
} }

@ -1,14 +1,17 @@
export default function counter ( used ) { export default function counter ( used ) {
const counts = {}; const counts = new Map();
used.forEach( name => counts[ name ] = 1 ); used.forEach( name => counts.set( name, 1 ) );
return function ( name ) { return function ( name ) {
if ( name in counts ) { if ( counts.has( name ) ) {
return `${name}${counts[ name ]++}`; const count = counts.get( name );
const newName = `${name}${count}`;
counts.set( name, count + 1 );
return newName;
} }
counts[ name ] = 1; counts.set( name, 1 );
return name; return name;
}; };
} }

@ -15,35 +15,34 @@ const metaTags = {
':Window': true ':Window': true
}; };
const specials = { const specials = new Map( [
script: { [ 'script', {
read: readScript, read: readScript,
property: 'js' property: 'js'
}, } ],
[ 'style', {
style: {
read: readStyle, read: readStyle,
property: 'css' property: 'css'
} } ]
}; ] );
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
const disallowedContents = { const disallowedContents = new Map( [
li: [ 'li' ], [ 'li', new Set( [ 'li' ] ) ],
dt: [ 'dt', 'dd' ], [ 'dt', new Set( [ 'dt', 'dd' ] ) ],
dd: [ 'dt', 'dd' ], [ 'dd', new Set( [ 'dt', 'dd' ] ) ],
p: 'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split( ' ' ), [ 'p', new Set( 'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split( ' ' ) ) ],
rt: [ 'rt', 'rp' ], [ 'rt', new Set( [ 'rt', 'rp' ] ) ],
rp: [ 'rt', 'rp' ], [ 'rp', new Set( [ 'rt', 'rp' ] ) ],
optgroup: [ 'optgroup' ], [ 'optgroup', new Set( [ 'optgroup' ] ) ],
option: [ 'option', 'optgroup' ], [ 'option', new Set( [ 'option', 'optgroup' ] ) ],
thead: [ 'tbody', 'tfoot' ], [ 'thead', new Set( [ 'tbody', 'tfoot' ] ) ],
tbody: [ 'tbody', 'tfoot' ], [ 'tbody', new Set( [ 'tbody', 'tfoot' ] ) ],
tfoot: [ 'tbody' ], [ 'tfoot', new Set( [ 'tbody' ] ) ],
tr: [ 'tr', 'tbody' ], [ 'tr', new Set( [ 'tr', 'tbody' ] ) ],
td: [ 'td', 'th', 'tr' ], [ 'td', new Set( [ 'td', 'th', 'tr' ] ) ],
th: [ 'td', 'th', 'tr' ] [ 'th', new Set( [ 'td', 'th', 'tr' ] ) ],
}; ] );
function stripWhitespace ( element ) { function stripWhitespace ( element ) {
if ( element.children.length ) { if ( element.children.length ) {
@ -127,11 +126,10 @@ export default function tag ( parser ) {
parser.stack.pop(); parser.stack.pop();
return null; return null;
} else if ( parent.name in disallowedContents ) { } else if ( disallowedContents.has( parent.name ) ) {
// can this be a child of the parent element, or does it implicitly // can this be a child of the parent element, or does it implicitly
// close it, like `<li>one<li>two`? // close it, like `<li>one<li>two`?
const disallowed = disallowedContents[ parent.name ]; if ( disallowedContents.get( parent.name ).has( name ) ) {
if ( ~disallowed.indexOf( name ) ) {
stripWhitespace( parent ); stripWhitespace( parent );
parent.end = start; parent.end = start;
@ -151,8 +149,8 @@ export default function tag ( parser ) {
parser.allowWhitespace(); parser.allowWhitespace();
// special cases top-level <script> and <style> // special cases top-level <script> and <style>
if ( name in specials && parser.stack.length === 1 ) { if ( specials.has( name ) && parser.stack.length === 1 ) {
const special = specials[ name ]; const special = specials.get( name );
if ( parser[ special.property ] ) { if ( parser[ special.property ] ) {
parser.index = start; parser.index = start;
@ -358,4 +356,4 @@ function getShorthandValue ( start, name ) {
name name
} }
}]; }];
} }

@ -1,10 +1,7 @@
import checkForDupes from '../utils/checkForDupes.js'; import checkForDupes from '../utils/checkForDupes.js';
import checkForComputedKeys from '../utils/checkForComputedKeys.js'; import checkForComputedKeys from '../utils/checkForComputedKeys.js';
const isFunctionExpression = { const isFunctionExpression = new Set( [ 'FunctionExpression', 'ArrowFunctionExpression' ] );
FunctionExpression: true,
ArrowFunctionExpression: true
};
export default function computed ( validator, prop ) { export default function computed ( validator, prop ) {
if ( prop.value.type !== 'ObjectExpression' ) { if ( prop.value.type !== 'ObjectExpression' ) {
@ -16,7 +13,7 @@ export default function computed ( validator, prop ) {
checkForComputedKeys( validator, prop.value.properties ); checkForComputedKeys( validator, prop.value.properties );
prop.value.properties.forEach( computation => { prop.value.properties.forEach( computation => {
if ( !isFunctionExpression[ computation.value.type ] ) { if ( !isFunctionExpression.has( computation.value.type ) ) {
validator.error( `Computed properties can be function expressions or arrow function expressions`, computation.value.start ); validator.error( `Computed properties can be function expressions or arrow function expressions`, computation.value.start );
return; return;
} }

@ -1,15 +1,11 @@
const disallowed = { const disallowed = new Set( [ 'Literal', 'ObjectExpression', 'ArrayExpression' ] );
Literal: true,
ObjectExpression: true,
ArrayExpression: true
};
export default function data ( validator, prop ) { export default function data ( validator, prop ) {
while ( prop.type === 'ParenthesizedExpression' ) prop = prop.expression; while ( prop.type === 'ParenthesizedExpression' ) prop = prop.expression;
// TODO should we disallow references and expressions as well? // TODO should we disallow references and expressions as well?
if ( disallowed[ prop.value.type ] ) { if ( disallowed.has( prop.value.type ) ) {
validator.error( `'data' must be a function`, prop.value.start ); validator.error( `'data' must be a function`, prop.value.start );
} }
} }

@ -2,14 +2,7 @@ import checkForDupes from '../utils/checkForDupes.js';
import checkForComputedKeys from '../utils/checkForComputedKeys.js'; import checkForComputedKeys from '../utils/checkForComputedKeys.js';
import usesThisOrArguments from '../utils/usesThisOrArguments.js'; import usesThisOrArguments from '../utils/usesThisOrArguments.js';
const builtin = { const builtin = new Set( [ 'set', 'get', 'on', 'fire', 'observe', 'teardown' ] );
set: true,
get: true,
on: true,
fire: true,
observe: true,
teardown: true
};
export default function methods ( validator, prop ) { export default function methods ( validator, prop ) {
if ( prop.value.type !== 'ObjectExpression' ) { if ( prop.value.type !== 'ObjectExpression' ) {
@ -21,7 +14,7 @@ export default function methods ( validator, prop ) {
checkForComputedKeys( validator, prop.value.properties ); checkForComputedKeys( validator, prop.value.properties );
prop.value.properties.forEach( prop => { prop.value.properties.forEach( prop => {
if ( builtin[ prop.key.name ] ) { if ( builtin.has( prop.key.name ) ) {
validator.error( `Cannot overwrite built-in method '${prop.key.name}'` ); validator.error( `Cannot overwrite built-in method '${prop.key.name}'` );
} }

@ -1,11 +1,11 @@
export default function checkForDupes ( validator, properties ) { export default function checkForDupes ( validator, properties ) {
const seen = Object.create( null ); const seen = new Set();
properties.forEach( prop => { properties.forEach( prop => {
if ( seen[ prop.key.name ] ) { if ( seen.has( prop.key.name ) ) {
validator.error( `Duplicate property '${prop.key.name}'`, prop.start ); validator.error( `Duplicate property '${prop.key.name}'`, prop.start );
} }
seen[ prop.key.name ] = true; seen.add( prop.key.name );
}); });
} }

Loading…
Cancel
Save