diff --git a/packages/pug-code-gen/index.js b/packages/pug-code-gen/index.js index 6409d0ad0..5c45c6dbd 100644 --- a/packages/pug-code-gen/index.js +++ b/packages/pug-code-gen/index.js @@ -767,6 +767,16 @@ Compiler.prototype = { this.buf.push(' }\n}).call(this);\n'); }, + visitEachOf: function(each){ + this.buf.push('' + + '// iterate ' + each.obj + '\n' + + 'for (const ' + each.val + ' of ' + each.obj + ') {\n') + + this.visit(each.block, each); + + this.buf.push('}\n'); + }, + /** * Visit `attrs`. * diff --git a/packages/pug-lexer/index.js b/packages/pug-lexer/index.js index 9ad073eac..9f84f2d7c 100644 --- a/packages/pug-lexer/index.js +++ b/packages/pug-lexer/index.js @@ -971,6 +971,39 @@ Lexer.prototype = { } }, + /** + * EachOf. + */ + + eachOf: function() { + var captures; + 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]; + 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)) { + this.error( + 'MALFORMED_EACH', + 'Pug each and for should not be prefixed with a dash ("-"). They are pug keywords and not part of JavaScript.' + ); + } + }, + /** * Code. */ @@ -1485,6 +1518,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..54606315e 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,19 @@ loop: return node; }, + parseEachOf: function(){ + var tok = this.expect('eachOf'); + var node = { + type: 'EachOf', + obj: tok.code, + val: tok.val, + block: this.block(), + line: tok.loc.start.line, + column: tok.loc.start.column, + filename: this.filename + }; + return node; + }, /** * 'extends' name */ diff --git a/packages/pug-walk/index.js b/packages/pug-walk/index.js index 94f1aba88..138aef0b3 100644 --- a/packages/pug-walk/index.js +++ b/packages/pug-walk/index.js @@ -56,6 +56,11 @@ 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); + } + break; case 'Conditional': if (ast.consequent) { ast.consequent = walkAST(ast.consequent, before, after, options);