From 8d115be89dcceead4069468cad171f50957c4fd6 Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Sun, 27 Dec 2015 23:11:09 +0000 Subject: [PATCH 01/10] Add test cases for spread attributes --- test/cases/attrs-spread.expected.json | 11 +++++++++++ test/cases/attrs-spread.pug | 2 ++ test/index.js | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/cases/attrs-spread.expected.json create mode 100644 test/cases/attrs-spread.pug diff --git a/test/cases/attrs-spread.expected.json b/test/cases/attrs-spread.expected.json new file mode 100644 index 0000000..6b16715 --- /dev/null +++ b/test/cases/attrs-spread.expected.json @@ -0,0 +1,11 @@ +{"type":"tag","line":1,"col":1,"val":"div"} +{"type":"start-attributes","line":1,"col":4} +{"type":"attribute","line":1,"col":5,"name":"...attrs","val":true,"mustEscape":true} +{"type":"end-attributes","line":1,"col":13} +{"type":"newline","line":2,"col":1} +{"type":"tag","line":2,"col":1,"val":"div"} +{"type":"start-attributes","line":2,"col":4} +{"type":"attribute","line":2,"col":5,"name":"...attrs","val":"foo","mustEscape":true} +{"type":"end-attributes","line":2,"col":17} +{"type":"newline","line":3,"col":1} +{"type":"eos","line":3,"col":1} diff --git a/test/cases/attrs-spread.pug b/test/cases/attrs-spread.pug new file mode 100644 index 0000000..aab3f76 --- /dev/null +++ b/test/cases/attrs-spread.pug @@ -0,0 +1,2 @@ +div(...attrs) +div(...attrs=foo) diff --git a/test/index.js b/test/index.js index 1ff2ba9..ae7b8fd 100644 --- a/test/index.js +++ b/test/index.js @@ -12,7 +12,7 @@ fs.readdirSync(dir).forEach(function (testCase) { if (/\.pug$/.test(testCase)) { console.dir(testCase); var expected = fs.readFileSync(dir + testCase.replace(/\.pug$/, '.expected.json'), 'utf8') - .split(/\n/).map(JSON.parse); + .trim().split(/\n/).map(JSON.parse); var result = lex(fs.readFileSync(dir + testCase, 'utf8'), dir + testCase); fs.writeFileSync(dir + testCase.replace(/\.pug$/, '.actual.json'), result.map(JSON.stringify).join('\n')); From 21d856ef44ae15ce7b6a0fc7b4aec284fa877930 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Fri, 8 Jan 2016 07:47:34 -0800 Subject: [PATCH 02/10] Implement attribute spread syntax --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index cc8fe11..4515830 100644 --- a/index.js +++ b/index.js @@ -1019,7 +1019,8 @@ Lexer.prototype = { if (!whitespaceRe.test(str[x])) { // if it is a JavaScript punctuator, then assume that it is // a part of the value - return !characterParser.isPunctuator(str[x]) || quoteRe.test(str[x]); + // also make exception for spread syntax + return /^\.\.\./.test(str.slice(x)) || !characterParser.isPunctuator(str[x]) || quoteRe.test(str[x]); } } } From 59f392c661bb1b5adb7b71d6647a9d1db8bd52e4 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Fri, 8 Jan 2016 07:58:35 -0800 Subject: [PATCH 03/10] Add test cases for more spread attributes --- test/cases/attrs-spread.expected.json | 20 +++++++++++++++++++- test/cases/attrs-spread.pug | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/test/cases/attrs-spread.expected.json b/test/cases/attrs-spread.expected.json index 6b16715..827466b 100644 --- a/test/cases/attrs-spread.expected.json +++ b/test/cases/attrs-spread.expected.json @@ -8,4 +8,22 @@ {"type":"attribute","line":2,"col":5,"name":"...attrs","val":"foo","mustEscape":true} {"type":"end-attributes","line":2,"col":17} {"type":"newline","line":3,"col":1} -{"type":"eos","line":3,"col":1} +{"type":"tag","line":3,"col":1,"val":"div"} +{"type":"start-attributes","line":3,"col":4} +{"type":"attribute","line":3,"col":5,"name":"a","val":"'bleh'","mustEscape":true} +{"type":"attribute","line":3,"col":15,"name":"...attrs","val":true,"mustEscape":false} +{"type":"end-attributes","line":3,"col":23} +{"type":"newline","line":4,"col":1} +{"type":"tag","line":4,"col":1,"val":"div"} +{"type":"start-attributes","line":4,"col":4} +{"type":"attribute","line":4,"col":5,"name":"a","val":"'bleh'","mustEscape":true} +{"type":"attribute","line":4,"col":14,"name":"...attrs","val":true,"mustEscape":false} +{"type":"end-attributes","line":4,"col":22} +{"type":"newline","line":5,"col":1} +{"type":"tag","line":5,"col":1,"val":"div"} +{"type":"start-attributes","line":5,"col":4} +{"type":"attribute","line":5,"col":5,"name":"a","val":true,"mustEscape":true} +{"type":"attribute","line":5,"col":7,"name":"...attrs","val":true,"mustEscape":false} +{"type":"end-attributes","line":5,"col":15} +{"type":"newline","line":6,"col":1} +{"type":"eos","line":6,"col":1} \ No newline at end of file diff --git a/test/cases/attrs-spread.pug b/test/cases/attrs-spread.pug index aab3f76..e4b708d 100644 --- a/test/cases/attrs-spread.pug +++ b/test/cases/attrs-spread.pug @@ -1,2 +1,5 @@ div(...attrs) div(...attrs=foo) +div(a='bleh', ...attrs) +div(a='bleh' ...attrs) +div(a ...attrs) From 6f2382d53ee45578e60c8b7029c4e043457dcf95 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 13 Jan 2016 16:37:22 -0800 Subject: [PATCH 04/10] Remove test case for ...attr=val --- test/cases/attrs-spread.expected.json | 23 +++++++++-------------- test/cases/attrs-spread.pug | 1 - 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/test/cases/attrs-spread.expected.json b/test/cases/attrs-spread.expected.json index 827466b..79f89ae 100644 --- a/test/cases/attrs-spread.expected.json +++ b/test/cases/attrs-spread.expected.json @@ -5,25 +5,20 @@ {"type":"newline","line":2,"col":1} {"type":"tag","line":2,"col":1,"val":"div"} {"type":"start-attributes","line":2,"col":4} -{"type":"attribute","line":2,"col":5,"name":"...attrs","val":"foo","mustEscape":true} -{"type":"end-attributes","line":2,"col":17} +{"type":"attribute","line":2,"col":5,"name":"a","val":"'bleh'","mustEscape":true} +{"type":"attribute","line":2,"col":15,"name":"...attrs","val":true,"mustEscape":false} +{"type":"end-attributes","line":2,"col":23} {"type":"newline","line":3,"col":1} {"type":"tag","line":3,"col":1,"val":"div"} {"type":"start-attributes","line":3,"col":4} {"type":"attribute","line":3,"col":5,"name":"a","val":"'bleh'","mustEscape":true} -{"type":"attribute","line":3,"col":15,"name":"...attrs","val":true,"mustEscape":false} -{"type":"end-attributes","line":3,"col":23} +{"type":"attribute","line":3,"col":14,"name":"...attrs","val":true,"mustEscape":false} +{"type":"end-attributes","line":3,"col":22} {"type":"newline","line":4,"col":1} {"type":"tag","line":4,"col":1,"val":"div"} {"type":"start-attributes","line":4,"col":4} -{"type":"attribute","line":4,"col":5,"name":"a","val":"'bleh'","mustEscape":true} -{"type":"attribute","line":4,"col":14,"name":"...attrs","val":true,"mustEscape":false} -{"type":"end-attributes","line":4,"col":22} +{"type":"attribute","line":4,"col":5,"name":"a","val":true,"mustEscape":true} +{"type":"attribute","line":4,"col":7,"name":"...attrs","val":true,"mustEscape":false} +{"type":"end-attributes","line":4,"col":15} {"type":"newline","line":5,"col":1} -{"type":"tag","line":5,"col":1,"val":"div"} -{"type":"start-attributes","line":5,"col":4} -{"type":"attribute","line":5,"col":5,"name":"a","val":true,"mustEscape":true} -{"type":"attribute","line":5,"col":7,"name":"...attrs","val":true,"mustEscape":false} -{"type":"end-attributes","line":5,"col":15} -{"type":"newline","line":6,"col":1} -{"type":"eos","line":6,"col":1} \ No newline at end of file +{"type":"eos","line":5,"col":1} \ No newline at end of file diff --git a/test/cases/attrs-spread.pug b/test/cases/attrs-spread.pug index e4b708d..a20a78a 100644 --- a/test/cases/attrs-spread.pug +++ b/test/cases/attrs-spread.pug @@ -1,5 +1,4 @@ div(...attrs) -div(...attrs=foo) div(a='bleh', ...attrs) div(a='bleh' ...attrs) div(a ...attrs) From e9fe994052b54483a48b22d7b9b8e86731fbd9d0 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 13 Jan 2016 16:38:11 -0800 Subject: [PATCH 05/10] Use spread-attribute as token name for spread attributes and add checks for value being empty --- index.js | 24 +++++++++++++++++++----- test/cases/attrs-spread.expected.json | 8 ++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 4515830..32500e0 100644 --- a/index.js +++ b/index.js @@ -968,11 +968,13 @@ Lexer.prototype = { var quote = ''; var self = this; + var quotedKey = false; this.consume(index + 1); var whitespaceRe = /[ \n\t]/; var quoteRe = /['"]/; + var spreadRe = /^\.\.\./; var escapedAttr = true var key = ''; @@ -1020,7 +1022,7 @@ Lexer.prototype = { // if it is a JavaScript punctuator, then assume that it is // a part of the value // also make exception for spread syntax - return /^\.\.\./.test(str.slice(x)) || !characterParser.isPunctuator(str[x]) || quoteRe.test(str[x]); + return spreadRe.test(str.slice(x)) || !characterParser.isPunctuator(str[x]) || quoteRe.test(str[x]); } } } @@ -1047,16 +1049,27 @@ Lexer.prototype = { key = key.trim(); key = key.replace(/^['"]|['"]$/g, ''); - var tok = this.tok('attribute'); - tok.name = key; - tok.val = '' == val ? true : val; + var tok; + if (spreadRe.test(key) && !quotedKey) { + tok = this.tok('spread-attribute'); + tok.val = key.slice(3); + tok.mustEscape = true; + if (val !== '') { + this.error('INVALID_SPREAD_ATTRIBUTE', 'a spread attribute cannot have a value'); + } + } else { + tok = this.tok('attribute'); + tok.name = key; + tok.val = '' == val ? true : val; + tok.mustEscape = escapedAttr; + } tok.col = colno; - tok.mustEscape = escapedAttr; this.tokens.push(tok); key = val = ''; loc = 'key'; escapedAttr = false; + quotedKey = false; this.lineno = lineno; } else { switch (loc) { @@ -1073,6 +1086,7 @@ Lexer.prototype = { if (key === '' && quoteRe.test(str[i])) { loc = 'key-char'; quote = str[i]; + quotedKey = true; } else if (str[i] === '!' || str[i] === '=') { escapedAttr = str[i] !== '!'; if (str[i] === '!') { diff --git a/test/cases/attrs-spread.expected.json b/test/cases/attrs-spread.expected.json index 79f89ae..628b573 100644 --- a/test/cases/attrs-spread.expected.json +++ b/test/cases/attrs-spread.expected.json @@ -1,24 +1,24 @@ {"type":"tag","line":1,"col":1,"val":"div"} {"type":"start-attributes","line":1,"col":4} -{"type":"attribute","line":1,"col":5,"name":"...attrs","val":true,"mustEscape":true} +{"type":"spread-attribute","line":1,"col":5,"val":"attrs","mustEscape":true} {"type":"end-attributes","line":1,"col":13} {"type":"newline","line":2,"col":1} {"type":"tag","line":2,"col":1,"val":"div"} {"type":"start-attributes","line":2,"col":4} {"type":"attribute","line":2,"col":5,"name":"a","val":"'bleh'","mustEscape":true} -{"type":"attribute","line":2,"col":15,"name":"...attrs","val":true,"mustEscape":false} +{"type":"spread-attribute","line":2,"col":15,"val":"attrs","mustEscape":true} {"type":"end-attributes","line":2,"col":23} {"type":"newline","line":3,"col":1} {"type":"tag","line":3,"col":1,"val":"div"} {"type":"start-attributes","line":3,"col":4} {"type":"attribute","line":3,"col":5,"name":"a","val":"'bleh'","mustEscape":true} -{"type":"attribute","line":3,"col":14,"name":"...attrs","val":true,"mustEscape":false} +{"type":"spread-attribute","line":3,"col":14,"val":"attrs","mustEscape":true} {"type":"end-attributes","line":3,"col":22} {"type":"newline","line":4,"col":1} {"type":"tag","line":4,"col":1,"val":"div"} {"type":"start-attributes","line":4,"col":4} {"type":"attribute","line":4,"col":5,"name":"a","val":true,"mustEscape":true} -{"type":"attribute","line":4,"col":7,"name":"...attrs","val":true,"mustEscape":false} +{"type":"spread-attribute","line":4,"col":7,"val":"attrs","mustEscape":true} {"type":"end-attributes","line":4,"col":15} {"type":"newline","line":5,"col":1} {"type":"eos","line":5,"col":1} \ No newline at end of file From ff810e1ef87a602dd03d7fa2f8b545c051b36b66 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 13 Jan 2016 18:02:48 -0800 Subject: [PATCH 06/10] attrs: Correctly reset escapedAttr --- index.js | 2 +- test/cases/attrs.expected.json | 24 ++++++++++++------------ test/cases/html5.expected.json | 2 +- test/cases/source.expected.json | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 32500e0..30bf99d 100644 --- a/index.js +++ b/index.js @@ -1068,7 +1068,7 @@ Lexer.prototype = { key = val = ''; loc = 'key'; - escapedAttr = false; + escapedAttr = true; quotedKey = false; this.lineno = lineno; } else { diff --git a/test/cases/attrs.expected.json b/test/cases/attrs.expected.json index 32a0ad3..2887cb2 100644 --- a/test/cases/attrs.expected.json +++ b/test/cases/attrs.expected.json @@ -14,8 +14,8 @@ {"type":"tag","line":3,"col":1,"val":"a"} {"type":"start-attributes","line":3,"col":2} {"type":"attribute","line":3,"col":3,"name":"foo","val":true,"mustEscape":true} -{"type":"attribute","line":3,"col":8,"name":"bar","val":true,"mustEscape":false} -{"type":"attribute","line":3,"col":13,"name":"baz","val":true,"mustEscape":false} +{"type":"attribute","line":3,"col":8,"name":"bar","val":true,"mustEscape":true} +{"type":"attribute","line":3,"col":13,"name":"baz","val":true,"mustEscape":true} {"type":"end-attributes","line":3,"col":16} {"type":"newline","line":4,"col":1} {"type":"tag","line":4,"col":1,"val":"a"} @@ -35,7 +35,7 @@ {"type":"tag","line":7,"col":3,"val":"option"} {"type":"start-attributes","line":7,"col":9} {"type":"attribute","line":7,"col":10,"name":"value","val":"'foo'","mustEscape":true} -{"type":"attribute","line":7,"col":23,"name":"selected","val":true,"mustEscape":false} +{"type":"attribute","line":7,"col":23,"name":"selected","val":true,"mustEscape":true} {"type":"end-attributes","line":7,"col":31} {"type":"text","line":7,"col":33,"val":"Foo"} {"type":"newline","line":8,"col":1} @@ -72,8 +72,8 @@ {"type":"tag","line":14,"col":1,"val":"a"} {"type":"start-attributes","line":14,"col":2} {"type":"attribute","line":14,"col":3,"name":"foo","val":true,"mustEscape":true} -{"type":"attribute","line":14,"col":7,"name":"bar","val":true,"mustEscape":false} -{"type":"attribute","line":14,"col":11,"name":"baz","val":true,"mustEscape":false} +{"type":"attribute","line":14,"col":7,"name":"bar","val":true,"mustEscape":true} +{"type":"attribute","line":14,"col":11,"name":"baz","val":true,"mustEscape":true} {"type":"end-attributes","line":14,"col":14} {"type":"newline","line":15,"col":1} {"type":"tag","line":15,"col":1,"val":"a"} @@ -93,7 +93,7 @@ {"type":"tag","line":18,"col":3,"val":"option"} {"type":"start-attributes","line":18,"col":9} {"type":"attribute","line":18,"col":10,"name":"value","val":"'foo'","mustEscape":true} -{"type":"attribute","line":18,"col":22,"name":"selected","val":true,"mustEscape":false} +{"type":"attribute","line":18,"col":22,"name":"selected","val":true,"mustEscape":true} {"type":"end-attributes","line":18,"col":30} {"type":"text","line":18,"col":32,"val":"Foo"} {"type":"newline","line":19,"col":1} @@ -127,37 +127,37 @@ {"type":"tag","line":25,"col":1,"val":"foo"} {"type":"start-attributes","line":25,"col":4} {"type":"attribute","line":25,"col":5,"name":"abc","val":true,"mustEscape":true} -{"type":"attribute","line":26,"col":5,"name":"def","val":true,"mustEscape":false} +{"type":"attribute","line":26,"col":5,"name":"def","val":true,"mustEscape":true} {"type":"end-attributes","line":26,"col":8} {"type":"newline","line":27,"col":1} {"type":"tag","line":27,"col":1,"val":"foo"} {"type":"start-attributes","line":27,"col":4} {"type":"attribute","line":27,"col":5,"name":"abc","val":true,"mustEscape":true} -{"type":"attribute","line":28,"col":5,"name":"def","val":true,"mustEscape":false} +{"type":"attribute","line":28,"col":5,"name":"def","val":true,"mustEscape":true} {"type":"end-attributes","line":28,"col":8} {"type":"newline","line":29,"col":1} {"type":"tag","line":29,"col":1,"val":"foo"} {"type":"start-attributes","line":29,"col":4} {"type":"attribute","line":29,"col":5,"name":"abc","val":true,"mustEscape":true} -{"type":"attribute","line":30,"col":3,"name":"def","val":true,"mustEscape":false} +{"type":"attribute","line":30,"col":3,"name":"def","val":true,"mustEscape":true} {"type":"end-attributes","line":30,"col":6} {"type":"newline","line":31,"col":1} {"type":"tag","line":31,"col":1,"val":"foo"} {"type":"start-attributes","line":31,"col":4} {"type":"attribute","line":31,"col":5,"name":"abc","val":true,"mustEscape":true} -{"type":"attribute","line":32,"col":4,"name":"def","val":true,"mustEscape":false} +{"type":"attribute","line":32,"col":4,"name":"def","val":true,"mustEscape":true} {"type":"end-attributes","line":32,"col":7} {"type":"newline","line":33,"col":1} {"type":"tag","line":33,"col":1,"val":"foo"} {"type":"start-attributes","line":33,"col":4} {"type":"attribute","line":33,"col":5,"name":"abc","val":true,"mustEscape":true} -{"type":"attribute","line":34,"col":3,"name":"def","val":true,"mustEscape":false} +{"type":"attribute","line":34,"col":3,"name":"def","val":true,"mustEscape":true} {"type":"end-attributes","line":34,"col":6} {"type":"newline","line":35,"col":1} {"type":"tag","line":35,"col":1,"val":"foo"} {"type":"start-attributes","line":35,"col":4} {"type":"attribute","line":35,"col":5,"name":"abc","val":true,"mustEscape":true} -{"type":"attribute","line":36,"col":5,"name":"def","val":true,"mustEscape":false} +{"type":"attribute","line":36,"col":5,"name":"def","val":true,"mustEscape":true} {"type":"end-attributes","line":36,"col":8} {"type":"newline","line":38,"col":1} {"type":"code","line":38,"col":1,"val":"var attrs = {foo: 'bar', bar: ''}","mustEscape":false,"buffer":false} diff --git a/test/cases/html5.expected.json b/test/cases/html5.expected.json index 2368d7a..8e28400 100644 --- a/test/cases/html5.expected.json +++ b/test/cases/html5.expected.json @@ -3,7 +3,7 @@ {"type":"tag","line":2,"col":1,"val":"input"} {"type":"start-attributes","line":2,"col":6} {"type":"attribute","line":2,"col":7,"name":"type","val":"'checkbox'","mustEscape":true} -{"type":"attribute","line":2,"col":24,"name":"checked","val":true,"mustEscape":false} +{"type":"attribute","line":2,"col":24,"name":"checked","val":true,"mustEscape":true} {"type":"end-attributes","line":2,"col":31} {"type":"newline","line":3,"col":1} {"type":"tag","line":3,"col":1,"val":"input"} diff --git a/test/cases/source.expected.json b/test/cases/source.expected.json index 05cc23b..7570d22 100644 --- a/test/cases/source.expected.json +++ b/test/cases/source.expected.json @@ -3,8 +3,8 @@ {"type":"tag","line":2,"col":3,"val":"audio"} {"type":"start-attributes","line":2,"col":8} {"type":"attribute","line":2,"col":9,"name":"preload","val":"'auto'","mustEscape":true} -{"type":"attribute","line":2,"col":25,"name":"autobuffer","val":true,"mustEscape":false} -{"type":"attribute","line":2,"col":37,"name":"controls","val":true,"mustEscape":false} +{"type":"attribute","line":2,"col":25,"name":"autobuffer","val":true,"mustEscape":true} +{"type":"attribute","line":2,"col":37,"name":"controls","val":true,"mustEscape":true} {"type":"end-attributes","line":2,"col":45} {"type":"indent","line":3,"col":1,"val":4} {"type":"tag","line":3,"col":5,"val":"source"} From a489f2ba9ae709a99971c48a8f5f771a774226bb Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 13 Jan 2016 18:16:13 -0800 Subject: [PATCH 07/10] attrs: Skip whitespace before scanning for key Fixes column tracking of quoted attribute keys with commas. Fixes `a(attr, 'weird=')`. --- index.js | 65 +++++++++++++++++++--------- test/cases/attrs.expected.json | 4 +- test/cases/mixin.attrs.expected.json | 2 +- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/index.js b/index.js index 30bf99d..fad297e 100644 --- a/index.js +++ b/index.js @@ -983,16 +983,38 @@ Lexer.prototype = { var lineno = startingLine; var colno = this.colno; var loc = 'key'; + var incrementColumn = function (i) { + if (str[i] === '\n') { + // Save the line number locally to keep this.lineno at the start of + // the attribute. + lineno++; + self.colno = 1; + // If the key has not been started, update this.lineno + // immediately. + if (!key) self.lineno = lineno; + } else if (str[i] !== undefined) { + self.incrementColumn(1); + } + }; + var skipWhitespace = function (i) { + if (whitespaceRe.test(str[i])) { + for (; i < str.length; i++) { + if (!whitespaceRe.test(str[i])) break; + incrementColumn(i); + } + } + return i; + }; var isEndOfAttribute = function (i) { // if the key is not started, then the attribute cannot be ended - if (key.trim() === '') { - colno = this.colno; - return false; - } + if (key === '') return false; // if there's nothing more then the attribute must be ended if (i === str.length) return true; - if (loc === 'key') { + // if the spread attribute has just started the attribute cannot be + // ended + if (loc === 'spread') return false; + else if (loc === 'key') { if (whitespaceRe.test(str[i])) { // find the first non-whitespace character for (var x = i; x < str.length; x++) { @@ -1050,19 +1072,18 @@ Lexer.prototype = { key = key.replace(/^['"]|['"]$/g, ''); var tok; - if (spreadRe.test(key) && !quotedKey) { + if (key === '...' && !quotedKey) { tok = this.tok('spread-attribute'); - tok.val = key.slice(3); - tok.mustEscape = true; - if (val !== '') { - this.error('INVALID_SPREAD_ATTRIBUTE', 'a spread attribute cannot have a value'); + tok.val = val; + if (!val) { + return this.error('EMPTY_SPREAD_ATTRIBUTE', 'A spread attribute must have a value.') } } else { tok = this.tok('attribute'); tok.name = key; tok.val = '' == val ? true : val; - tok.mustEscape = escapedAttr; } + tok.mustEscape = escapedAttr; tok.col = colno; this.tokens.push(tok); @@ -1083,6 +1104,10 @@ Lexer.prototype = { } break; case 'key': + if (key === '') { + i = skipWhitespace(i); + colno = this.colno; + } if (key === '' && quoteRe.test(str[i])) { loc = 'key-char'; quote = str[i]; @@ -1099,23 +1124,21 @@ Lexer.prototype = { } else { key += str[i] } + if (key === '...') loc = 'spread'; break; + case 'spread': + if (!whitespaceRe.test(str[i])) { + loc = 'value'; + } + // no cleaner ways to do so, so has to use a + // fallthrough case 'value': state = characterParser.parseChar(str[i], state); val += str[i]; break; } } - if (str[i] === '\n') { - // Save the line number locally to keep this.lineno at the start of - // the attribute. - lineno++; - this.colno = 1; - // If the key has not been started, update this.lineno immediately. - if (!key.trim()) this.lineno = lineno; - } else if (str[i] !== undefined) { - this.incrementColumn(1); - } + incrementColumn(i); } // Reset the line numbers based on the line started on diff --git a/test/cases/attrs.expected.json b/test/cases/attrs.expected.json index 2887cb2..4d5a791 100644 --- a/test/cases/attrs.expected.json +++ b/test/cases/attrs.expected.json @@ -168,13 +168,13 @@ {"type":"tag","line":42,"col":1,"val":"a"} {"type":"start-attributes","line":42,"col":2} {"type":"attribute","line":42,"col":3,"name":"foo","val":"'foo'","mustEscape":true} -{"type":"attribute","line":42,"col":14,"name":"bar","val":"\"bar\"","mustEscape":true} +{"type":"attribute","line":42,"col":13,"name":"bar","val":"\"bar\"","mustEscape":true} {"type":"end-attributes","line":42,"col":24} {"type":"newline","line":43,"col":1} {"type":"tag","line":43,"col":1,"val":"a"} {"type":"start-attributes","line":43,"col":2} {"type":"attribute","line":43,"col":3,"name":"foo","val":"'foo'","mustEscape":true} -{"type":"attribute","line":43,"col":14,"name":"bar","val":"'bar'","mustEscape":true} +{"type":"attribute","line":43,"col":13,"name":"bar","val":"'bar'","mustEscape":true} {"type":"end-attributes","line":43,"col":24} {"type":"newline","line":44,"col":1} {"type":"eos","line":44,"col":1} \ No newline at end of file diff --git a/test/cases/mixin.attrs.expected.json b/test/cases/mixin.attrs.expected.json index 7ba476c..d182b92 100644 --- a/test/cases/mixin.attrs.expected.json +++ b/test/cases/mixin.attrs.expected.json @@ -132,7 +132,7 @@ {"type":"outdent","line":41,"col":1} {"type":"call","line":41,"col":1,"val":"work_filmstrip_item","args":"'work'"} {"type":"start-attributes","line":41,"col":29} -{"type":"attribute","line":41,"col":31,"name":"data-profile","val":"'profile'","mustEscape":true} +{"type":"attribute","line":41,"col":30,"name":"data-profile","val":"'profile'","mustEscape":true} {"type":"attribute","line":41,"col":56,"name":"data-creator-name","val":"'name'","mustEscape":true} {"type":"end-attributes","line":41,"col":82} {"type":"newline","line":43,"col":1} From dd80145a25cffee51276a1353eee0ede9d1254e7 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 13 Jan 2016 18:21:32 -0800 Subject: [PATCH 08/10] Add support for unescaped spread attributes --- index.js | 6 +++++- test/cases/attrs-spread.expected.json | 18 +++++++++++++++++- test/cases/attrs-spread.pug | 5 +++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index fad297e..30fae3e 100644 --- a/index.js +++ b/index.js @@ -1127,7 +1127,11 @@ Lexer.prototype = { if (key === '...') loc = 'spread'; break; case 'spread': - if (!whitespaceRe.test(str[i])) { + if (str[i] === '!') { + escapedAttr = false; + loc = 'value'; + break; + } else if (!whitespaceRe.test(str[i])) { loc = 'value'; } // no cleaner ways to do so, so has to use a diff --git a/test/cases/attrs-spread.expected.json b/test/cases/attrs-spread.expected.json index 628b573..8c13cbd 100644 --- a/test/cases/attrs-spread.expected.json +++ b/test/cases/attrs-spread.expected.json @@ -21,4 +21,20 @@ {"type":"spread-attribute","line":4,"col":7,"val":"attrs","mustEscape":true} {"type":"end-attributes","line":4,"col":15} {"type":"newline","line":5,"col":1} -{"type":"eos","line":5,"col":1} \ No newline at end of file +{"type":"tag","line":5,"col":1,"val":"div"} +{"type":"start-attributes","line":5,"col":4} +{"type":"attribute","line":5,"col":5,"name":"a","val":true,"mustEscape":true} +{"type":"spread-attribute","line":5,"col":7,"val":"attrs","mustEscape":true} +{"type":"end-attributes","line":5,"col":16} +{"type":"newline","line":6,"col":1} +{"type":"tag","line":6,"col":1,"val":"div"} +{"type":"start-attributes","line":6,"col":4} +{"type":"spread-attribute","line":6,"col":5,"val":"attrs","mustEscape":false} +{"type":"end-attributes","line":6,"col":14} +{"type":"newline","line":7,"col":1} +{"type":"tag","line":7,"col":1,"val":"div"} +{"type":"start-attributes","line":7,"col":4} +{"type":"spread-attribute","line":7,"col":5,"val":"attrs","mustEscape":false} +{"type":"end-attributes","line":9,"col":7} +{"type":"newline","line":10,"col":1} +{"type":"eos","line":10,"col":1} \ No newline at end of file diff --git a/test/cases/attrs-spread.pug b/test/cases/attrs-spread.pug index a20a78a..ca5055e 100644 --- a/test/cases/attrs-spread.pug +++ b/test/cases/attrs-spread.pug @@ -2,3 +2,8 @@ div(...attrs) div(a='bleh', ...attrs) div(a='bleh' ...attrs) div(a ...attrs) +div(a ... attrs) +div(...!attrs) +div(... +! + attrs) From 5a54b297b84db32a5667aad01a1efd91eb027b87 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 13 Jan 2016 19:17:18 -0800 Subject: [PATCH 09/10] attrs: Remove redundant `quotedKey` --- index.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 30fae3e..7a2306e 100644 --- a/index.js +++ b/index.js @@ -968,7 +968,6 @@ Lexer.prototype = { var quote = ''; var self = this; - var quotedKey = false; this.consume(index + 1); @@ -1069,16 +1068,16 @@ Lexer.prototype = { this.error('COLON_ATTRIBUTE', '":" is not valid as the start or end of an un-quoted attribute.'); } key = key.trim(); - key = key.replace(/^['"]|['"]$/g, ''); var tok; - if (key === '...' && !quotedKey) { + if (key === '...') { tok = this.tok('spread-attribute'); tok.val = val; if (!val) { return this.error('EMPTY_SPREAD_ATTRIBUTE', 'A spread attribute must have a value.') } } else { + key = key.replace(/^['"]|['"]$/g, ''); tok = this.tok('attribute'); tok.name = key; tok.val = '' == val ? true : val; @@ -1090,7 +1089,6 @@ Lexer.prototype = { key = val = ''; loc = 'key'; escapedAttr = true; - quotedKey = false; this.lineno = lineno; } else { switch (loc) { @@ -1099,9 +1097,8 @@ Lexer.prototype = { loc = 'key'; if (i + 1 < str.length && !/[ ,!=\n\t]/.test(str[i + 1])) this.error('INVALID_KEY_CHARACTER', 'Unexpected character "' + str[i + 1] + '" expected ` `, `\\n`, `\t`, `,`, `!` or `=`'); - } else { - key += str[i]; } + key += str[i]; break; case 'key': if (key === '') { @@ -1111,7 +1108,7 @@ Lexer.prototype = { if (key === '' && quoteRe.test(str[i])) { loc = 'key-char'; quote = str[i]; - quotedKey = true; + key += str[i]; } else if (str[i] === '!' || str[i] === '=') { escapedAttr = str[i] !== '!'; if (str[i] === '!') { From 6d406e44f1935c7aa9ea8bb17795a1c7cacc3ef0 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Wed, 13 Jan 2016 19:18:55 -0800 Subject: [PATCH 10/10] Add test case for "...attr" as attribute --- test/cases/attrs-spread.expected.json | 10 +++++++++- test/cases/attrs-spread.pug | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/cases/attrs-spread.expected.json b/test/cases/attrs-spread.expected.json index 8c13cbd..23bfa65 100644 --- a/test/cases/attrs-spread.expected.json +++ b/test/cases/attrs-spread.expected.json @@ -37,4 +37,12 @@ {"type":"spread-attribute","line":7,"col":5,"val":"attrs","mustEscape":false} {"type":"end-attributes","line":9,"col":7} {"type":"newline","line":10,"col":1} -{"type":"eos","line":10,"col":1} \ No newline at end of file +{"type":"tag","line":10,"col":1,"val":"div"} +{"type":"start-attributes","line":10,"col":4} +{"type":"attribute","line":10,"col":5,"name":"...attrs","val":true,"mustEscape":true} +{"type":"spread-attribute","line":10,"col":16,"val":"attrs","mustEscape":true} +{"type":"attribute","line":10,"col":26,"name":"...attrs","val":"'val'","mustEscape":true} +{"type":"attribute","line":10,"col":44,"name":"...","val":true,"mustEscape":true} +{"type":"end-attributes","line":10,"col":49} +{"type":"newline","line":11,"col":1} +{"type":"eos","line":11,"col":1} \ No newline at end of file diff --git a/test/cases/attrs-spread.pug b/test/cases/attrs-spread.pug index ca5055e..c20c1c2 100644 --- a/test/cases/attrs-spread.pug +++ b/test/cases/attrs-spread.pug @@ -7,3 +7,4 @@ div(...!attrs) div(... ! attrs) +div("...attrs" ...attrs, '...attrs' ='val' "...")