diff --git a/compiler/generate/index.js b/compiler/generate/index.js index f69191147b..dec7bc2150 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -9,7 +9,7 @@ import getOutro from './utils/getOutro.js'; import visitors from './visitors/index.js'; import processCss from './css/process.js'; -export default function generate ( parsed, source, options ) { +export default function generate ( parsed, source, options, names ) { const format = options.format || 'es'; const renderers = []; @@ -149,15 +149,13 @@ export default function generate ( parsed, source, options ) { }; }, - // TODO use getName instead of counters - counters: { - if: 0, - each: 0 - }, - events: {}, - getName: counter(), + getUniqueName: counter( names ), + + getUniqueNameMaker () { + return counter( names ); + }, cssId: parsed.css ? `svelte-${parsed.hash}` : '', @@ -278,7 +276,7 @@ export default function generate ( parsed, source, options ) { indexNames: {}, listNames: {}, - counter: counter() + getUniqueName: generator.getUniqueNameMaker() }); parsed.html.children.forEach( generator.visit ); diff --git a/compiler/generate/utils/counter.js b/compiler/generate/utils/counter.js index 18dce577a9..7a954ccf59 100644 --- a/compiler/generate/utils/counter.js +++ b/compiler/generate/utils/counter.js @@ -1,12 +1,14 @@ -export default function counter () { +export default function counter ( used ) { const counts = {}; - return function ( label ) { - if ( label in counts ) { - return `${label}${counts[ label ]++}`; + used.forEach( name => counts[ name ] = 1 ); + + return function ( name ) { + if ( name in counts ) { + return `${name}${counts[ name ]++}`; } - counts[ label ] = 1; - return label; + counts[ name ] = 1; + return name; }; } diff --git a/compiler/generate/visitors/Component.js b/compiler/generate/visitors/Component.js index 7fcb427b41..29e42d8cd9 100644 --- a/compiler/generate/visitors/Component.js +++ b/compiler/generate/visitors/Component.js @@ -1,11 +1,10 @@ import deindent from '../utils/deindent.js'; import addComponentAttributes from './attributes/addComponentAttributes.js'; -import counter from '../utils/counter.js'; export default { enter ( generator, node ) { const hasChildren = node.children.length > 0; - const name = generator.current.counter( `${node.name[0].toLowerCase()}${node.name.slice( 1 )}` ); + const name = generator.current.getUniqueName( `${node.name[0].toLowerCase()}${node.name.slice( 1 )}` ); const local = { name, @@ -33,12 +32,12 @@ export default { ]; // Component has children if ( hasChildren ) { - const yieldName = `render${name}YieldFragment`; + const yieldName = generator.current.getUniqueName( `render${name}YieldFragment` ); // {{YIELD STUFF}} generator.push({ useAnchor: true, - name: generator.current.counter(yieldName), + name: yieldName, target: 'target', localElementDepth: 0, @@ -48,7 +47,7 @@ export default { detachStatements: [], teardownStatements: [], - counter: counter() + getUniqueName: generator.getUniqueNameMaker() }); node.children.forEach( generator.visit ); @@ -106,9 +105,15 @@ export default { if ( local.dynamicAttributes.length ) { const updates = local.dynamicAttributes.map( attribute => { - return deindent` - if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value}; - `; + if ( attribute.dependencies.length ) { + return deindent` + if ( ${attribute.dependencies.map( dependency => `'${dependency}' in changed` ).join( '||' )} ) ${name}_changes.${attribute.name} = ${attribute.value}; + `; + } + + // TODO this is an odd situation to encounter – I *think* it should only happen with + // each block indices, in which case it may be possible to optimise this + return `${name}_changes.${attribute.name} = ${attribute.value};`; }); local.update.push( deindent` diff --git a/compiler/generate/visitors/EachBlock.js b/compiler/generate/visitors/EachBlock.js index 845105160f..567683da05 100644 --- a/compiler/generate/visitors/EachBlock.js +++ b/compiler/generate/visitors/EachBlock.js @@ -1,14 +1,13 @@ import deindent from '../utils/deindent.js'; -import counter from '../utils/counter.js'; export default { enter ( generator, node ) { - const i = generator.counters.each++; - const name = `eachBlock_${i}`; + const name = generator.getUniqueName( `eachBlock` ); + const renderer = generator.getUniqueName( `renderEachBlock` ); const elseName = `${name}_else`; const iterations = `${name}_iterations`; - const renderer = `renderEachBlock_${i}`; const renderElse = `${renderer}_else`; + const i = generator.current.getUniqueName( `i` ); const { params } = generator.current; const listName = `${name}_value`; @@ -26,9 +25,9 @@ export default { var ${iterations} = []; ${node.else ? `var ${elseName} = null;` : ''} - for ( var i = 0; i < ${name}_value.length; i += 1 ) { - ${iterations}[i] = ${renderer}( ${params}, ${listName}, ${listName}[i], i, component ); - ${!isToplevel ? `${iterations}[i].mount( ${anchor}.parentNode, ${anchor} );` : ''} + for ( var ${i} = 0; ${i} < ${name}_value.length; ${i} += 1 ) { + ${iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component ); + ${!isToplevel ? `${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );` : ''} } ` ); if ( node.else ) { @@ -42,8 +41,8 @@ export default { if ( isToplevel ) { generator.current.mountStatements.push( deindent` - for ( var i = 0; i < ${iterations}.length; i += 1 ) { - ${iterations}[i].mount( ${anchor}.parentNode, ${anchor} ); + for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} ); } ` ); if ( node.else ) { @@ -58,21 +57,22 @@ export default { generator.current.updateStatements.push( deindent` var ${name}_value = ${snippet}; - for ( var i = 0; i < ${name}_value.length; i += 1 ) { - if ( !${iterations}[i] ) { - ${iterations}[i] = ${renderer}( ${params}, ${listName}, ${listName}[i], i, component ); - ${iterations}[i].mount( ${anchor}.parentNode, ${anchor} ); + for ( var ${i} = 0; ${i} < ${name}_value.length; ${i} += 1 ) { + if ( !${iterations}[${i}] ) { + ${iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component ); + ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} ); } else { - ${iterations}[i].update( changed, ${params}, ${listName}, ${listName}[i], i ); + ${iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} ); } } - for ( var i = ${name}_value.length; i < ${iterations}.length; i += 1 ) { - ${iterations}[i].teardown( true ); + for ( var ${i} = ${name}_value.length; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].teardown( true ); } ${iterations}.length = ${listName}.length; ` ); + if ( node.else ) { generator.current.updateStatements.push( deindent` if ( !${name}_value.length && ${elseName} ) { @@ -87,10 +87,11 @@ export default { } generator.current.teardownStatements.push( deindent` - for ( var i = 0; i < ${iterations}.length; i += 1 ) { - ${iterations}[i].teardown( ${isToplevel ? 'detach' : 'false'} ); + for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].teardown( ${isToplevel ? 'detach' : 'false'} ); } ` ); + if ( node.else ) { generator.current.teardownStatements.push( deindent` if ( ${elseName} ) { @@ -112,7 +113,7 @@ export default { detachStatements: [], teardownStatements: [], - counter: counter() + getUniqueName: generator.getUniqueNameMaker() }); node.else.children.forEach( generator.visit ); generator.addRenderer( generator.current ); @@ -163,9 +164,7 @@ export default { detachStatements: [], teardownStatements: [], - counter: counter(), - - parent: generator.current + getUniqueName: generator.getUniqueNameMaker() }); }, diff --git a/compiler/generate/visitors/Element.js b/compiler/generate/visitors/Element.js index 6ffad9635d..5850398a58 100644 --- a/compiler/generate/visitors/Element.js +++ b/compiler/generate/visitors/Element.js @@ -9,7 +9,7 @@ export default { return Component.enter( generator, node ); } - const name = generator.current.counter( node.name ); + const name = generator.current.getUniqueName( node.name ); const local = { name, diff --git a/compiler/generate/visitors/IfBlock.js b/compiler/generate/visitors/IfBlock.js index 254b7f0969..cf905d4fea 100644 --- a/compiler/generate/visitors/IfBlock.js +++ b/compiler/generate/visitors/IfBlock.js @@ -1,5 +1,4 @@ import deindent from '../utils/deindent.js'; -import counter from '../utils/counter.js'; // collect all the conditions and blocks in the if/elseif/else chain function generateBlock ( generator, node, name ) { @@ -16,7 +15,7 @@ function generateBlock ( generator, node, name ) { detachStatements: [], teardownStatements: [], - counter: counter() + getUniqueName: generator.getUniqueNameMaker() }); node.children.forEach( generator.visit ); generator.addRenderer( generator.current ); @@ -54,15 +53,13 @@ function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { export default { enter ( generator, node ) { - const i = generator.counters.if++; - const { params } = generator.current; - const name = `ifBlock_${i}`; - const getBlock = `getBlock_${i}`; - const currentBlock = `currentBlock_${i}`; + const name = generator.getUniqueName( `ifBlock` ); + const getBlock = generator.getUniqueName( `getBlock` ); + const currentBlock = generator.getUniqueName( `currentBlock` ); const isToplevel = generator.current.localElementDepth === 0; - const conditionsAndBlocks = getConditionsAndBlocks( generator, node, `renderIfBlock_${i}` ); + const conditionsAndBlocks = getConditionsAndBlocks( generator, node, generator.getUniqueName( `renderIfBlock` ) ); const anchor = generator.createAnchor( name, `#if ${generator.source.slice( node.expression.start, node.expression.end )}` ); diff --git a/compiler/generate/visitors/MustacheTag.js b/compiler/generate/visitors/MustacheTag.js index a7078e1e28..a29a8473fc 100644 --- a/compiler/generate/visitors/MustacheTag.js +++ b/compiler/generate/visitors/MustacheTag.js @@ -2,7 +2,7 @@ import deindent from '../utils/deindent.js'; export default { enter ( generator, node ) { - const name = generator.current.counter( 'text' ); + const name = generator.current.getUniqueName( 'text' ); generator.addSourcemapLocations( node.expression ); const { snippet } = generator.contextualise( node.expression ); diff --git a/compiler/generate/visitors/RawMustacheTag.js b/compiler/generate/visitors/RawMustacheTag.js index 1b0b39e302..8ffb29137e 100644 --- a/compiler/generate/visitors/RawMustacheTag.js +++ b/compiler/generate/visitors/RawMustacheTag.js @@ -2,7 +2,7 @@ import deindent from '../utils/deindent.js'; export default { enter ( generator, node ) { - const name = generator.current.counter( 'raw' ); + const name = generator.current.getUniqueName( 'raw' ); generator.addSourcemapLocations( node.expression ); const { snippet } = generator.contextualise( node.expression ); @@ -25,7 +25,7 @@ export default { } `; - if ( isToplevel ) { + if ( isToplevel ) { generator.current.mountStatements.push(mountStatement); } else { generator.current.initStatements.push(mountStatement); diff --git a/compiler/generate/visitors/Text.js b/compiler/generate/visitors/Text.js index dbf64ce880..ead59ed3b3 100644 --- a/compiler/generate/visitors/Text.js +++ b/compiler/generate/visitors/Text.js @@ -1,6 +1,6 @@ export default { enter ( generator, node ) { - const name = generator.current.counter( `text` ); + const name = generator.current.getUniqueName( `text` ); generator.addElement( name, `document.createTextNode( ${JSON.stringify( node.data )} )` ); } }; diff --git a/compiler/generate/visitors/attributes/addElementAttributes.js b/compiler/generate/visitors/attributes/addElementAttributes.js index 6f372b24ee..0189f988ea 100644 --- a/compiler/generate/visitors/attributes/addElementAttributes.js +++ b/compiler/generate/visitors/attributes/addElementAttributes.js @@ -138,7 +138,7 @@ export default function addElementAttributes ( generator, node, local ) { return `var ${listName} = this.__svelte.${listName}, ${indexName} = this.__svelte.${indexName}, ${name} = ${listName}[${indexName}]`; }); - const handlerName = generator.current.counter( `${attribute.name}Handler` ); + const handlerName = generator.current.getUniqueName( `${attribute.name}Handler` ); const handlerBody = ( declarations.length ? declarations.join( '\n' ) + '\n\n' : '' ) + `[✂${attribute.expression.start}-${attribute.expression.end}✂];`; if ( attribute.name in generator.events ) { diff --git a/compiler/generate/visitors/attributes/binding/index.js b/compiler/generate/visitors/attributes/binding/index.js index c2d0485d3f..677ec71059 100644 --- a/compiler/generate/visitors/attributes/binding/index.js +++ b/compiler/generate/visitors/attributes/binding/index.js @@ -17,7 +17,7 @@ export default function createBinding ( generator, node, attribute, current, loc }); } - const handler = current.counter( `${local.name}ChangeHandler` ); + const handler = current.getUniqueName( `${local.name}ChangeHandler` ); let setter; let eventName = 'change'; diff --git a/compiler/index.js b/compiler/index.js index 1ee0dcfa42..01d0685b9c 100644 --- a/compiler/index.js +++ b/compiler/index.js @@ -15,9 +15,9 @@ export function compile ( source, options = {} ) { }; } - validate( parsed, source, options ); + const { names } = validate( parsed, source, options ); - return generate( parsed, source, options ); + return generate( parsed, source, options, names ); } export { parse, validate }; diff --git a/compiler/validate/html/index.js b/compiler/validate/html/index.js new file mode 100644 index 0000000000..d7d7378fbd --- /dev/null +++ b/compiler/validate/html/index.js @@ -0,0 +1,14 @@ +export default function validateHtml ( validator, html ) { + function visit ( node ) { + if ( node.type === 'EachBlock' ) { + if ( !~validator.names.indexOf( node.context ) ) validator.names.push( node.context ); + if ( node.index && !~validator.names.indexOf( node.index ) ) validator.names.push( node.index ); + } + + if ( node.children ) { + node.children.forEach( visit ); + } + } + + html.children.forEach( visit ); +} diff --git a/compiler/validate/index.js b/compiler/validate/index.js index a2ee763a2c..89811421e5 100644 --- a/compiler/validate/index.js +++ b/compiler/validate/index.js @@ -1,4 +1,5 @@ import validateJs from './js/index.js'; +import validateHtml from './html/index.js'; import { getLocator } from 'locate-character'; import getCodeFrame from '../utils/getCodeFrame.js'; @@ -39,16 +40,18 @@ export default function validate ( parsed, source, options ) { templateProperties: {}, - errors: [], - warnings: [] + names: [] }; + if ( parsed.html ) { + validateHtml( validator, parsed.html ); + } + if ( parsed.js ) { validateJs( validator, parsed.js ); } return { - errors: validator.errors, - warnings: validator.warnings + names: validator.names }; } diff --git a/test/compiler/names-deconflicted-nested/_config.js b/test/compiler/names-deconflicted-nested/_config.js new file mode 100644 index 0000000000..f666a00448 --- /dev/null +++ b/test/compiler/names-deconflicted-nested/_config.js @@ -0,0 +1,15 @@ +export default { + data: { + array: [ [0,0,0], [0,0,0], [0,0,0] ] + }, + + html: ` +
+ [ 0, 0 ][ 0, 1 ][ 0, 2 ] +
+ [ 1, 0 ][ 1, 1 ][ 1, 2 ] +
+ [ 2, 0 ][ 2, 1 ][ 2, 2 ] +
+ ` +}; diff --git a/test/compiler/names-deconflicted-nested/main.html b/test/compiler/names-deconflicted-nested/main.html new file mode 100644 index 0000000000..7de2cf204d --- /dev/null +++ b/test/compiler/names-deconflicted-nested/main.html @@ -0,0 +1,7 @@ +{{#each array as row, i}} +
+ {{#each row as cell, j}} + [ {{i}}, {{j}} ] + {{/each}} +
+{{/each}} diff --git a/test/compiler/names-deconflicted/Widget.html b/test/compiler/names-deconflicted/Widget.html new file mode 100644 index 0000000000..9a6074567b --- /dev/null +++ b/test/compiler/names-deconflicted/Widget.html @@ -0,0 +1 @@ +

{{index + 1}}: {{widget.name}}

diff --git a/test/compiler/names-deconflicted/_config.js b/test/compiler/names-deconflicted/_config.js new file mode 100644 index 0000000000..0c4f9d910a --- /dev/null +++ b/test/compiler/names-deconflicted/_config.js @@ -0,0 +1,14 @@ +export default { + html: `

1: foo

2: bar

3: baz

`, + + test ( assert, component, target ) { + component.set({ + widgets: [ + { name: 'bish' }, + { name: 'bosh' } + ] + }); + + assert.htmlEqual( target.innerHTML, `

1: bish

2: bosh

` ); + } +}; diff --git a/test/compiler/names-deconflicted/main.html b/test/compiler/names-deconflicted/main.html new file mode 100644 index 0000000000..ca6eac71db --- /dev/null +++ b/test/compiler/names-deconflicted/main.html @@ -0,0 +1,21 @@ +{{#each widgets as widget, i}} + +{{/each}} + + diff --git a/test/test.js b/test/test.js index 297406c72f..f53bef8a19 100644 --- a/test/test.js +++ b/test/test.js @@ -177,7 +177,7 @@ describe( 'svelte', () => { const errors = []; const warnings = []; - svelte.validate( parsed, input, { + const { names } = svelte.validate( parsed, input, { onerror ( error ) { errors.push({ message: error.message, @@ -197,9 +197,11 @@ describe( 'svelte', () => { const expectedErrors = tryToLoadJson( `test/validator/${dir}/errors.json` ) || []; const expectedWarnings = tryToLoadJson( `test/validator/${dir}/warnings.json` ) || []; + const expectedNames = tryToLoadJson( `test/validator/${dir}/names.json` ) || []; assert.deepEqual( errors, expectedErrors ); assert.deepEqual( warnings, expectedWarnings ); + assert.deepEqual( names, expectedNames ); } catch ( err ) { if ( err.name !== 'ParseError' ) throw err; diff --git a/test/validator/names/input.html b/test/validator/names/input.html new file mode 100644 index 0000000000..a944d5fec0 --- /dev/null +++ b/test/validator/names/input.html @@ -0,0 +1,3 @@ +{{#each things as thing, index}} +

{{index}}: {{thing}}

+{{/each}} diff --git a/test/validator/names/names.json b/test/validator/names/names.json new file mode 100644 index 0000000000..01151a29fd --- /dev/null +++ b/test/validator/names/names.json @@ -0,0 +1,4 @@ +[ + "thing", + "index" +]