From 854a37c0d7c6cb3c949cf96200da49f50bcf0198 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 17 Mar 2017 15:49:21 -0400 Subject: [PATCH] allow reserved words in tags e.g. {{class}} (#383) --- src/parse/read/expression.js | 17 +- .../attribute-dynamic-reserved/_config.js | 14 ++ .../attribute-dynamic-reserved/main.html | 1 + .../attribute-dynamic-reserved/input.html | 1 + .../attribute-dynamic-reserved/output.json | 40 +++++ tmp.js | 167 ++++++++++++++++++ 6 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 test/generator/samples/attribute-dynamic-reserved/_config.js create mode 100644 test/generator/samples/attribute-dynamic-reserved/main.html create mode 100644 test/parser/samples/attribute-dynamic-reserved/input.html create mode 100644 test/parser/samples/attribute-dynamic-reserved/output.json create mode 100644 tmp.js diff --git a/src/parse/read/expression.js b/src/parse/read/expression.js index e0d5812d72..61cebc9cc7 100644 --- a/src/parse/read/expression.js +++ b/src/parse/read/expression.js @@ -1,13 +1,24 @@ import { parseExpressionAt } from 'acorn'; export default function readExpression ( parser ) { + const start = parser.index; + + const name = parser.readUntil( /\s*}}/ ); + if ( name && /^\w+$/.test( name ) ) { + return { + type: 'Identifier', + start, + end: start + name.length, + name + }; + } + + parser.index = start; + try { const node = parseExpressionAt( parser.template, parser.index ); parser.index = node.end; - // TODO check it's a valid expression. probably shouldn't have - // [arrow] function expressions, etc - return node; } catch ( err ) { parser.acornError( err ); diff --git a/test/generator/samples/attribute-dynamic-reserved/_config.js b/test/generator/samples/attribute-dynamic-reserved/_config.js new file mode 100644 index 0000000000..e14059fec0 --- /dev/null +++ b/test/generator/samples/attribute-dynamic-reserved/_config.js @@ -0,0 +1,14 @@ +export default { + data: { + class: 'foo' + }, + + html: `
`, + + test ( assert, component, target ) { + component.set({ class: 'bar' }); + assert.equal( target.innerHTML, `
` ); + + component.destroy(); + } +}; diff --git a/test/generator/samples/attribute-dynamic-reserved/main.html b/test/generator/samples/attribute-dynamic-reserved/main.html new file mode 100644 index 0000000000..6b149d165f --- /dev/null +++ b/test/generator/samples/attribute-dynamic-reserved/main.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic-reserved/input.html b/test/parser/samples/attribute-dynamic-reserved/input.html new file mode 100644 index 0000000000..6b149d165f --- /dev/null +++ b/test/parser/samples/attribute-dynamic-reserved/input.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic-reserved/output.json b/test/parser/samples/attribute-dynamic-reserved/output.json new file mode 100644 index 0000000000..a097655e2a --- /dev/null +++ b/test/parser/samples/attribute-dynamic-reserved/output.json @@ -0,0 +1,40 @@ +{ + "hash": 3305933215, + "html": { + "start": 0, + "end": 29, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 29, + "type": "Element", + "name": "div", + "attributes": [ + { + "start": 5, + "end": 22, + "type": "Attribute", + "name": "class", + "value": [ + { + "start": 12, + "end": 21, + "type": "MustacheTag", + "expression": { + "type": "Identifier", + "start": 14, + "end": 19, + "name": "class" + } + } + ] + } + ], + "children": [] + } + ] + }, + "css": null, + "js": null +} \ No newline at end of file diff --git a/tmp.js b/tmp.js new file mode 100644 index 0000000000..7caf1af725 --- /dev/null +++ b/tmp.js @@ -0,0 +1,167 @@ +import { createComment, insertNode, detachNode, teardownEach, createElement, addEventListener, removeEventListener, appendNode, createText, dispatchObservers, proto } from "/Users/rharris/Documents/www/SVELTE/svelte/shared.js" + +function renderMainFragment ( root, component ) { + var eachBlock_anchor = createComment(); + var eachBlock_value = root.values; + var eachBlock_iterations = []; + + for ( var i = 0; i < eachBlock_value.length; i += 1 ) { + eachBlock_iterations[i] = renderEachBlock( root, eachBlock_value, eachBlock_value[i], i, component ); + } + + var text = createText( "\n\n" ); + + var p = createElement( 'p' ); + + var last_text1 = root.selected + var text1 = createText( last_text1 ); + appendNode( text1, p ); + + return { + mount: function ( target, anchor ) { + insertNode( eachBlock_anchor, target, anchor ); + + for ( var i = 0; i < eachBlock_iterations.length; i += 1 ) { + eachBlock_iterations[i].mount( eachBlock_anchor.parentNode, eachBlock_anchor ); + } + + insertNode( text, target, anchor ); + insertNode( p, target, anchor ); + }, + + update: function ( changed, root ) { + var __tmp; + + var eachBlock_value = root.values; + + for ( var i = 0; i < eachBlock_value.length; i += 1 ) { + if ( !eachBlock_iterations[i] ) { + eachBlock_iterations[i] = renderEachBlock( root, eachBlock_value, eachBlock_value[i], i, component ); + eachBlock_iterations[i].mount( eachBlock_anchor.parentNode, eachBlock_anchor ); + } else { + eachBlock_iterations[i].update( changed, root, eachBlock_value, eachBlock_value[i], i ); + } + } + + teardownEach( eachBlock_iterations, true, eachBlock_value.length ); + + eachBlock_iterations.length = eachBlock_value.length; + + if ( ( __tmp = root.selected ) !== last_text1 ) { + text1.data = last_text1 = __tmp; + } + }, + + teardown: function ( detach ) { + teardownEach( eachBlock_iterations, detach ); + + if ( detach ) { + detachNode( eachBlock_anchor ); + detachNode( text ); + detachNode( p ); + } + } + }; +} + +function renderEachBlock ( root, eachBlock_value, value, value__index, component ) { + var label = createElement( 'label' ); + + var input = createElement( 'input' ); + input.type = "checkbox"; + var last_input_value = value; + input.value = input.__value = last_input_value; + + var input_updating = false; + + function inputChangeHandler () { + input_updating = true; + component._set({ selected: getBindingGroupValue( component._bindingGroups[0] ) }); + input_updating = false; + } + + addEventListener( input, 'change', inputChangeHandler ); + + appendNode( input, label ); + + input.group = root.selected; + + appendNode( createText( " " ), label ); + var last_text1 = value + var text1 = createText( last_text1 ); + appendNode( text1, label ); + + return { + mount: function ( target, anchor ) { + insertNode( label, target, anchor ); + }, + + update: function ( changed, root, eachBlock_value, value, value__index ) { + var __tmp; + + if ( ( __tmp = value ) !== last_input_value ) { + last_input_value = __tmp; + input.value = input.__value = last_input_value; + } + + if ( !input_updating ) { + input.checked = root.selected.indexOf( last_input_value ) !== -1; + } + + if ( ( __tmp = value ) !== last_text1 ) { + text1.data = last_text1 = __tmp; + } + }, + + teardown: function ( detach ) { + removeEventListener( input, 'change', inputChangeHandler ); + + if ( detach ) { + detachNode( label ); + } + } + }; +} + +function SvelteComponent ( options ) { + options = options || {}; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root; + this._yield = options._yield; + + this._torndown = false; + + this._fragment = renderMainFragment( this._state, this ); + if ( options.target ) this._fragment.mount( options.target, null ); +} + +SvelteComponent.prototype = Object.assign( {}, proto ); + +SvelteComponent.prototype._set = function _set ( newState ) { + var oldState = this._state; + this._state = Object.assign( {}, oldState, newState ); + + dispatchObservers( this, this._observers.pre, newState, oldState ); + if ( this._fragment ) this._fragment.update( newState, this._state ); + dispatchObservers( this, this._observers.post, newState, oldState ); +}; + +SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = function destroy ( detach ) { + this.fire( 'destroy' ); + + this._fragment.teardown( detach !== false ); + this._fragment = null; + + this._state = {}; + this._torndown = true; +}; + +export default SvelteComponent; \ No newline at end of file