From 9b3d1ece7bdadec03b5afe7802ca682cd4b21e8e Mon Sep 17 00:00:00 2001 From: Max Rumsey Date: Wed, 11 Sep 2019 08:20:14 +0800 Subject: [PATCH 1/5] Adding 'each .. of' syntax --- packages/pug-code-gen/index.js | 46 +++++++++++++++++++ packages/pug-lexer/index.js | 26 +++++++++++ .../test/check-lexer-functions.test.js | 1 + packages/pug-parser/index.js | 20 ++++++++ packages/pug-walk/index.js | 8 ++++ 5 files changed, 101 insertions(+) diff --git a/packages/pug-code-gen/index.js b/packages/pug-code-gen/index.js index 6409d0ad0..70b19fc81 100644 --- a/packages/pug-code-gen/index.js +++ b/packages/pug-code-gen/index.js @@ -767,6 +767,52 @@ Compiler.prototype = { this.buf.push(' }\n}).call(this);\n'); }, + visitEachOf: function(each){ + var indexVarName = each.key || 'pug_index' + this.eachCount; + this.eachCount++; + + this.buf.push('' + + '// iterate ' + each.obj + '\n' + + ';(function(){\n' + + ' var $$obj = ' + each.obj + ';\n' + + ' if (\'number\' == typeof $$obj.length) {'); + + if (each.alternate) { + this.buf.push(' if ($$obj.length) {'); + } + + this.buf.push('' + + ' for (var ' + indexVarName + ' = 0, $$l = $$obj.length; ' + indexVarName + ' < $$l; ' + indexVarName + '++) {\n' + + ' var ' + each.val + ' = $$obj[' + indexVarName + '];'); + + this.visit(each.block, each); + + this.buf.push(' }'); + + if (each.alternate) { + this.buf.push(' } else {'); + this.visit(each.alternate, each); + this.buf.push(' }'); + } + + this.buf.push('' + + ' } else {\n' + + ' var $$l = 0;\n' + + ' for (var ' + indexVarName + ' of $$obj) {\n' + + ' $$l++;\n' + + ' var ' + each.val + ' = ' + indexVarName + ';'); + + this.visit(each.block, each); + + this.buf.push(' }'); + if (each.alternate) { + this.buf.push(' if ($$l === 0) {'); + this.visit(each.alternate, each); + this.buf.push(' }'); + } + this.buf.push(' }\n}).call(this);\n'); + }, + /** * Visit `attrs`. * diff --git a/packages/pug-lexer/index.js b/packages/pug-lexer/index.js index 9ad073eac..cea2bb04e 100644 --- a/packages/pug-lexer/index.js +++ b/packages/pug-lexer/index.js @@ -971,6 +971,31 @@ Lexer.prototype = { } }, + /** + * EachOf. + */ + + eachOf: function() { + var captures; + if (captures = /^(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? * of *([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('eachOf', captures[1]); + tok.key = captures[2] || null; + this.incrementColumn(captures[0].length - captures[3].length); + this.assertExpression(captures[3]) + tok.code = captures[3]; + this.incrementColumn(captures[3].length); + this.tokens.push(this.tokEnd(tok)); + return true; + } + if (captures = /^- *(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? +of +([^\n]+)/.exec(this.input)) { + this.error( + 'MALFORMED_EACH', + 'Pug each and for should no longer be prefixed with a dash ("-"). They are pug keywords and not part of JavaScript.' + ); + } + }, + /** * Code. */ @@ -1485,6 +1510,7 @@ Lexer.prototype = { || this.callLexerFunction('mixin') || this.callLexerFunction('call') || this.callLexerFunction('conditional') + || this.callLexerFunction('eachOf') || this.callLexerFunction('each') || this.callLexerFunction('while') || this.callLexerFunction('tag') diff --git a/packages/pug-lexer/test/check-lexer-functions.test.js b/packages/pug-lexer/test/check-lexer-functions.test.js index b639eae7d..9ff5870e3 100644 --- a/packages/pug-lexer/test/check-lexer-functions.test.js +++ b/packages/pug-lexer/test/check-lexer-functions.test.js @@ -23,6 +23,7 @@ var lexerFunctions = { doctype: true, dot: true, each: true, + eachOf: true, eos: true, endInterpolation: true, extends: true, diff --git a/packages/pug-parser/index.js b/packages/pug-parser/index.js index 9036ca3bf..a04e0ce2a 100644 --- a/packages/pug-parser/index.js +++ b/packages/pug-parser/index.js @@ -234,6 +234,8 @@ Parser.prototype = { return this.parseDot(); case 'each': return this.parseEach(); + case 'eachOf': + return this.parseEachOf(); case 'code': return this.parseCode(); case 'blockcode': @@ -761,6 +763,24 @@ loop: return node; }, + parseEachOf: function(){ + var tok = this.expect('eachOf'); + var node = { + type: 'EachOf', + obj: tok.code, + val: tok.val, + key: tok.key, + block: this.block(), + line: tok.loc.start.line, + column: tok.loc.start.column, + filename: this.filename + }; + if (this.peek().type == 'else') { + this.advance(); + node.alternate = this.block(); + } + return node; + }, /** * 'extends' name */ diff --git a/packages/pug-walk/index.js b/packages/pug-walk/index.js index 94f1aba88..9ae10b70b 100644 --- a/packages/pug-walk/index.js +++ b/packages/pug-walk/index.js @@ -56,6 +56,14 @@ function walkAST(ast, before, after, options) { ast.alternate = walkAST(ast.alternate, before, after, options); } break; + case 'EachOf': + if (ast.block) { + ast.block = walkAST(ast.block, before, after, options); + } + if (ast.alternate) { + ast.alternate = walkAST(ast.alternate, before, after, options); + } + break; case 'Conditional': if (ast.consequent) { ast.consequent = walkAST(ast.consequent, before, after, options); From 418a11a99779637ec8037cc6d6d2434df7950a40 Mon Sep 17 00:00:00 2001 From: Max Rumsey Date: Fri, 13 Sep 2019 08:28:21 +0800 Subject: [PATCH 2/5] Removing 'else' syntax and changing '-' message --- packages/pug-lexer/index.js | 2 +- packages/pug-parser/index.js | 4 ---- packages/pug-walk/index.js | 3 --- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/pug-lexer/index.js b/packages/pug-lexer/index.js index cea2bb04e..af27323b8 100644 --- a/packages/pug-lexer/index.js +++ b/packages/pug-lexer/index.js @@ -991,7 +991,7 @@ Lexer.prototype = { if (captures = /^- *(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? +of +([^\n]+)/.exec(this.input)) { this.error( 'MALFORMED_EACH', - 'Pug each and for should no longer be prefixed with a dash ("-"). They are pug keywords and not part of JavaScript.' + 'Pug each and for should not be prefixed with a dash ("-"). They are pug keywords and not part of JavaScript.' ); } }, diff --git a/packages/pug-parser/index.js b/packages/pug-parser/index.js index a04e0ce2a..62e059bdf 100644 --- a/packages/pug-parser/index.js +++ b/packages/pug-parser/index.js @@ -775,10 +775,6 @@ loop: column: tok.loc.start.column, filename: this.filename }; - if (this.peek().type == 'else') { - this.advance(); - node.alternate = this.block(); - } return node; }, /** diff --git a/packages/pug-walk/index.js b/packages/pug-walk/index.js index 9ae10b70b..138aef0b3 100644 --- a/packages/pug-walk/index.js +++ b/packages/pug-walk/index.js @@ -60,9 +60,6 @@ function walkAST(ast, before, after, options) { if (ast.block) { ast.block = walkAST(ast.block, before, after, options); } - if (ast.alternate) { - ast.alternate = walkAST(ast.alternate, before, after, options); - } break; case 'Conditional': if (ast.consequent) { From c22480f7f7280febefcb32e9bfeb5d940c7d5432 Mon Sep 17 00:00:00 2001 From: Max Rumsey Date: Fri, 13 Sep 2019 09:15:28 +0800 Subject: [PATCH 3/5] Adding support for [key, value] --- packages/pug-code-gen/index.js | 50 ++++++++++++---------------------- packages/pug-lexer/index.js | 2 +- 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/packages/pug-code-gen/index.js b/packages/pug-code-gen/index.js index 70b19fc81..5d535e8dc 100644 --- a/packages/pug-code-gen/index.js +++ b/packages/pug-code-gen/index.js @@ -65,6 +65,7 @@ function Compiler(node, options) { this.mixins = {}; this.dynamicMixins = false; this.eachCount = 0; + this.eachOfCount = 0; if (options.doctype) this.setDoctype(options.doctype); this.runtimeFunctionsUsed = []; this.inlineRuntimeFunctions = options.inlineRuntimeFunctions || false; @@ -768,48 +769,33 @@ Compiler.prototype = { }, visitEachOf: function(each){ - var indexVarName = each.key || 'pug_index' + this.eachCount; - this.eachCount++; + var valVarName = 'pug_val' + this.eachOfCount; + if (each.key) { + var keyVarName = 'pug_key' + this.eachOfCount + } + this.eachOfCount++; this.buf.push('' + '// iterate ' + each.obj + '\n' + ';(function(){\n' + ' var $$obj = ' + each.obj + ';\n' - + ' if (\'number\' == typeof $$obj.length) {'); - - if (each.alternate) { - this.buf.push(' if ($$obj.length) {'); + + ' var $$l = 0;\n') + if (each.key) { + this.buf.push(' for (const [' + keyVarName + ', ' + valVarName + '] of $$obj) {\n'); + } else { + this.buf.push(' for (const ' + valVarName + ' of $$obj) {\n'); } - - this.buf.push('' - + ' for (var ' + indexVarName + ' = 0, $$l = $$obj.length; ' + indexVarName + ' < $$l; ' + indexVarName + '++) {\n' - + ' var ' + each.val + ' = $$obj[' + indexVarName + '];'); - - this.visit(each.block, each); - - this.buf.push(' }'); - - if (each.alternate) { - this.buf.push(' } else {'); - this.visit(each.alternate, each); - this.buf.push(' }'); + this.buf.push(' $$l++;\n'); + if (each.key) { + this.buf.push('' + + ' var ' + each.key + ' = ' + valVarName + ';' + + ' var ' + each.val + ' = ' + keyVarName + ';'); + } else { + this.buf.push(' var ' + each.val + ' = ' + valVarName + ';') } - this.buf.push('' - + ' } else {\n' - + ' var $$l = 0;\n' - + ' for (var ' + indexVarName + ' of $$obj) {\n' - + ' $$l++;\n' - + ' var ' + each.val + ' = ' + indexVarName + ';'); - this.visit(each.block, each); - this.buf.push(' }'); - if (each.alternate) { - this.buf.push(' if ($$l === 0) {'); - this.visit(each.alternate, each); - this.buf.push(' }'); - } this.buf.push(' }\n}).call(this);\n'); }, diff --git a/packages/pug-lexer/index.js b/packages/pug-lexer/index.js index af27323b8..c95b3c9b4 100644 --- a/packages/pug-lexer/index.js +++ b/packages/pug-lexer/index.js @@ -977,7 +977,7 @@ Lexer.prototype = { eachOf: function() { var captures; - if (captures = /^(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? * of *([^\n]+)/.exec(this.input)) { + if (captures = /^(?:each|for) +\[?([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))?\]? * of *([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); var tok = this.tok('eachOf', captures[1]); tok.key = captures[2] || null; From 15878376cd86ecc577e7f4cf8fcba488e1176822 Mon Sep 17 00:00:00 2001 From: Max Rumsey Date: Mon, 16 Sep 2019 10:21:27 +0800 Subject: [PATCH 4/5] Changing way [x, y] is parsed, requested changes --- packages/pug-code-gen/index.js | 25 ++----------------------- packages/pug-lexer/index.js | 12 ++++++------ 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/packages/pug-code-gen/index.js b/packages/pug-code-gen/index.js index 5d535e8dc..c7d5070be 100644 --- a/packages/pug-code-gen/index.js +++ b/packages/pug-code-gen/index.js @@ -769,34 +769,13 @@ Compiler.prototype = { }, visitEachOf: function(each){ - var valVarName = 'pug_val' + this.eachOfCount; - if (each.key) { - var keyVarName = 'pug_key' + this.eachOfCount - } - this.eachOfCount++; - this.buf.push('' + '// iterate ' + each.obj + '\n' - + ';(function(){\n' - + ' var $$obj = ' + each.obj + ';\n' - + ' var $$l = 0;\n') - if (each.key) { - this.buf.push(' for (const [' + keyVarName + ', ' + valVarName + '] of $$obj) {\n'); - } else { - this.buf.push(' for (const ' + valVarName + ' of $$obj) {\n'); - } - this.buf.push(' $$l++;\n'); - if (each.key) { - this.buf.push('' - + ' var ' + each.key + ' = ' + valVarName + ';' - + ' var ' + each.val + ' = ' + keyVarName + ';'); - } else { - this.buf.push(' var ' + each.val + ' = ' + valVarName + ';') - } + + 'for (const ' + each.val + ' of ' + each.obj + ') {\n') this.visit(each.block, each); - this.buf.push(' }\n}).call(this);\n'); + this.buf.push('}\n'); }, /** diff --git a/packages/pug-lexer/index.js b/packages/pug-lexer/index.js index c95b3c9b4..08d3d5426 100644 --- a/packages/pug-lexer/index.js +++ b/packages/pug-lexer/index.js @@ -977,14 +977,14 @@ Lexer.prototype = { eachOf: function() { var captures; - if (captures = /^(?:each|for) +\[?([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))?\]? * of *([^\n]+)/.exec(this.input)) { + if (captures = /^(?:each|for) (.*) * of *([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); var tok = this.tok('eachOf', captures[1]); - tok.key = captures[2] || null; - this.incrementColumn(captures[0].length - captures[3].length); - this.assertExpression(captures[3]) - tok.code = captures[3]; - this.incrementColumn(captures[3].length); + tok.value = captures[1] || null; + this.incrementColumn(captures[0].length - captures[2].length); + this.assertExpression(captures[2]) + tok.code = captures[2]; + this.incrementColumn(captures[2].length); this.tokens.push(this.tokEnd(tok)); return true; } From 4accdda70bafc14425de9b86788dea2a452cbfbd Mon Sep 17 00:00:00 2001 From: Max Rumsey Date: Mon, 16 Sep 2019 20:51:54 +0800 Subject: [PATCH 5/5] Requested Changes --- packages/pug-code-gen/index.js | 1 - packages/pug-lexer/index.js | 12 ++++++++++-- packages/pug-parser/index.js | 1 - 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/pug-code-gen/index.js b/packages/pug-code-gen/index.js index c7d5070be..5c45c6dbd 100644 --- a/packages/pug-code-gen/index.js +++ b/packages/pug-code-gen/index.js @@ -65,7 +65,6 @@ function Compiler(node, options) { this.mixins = {}; this.dynamicMixins = false; this.eachCount = 0; - this.eachOfCount = 0; if (options.doctype) this.setDoctype(options.doctype); this.runtimeFunctionsUsed = []; this.inlineRuntimeFunctions = options.inlineRuntimeFunctions || false; diff --git a/packages/pug-lexer/index.js b/packages/pug-lexer/index.js index 08d3d5426..9f84f2d7c 100644 --- a/packages/pug-lexer/index.js +++ b/packages/pug-lexer/index.js @@ -977,15 +977,23 @@ Lexer.prototype = { eachOf: function() { var captures; - if (captures = /^(?:each|for) (.*) * of *([^\n]+)/.exec(this.input)) { + if (captures = /^(?:each|for) (.*) of *([^\n]+)/.exec(this.input)) { this.consume(captures[0].length); var tok = this.tok('eachOf', captures[1]); - tok.value = captures[1] || null; + tok.value = captures[1]; this.incrementColumn(captures[0].length - captures[2].length); this.assertExpression(captures[2]) tok.code = captures[2]; this.incrementColumn(captures[2].length); this.tokens.push(this.tokEnd(tok)); + + if (!(/^[a-zA-Z_$][\w$]*$/.test(tok.value.trim()) || /^\[ *[a-zA-Z_$][\w$]* *\, *[a-zA-Z_$][\w$]* *\]$/.test(tok.value.trim()))) { + this.error( + 'MALFORMED_EACH_OF_LVAL', + 'The value variable for each must either be a valid identifier (e.g. `item`) or a pair of identifiers in square brackets (e.g. `[key, value]`).' + ); + } + return true; } if (captures = /^- *(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? +of +([^\n]+)/.exec(this.input)) { diff --git a/packages/pug-parser/index.js b/packages/pug-parser/index.js index 62e059bdf..54606315e 100644 --- a/packages/pug-parser/index.js +++ b/packages/pug-parser/index.js @@ -769,7 +769,6 @@ loop: type: 'EachOf', obj: tok.code, val: tok.val, - key: tok.key, block: this.block(), line: tok.loc.start.line, column: tok.loc.start.column,