From 02068bfb3aed13da3209cf0c089ea60a8001e2e2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 May 2017 12:29:01 -0400 Subject: [PATCH 1/5] separate parsing rules for textareas --- src/parse/state/tag.ts | 91 +++++++++++-------- .../samples/textarea-children/input.html | 3 + .../samples/textarea-children/output.json | 44 +++++++++ 3 files changed, 99 insertions(+), 39 deletions(-) create mode 100644 test/parser/samples/textarea-children/input.html create mode 100644 test/parser/samples/textarea-children/output.json diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 97d34db372..47b8b1c7d6 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -9,7 +9,6 @@ import { Parser } from '../index'; import { Node } from '../../interfaces'; const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; -const invalidUnquotedAttributeCharacters = /[\s"'=<>\/`]/; const SELF = ':Self'; @@ -181,6 +180,11 @@ export default function tag ( parser: Parser ) { if ( selfClosing ) { element.end = parser.index; + } else if ( name === 'textarea' ) { + // special case + element.children = readSequence( parser, () => parser.template.slice( parser.index, parser.index + 11 ) === '' ); + parser.read( /<\/textarea>/ ); + element.end = parser.index; } else { // don't push self-closing elements onto the stack parser.stack.push( element ); @@ -280,11 +284,41 @@ function readAttribute ( parser: Parser, uniqueNames ) { } function readAttributeValue ( parser: Parser ) { - let quoteMark; + const quoteMark = ( + parser.eat( `'` ) ? `'` : + parser.eat( `"` ) ? `"` : + null + ); + + const regex = ( + quoteMark === `'` ? /'/ : + quoteMark === `"` ? /"/ : + /[\s"'=<>\/`]/ + ); + + const value = readSequence( parser, () => regex.test( parser.template[ parser.index ] ) ); + + if ( quoteMark ) parser.index += 1; + return value; +} - if ( parser.eat( `'` ) ) quoteMark = `'`; - if ( parser.eat( `"` ) ) quoteMark = `"`; +function getShorthandValue ( start: number, name: string ) { + const end = start + name.length; + return [{ + type: 'AttributeShorthand', + start, + end, + expression: { + type: 'Identifier', + start, + end, + name + } + }]; +} + +function readSequence ( parser: Parser, done: () => boolean ) { let currentChunk: Node = { start: parser.index, end: null, @@ -292,16 +326,24 @@ function readAttributeValue ( parser: Parser ) { data: '' }; - const done = quoteMark ? - char => char === quoteMark : - char => invalidUnquotedAttributeCharacters.test( char ); - const chunks = []; while ( parser.index < parser.template.length ) { const index = parser.index; - if ( parser.eat( '{{' ) ) { + if ( done() ) { + currentChunk.end = parser.index; + + if ( currentChunk.data ) chunks.push( currentChunk ); + + chunks.forEach( chunk => { + if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data ); + }); + + return chunks; + } + + else if ( parser.eat( '{{' ) ) { if ( currentChunk.data ) { currentChunk.end = index; chunks.push( currentChunk ); @@ -328,39 +370,10 @@ function readAttributeValue ( parser: Parser ) { }; } - else if ( done( parser.template[ parser.index ] ) ) { - currentChunk.end = parser.index; - if ( quoteMark ) parser.index += 1; - - if ( currentChunk.data ) chunks.push( currentChunk ); - - chunks.forEach( chunk => { - if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data ); - }); - - return chunks; - } - else { currentChunk.data += parser.template[ parser.index++ ]; } } parser.error( `Unexpected end of input` ); -} - -function getShorthandValue ( start: number, name: string ) { - const end = start + name.length; - - return [{ - type: 'AttributeShorthand', - start, - end, - expression: { - type: 'Identifier', - start, - end, - name - } - }]; -} +} \ No newline at end of file diff --git a/test/parser/samples/textarea-children/input.html b/test/parser/samples/textarea-children/input.html new file mode 100644 index 0000000000..de50c807d6 --- /dev/null +++ b/test/parser/samples/textarea-children/input.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/parser/samples/textarea-children/output.json b/test/parser/samples/textarea-children/output.json new file mode 100644 index 0000000000..4b67ac108a --- /dev/null +++ b/test/parser/samples/textarea-children/output.json @@ -0,0 +1,44 @@ +{ + "hash": 3618147195, + "html": { + "start": 0, + "end": 63, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 63, + "type": "Element", + "name": "textarea", + "attributes": [], + "children": [ + { + "start": 10, + "end": 40, + "type": "Text", + "data": "\n\t

not actually an element. " + }, + { + "start": 40, + "end": 47, + "type": "MustacheTag", + "expression": { + "type": "Identifier", + "start": 42, + "end": 45, + "name": "foo" + } + }, + { + "start": 47, + "end": 52, + "type": "Text", + "data": "

\n" + } + ] + } + ] + }, + "css": null, + "js": null +} \ No newline at end of file From e46e631a489424042ce26e9789540c628a9d5e5f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 May 2017 12:29:44 -0400 Subject: [PATCH 2/5] prevent textarea from having both value and children --- src/validate/html/validateElement.ts | 8 ++++++++ .../validator/samples/textarea-value-children/errors.json | 8 ++++++++ test/validator/samples/textarea-value-children/input.html | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 test/validator/samples/textarea-value-children/errors.json create mode 100644 test/validator/samples/textarea-value-children/input.html diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts index 6610e7ee69..18c906b298 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -77,6 +77,14 @@ export default function validateElement ( validator: Validator, node: Node ) { 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 \ No newline at end of file From 791bbc0ad585601c2e9f8297d99d7100d82fc148 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 27 May 2017 12:30:29 -0400 Subject: [PATCH 3/5] use value property for textareas --- src/generators/dom/visitors/Element/lookup.ts | 2 +- test/runtime/samples/textarea-value/_config.js | 17 +++++++++++++++++ test/runtime/samples/textarea-value/main.html | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/textarea-value/_config.js create mode 100644 test/runtime/samples/textarea-value/main.html diff --git a/src/generators/dom/visitors/Element/lookup.ts b/src/generators/dom/visitors/Element/lookup.ts index 41fc249e61..de48507fea 100644 --- a/src/generators/dom/visitors/Element/lookup.ts +++ b/src/generators/dom/visitors/Element/lookup.ts @@ -109,7 +109,7 @@ const lookup = { 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' ] }, + value: { appliesTo: [ 'button', 'option', 'input', 'li', 'meter', 'progress', 'param', 'select', 'textarea' ] }, width: { appliesTo: [ 'canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video' ] }, wrap: { appliesTo: [ 'textarea' ] } }; diff --git a/test/runtime/samples/textarea-value/_config.js b/test/runtime/samples/textarea-value/_config.js new file mode 100644 index 0000000000..89ff63508c --- /dev/null +++ b/test/runtime/samples/textarea-value/_config.js @@ -0,0 +1,17 @@ +export default { + 'skip-ssr': true, // SSR behaviour is awkwardly different + + data: { + foo: 42 + }, + + html: ``, + + test ( assert, component, target ) { + const textarea = target.querySelector( 'textarea' ); + assert.strictEqual( textarea.value, '42' ); + + component.set({ foo: 43 }); + assert.strictEqual( textarea.value, '43' ); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/textarea-value/main.html b/test/runtime/samples/textarea-value/main.html new file mode 100644 index 0000000000..215166ab10 --- /dev/null +++ b/test/runtime/samples/textarea-value/main.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/server-side-rendering/samples/textarea-children/main.html b/test/server-side-rendering/samples/textarea-children/main.html new file mode 100644 index 0000000000..7209683448 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-children/main.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/textarea-value/_expected.html b/test/server-side-rendering/samples/textarea-value/_expected.html new file mode 100644 index 0000000000..cd39aabde5 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-value/_expected.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/server-side-rendering/samples/textarea-value/main.html b/test/server-side-rendering/samples/textarea-value/main.html new file mode 100644 index 0000000000..33375b5785 --- /dev/null +++ b/test/server-side-rendering/samples/textarea-value/main.html @@ -0,0 +1,9 @@ +`, + + test ( assert, component, target ) { + const textarea = target.querySelector( 'textarea' ); + assert.strictEqual( textarea.value, `\n\t

not actually an element. 42

\n` ); + + component.set({ foo: 43 }); + assert.strictEqual( textarea.value, `\n\t

not actually an element. 43

\n` ); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/textarea-children/main.html b/test/runtime/samples/textarea-children/main.html new file mode 100644 index 0000000000..de50c807d6 --- /dev/null +++ b/test/runtime/samples/textarea-children/main.html @@ -0,0 +1,3 @@ + \ No newline at end of file