From d745bd7e32a60734939c01e17aa40a6b89121af6 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 17:29:51 +0200 Subject: [PATCH 01/40] Make test runner more generic. --- test/driver.js | 5 ++--- test/run.js | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/driver.js b/test/driver.js index a08bf6e837..f948fe656a 100644 --- a/test/driver.js +++ b/test/driver.js @@ -1,6 +1,5 @@ (function(exports) { var tests = []; - var acorn = typeof require == "undefined" ? window.acorn : require("../acorn.js"); exports.test = function(code, ast, options, comments) { tests.push({code: code, ast: ast, options: options, comments: comments}); @@ -12,7 +11,7 @@ tests.push({code: code, assert: assert, options: options}); }; - exports.runTests = function(callback) { + exports.runTests = function(parse, callback) { var comments; function onComment(block, text, start, end, startLoc, endLoc) { @@ -33,7 +32,7 @@ try { comments = []; if (test.options && !test.options.onComment) test.options.onComment = onComment; - var ast = acorn.parse(test.code, test.options || opts); + var ast = parse(test.code, test.options || opts); if (test.error) callback("fail", test.code, "Expected error message: " + test.error + "\nBut parsing succeeded."); else if (test.assert) { diff --git a/test/run.js b/test/run.js index 2d44344745..7e84a79464 100644 --- a/test/run.js +++ b/test/run.js @@ -9,7 +9,8 @@ function report(state, code, message) { } var t0 = +new Date; -driver.runTests(report); +var acorn = typeof require == "undefined" ? window.acorn : require("../acorn.js"); +driver.runTests(acorn.parse, report); console.log(testsRun + " tests run in " + (+new Date - t0) + "ms"); if (failed) { From 074db16fb7ae737b5dc2757d9e7f26ef296d9e32 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 17:35:49 +0200 Subject: [PATCH 02/40] Indentation fix. --- test/driver.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/driver.js b/test/driver.js index f948fe656a..539fc00308 100644 --- a/test/driver.js +++ b/test/driver.js @@ -15,14 +15,14 @@ var comments; function onComment(block, text, start, end, startLoc, endLoc) { - comments.push({ - block: block, - text: text, - start: start, - end: end, - startLoc: { line: startLoc.line, column: startLoc.column }, - endLoc: { line: endLoc.line, column: endLoc.column } - }); + comments.push({ + block: block, + text: text, + start: start, + end: end, + startLoc: { line: startLoc.line, column: startLoc.column }, + endLoc: { line: endLoc.line, column: endLoc.column } + }); } var opts = {locations: true, onComment: onComment}; @@ -32,7 +32,7 @@ try { comments = []; if (test.options && !test.options.onComment) test.options.onComment = onComment; - var ast = parse(test.code, test.options || opts); + var ast = acorn.parse(test.code, test.options || opts); if (test.error) callback("fail", test.code, "Expected error message: " + test.error + "\nBut parsing succeeded."); else if (test.assert) { From 86f8c56d2be35fc9753d6328a6c56294099a0c42 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 17:39:58 +0200 Subject: [PATCH 03/40] Added .editorconfig. --- .editorconfig | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..0020fc03ac --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*] +indent_style = space +indent_size = 2 From d424874cf89a71db3b8efcd5bbb9ebb96b027ed9 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 17:44:39 +0200 Subject: [PATCH 04/40] Editorconfig: enforce Unix line endings and extra new line in the end of file. --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index 0020fc03ac..c14d5c67b4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,3 +3,5 @@ root = true [*] indent_style = space indent_size = 2 +end_of_line = lf +insert_final_newline = true From 7db211d56a6f2957941e94976a18bd6c19f8e36d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 17:59:10 +0200 Subject: [PATCH 05/40] Added loose parser support to test runner (currently failing for 208/1680). --- test/driver.js | 14 ++++++++++---- test/run.js | 10 +++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/test/driver.js b/test/driver.js index 539fc00308..8b812bfdca 100644 --- a/test/driver.js +++ b/test/driver.js @@ -11,7 +11,8 @@ tests.push({code: code, assert: assert, options: options}); }; - exports.runTests = function(parse, callback) { + exports.runTests = function(config) { + var parse = config.parse, callback = config.callback; var comments; function onComment(block, text, start, end, startLoc, endLoc) { @@ -32,9 +33,14 @@ try { comments = []; if (test.options && !test.options.onComment) test.options.onComment = onComment; - var ast = acorn.parse(test.code, test.options || opts); - if (test.error) callback("fail", test.code, - "Expected error message: " + test.error + "\nBut parsing succeeded."); + var ast = parse(test.code, test.options || opts); + if (test.error) { + if (config.loose) { + callback("ok", test.code); + } else { + callback("fail", test.code, "Expected error message: " + test.error + "\nBut parsing succeeded."); + } + } else if (test.assert) { var error = test.assert(ast); if (error) callback("fail", test.code, diff --git a/test/run.js b/test/run.js index 7e84a79464..2fa2c8dbff 100644 --- a/test/run.js +++ b/test/run.js @@ -9,8 +9,12 @@ function report(state, code, message) { } var t0 = +new Date; -var acorn = typeof require == "undefined" ? window.acorn : require("../acorn.js"); -driver.runTests(acorn.parse, report); + +var parse = (typeof require === "undefined" ? window.acorn : require("../acorn.js")).parse; +var parse_dammit = (typeof require === "undefined") ? window.acorn_loose : require("../acorn_loose").parse_dammit; + +driver.runTests({parse: parse, callback: report}); +driver.runTests({parse: parse_dammit, loose: true, callback: report}); console.log(testsRun + " tests run in " + (+new Date - t0) + "ms"); if (failed) { @@ -20,4 +24,4 @@ if (failed) { }); } else { console.log("All passed."); -} \ No newline at end of file +} From 61d2067b2bf64862b09c7634057a2017fa352614 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 18:08:43 +0200 Subject: [PATCH 06/40] Loose: Added ParenthesizedExpression. --- acorn_loose.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/acorn_loose.js b/acorn_loose.js index 09fbf210fe..6829f2f606 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -664,9 +664,15 @@ return finishNode(node, "Literal"); case tt.parenL: + var start = storeCurrentPos(); next(); var val = parseExpression(); expect(tt.parenR); + if (options.preserveParens) { + var par = startNodeAt(start); + par.expression = val; + val = finishNode(par, "ParenthesizedExpression"); + } return val; case tt.bracketL: From c26fd33826de858c1edfbea66db31984002664e0 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 19:20:00 +0200 Subject: [PATCH 07/40] Clone test options object since calling `parse` is destructive for it. --- test/driver.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/driver.js b/test/driver.js index 8b812bfdca..5c71b1eeff 100644 --- a/test/driver.js +++ b/test/driver.js @@ -32,8 +32,9 @@ var test = tests[i]; try { comments = []; - if (test.options && !test.options.onComment) test.options.onComment = onComment; - var ast = parse(test.code, test.options || opts); + var testOpts = JSON.parse(JSON.stringify(test.options || opts)); + if (!testOpts.onComment) testOpts.onComment = onComment; + var ast = parse(test.code, testOpts); if (test.error) { if (config.loose) { callback("ok", test.code); From 11ecb20e9e9e5862b688bbc61f50974328aefb1d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 19:27:00 +0200 Subject: [PATCH 08/40] Loose: ES6 function params support. --- acorn.js | 2 +- acorn_loose.js | 82 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/acorn.js b/acorn.js index d94aee2fc3..14648d36a1 100644 --- a/acorn.js +++ b/acorn.js @@ -444,7 +444,7 @@ parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string, - arrow: _arrow, bquote: _bquote, dollarBraceL: _dollarBraceL}; + arrow: _arrow, bquote: _bquote, dollarBraceL: _dollarBraceL, star: _star}; for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on diff --git a/acorn_loose.js b/acorn_loose.js index 6829f2f606..39615aac63 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -256,6 +256,8 @@ if (token.type === type) { next(); return true; + } else { + return false; } } @@ -759,19 +761,79 @@ return finishNode(node, "Identifier"); } + function initFunction(node) { + node.id = null; + node.params = []; + if (options.ecmaVersion >= 6) { + node.defaults = []; + node.rest = null; + node.generator = false; + node.expression = false; + } + } + + // Convert existing expression atom to assignable pattern + // if possible. + + function toAssignable(node) { + if (options.ecmaVersion >= 6 && node) { + switch (node.type) { + case "ObjectExpression": + node.type = "ObjectPattern"; + var props = node.properties; + for (var i = 0; i < props.length; i++) { + toAssignable(props[i].value); + } + break; + + case "ArrayExpression": + node.type = "ArrayPattern"; + var elms = node.elements; + for (var i = 0; i < elms.length; i++) { + toAssignable(elms[i]); + } + break; + + case "SpreadElement": + toAssignable(node.argument); + break; + } + } + return node; + } + + function parseFunctionParams(node) { + var defaults = [], hasDefaults = false; + + pushCx(); + var params = parseExprList(tt.parenR); + for (var i = 0; i < params.length; i++) { + var param = toAssignable(params[i]), defValue = null; + if (param.type === "SpreadElement") { + if (i === params.length - 1) { + params.length--; + node.rest = param.argument; + } + } else if (param.type === "AssignmentExpression") { + defValue = param.right; + param = param.left; + } + node.params.push(param); + defaults.push(defValue); + if (defValue) hasDefaults = true; + } + + if (hasDefaults) node.defaults = defaults; + } + function parseFunction(node, isStatement) { + initFunction(node); + if (options.ecmaVersion >= 6) { + node.generator = eat(tt.star); + } if (token.type === tt.name) node.id = parseIdent(); else if (isStatement) node.id = dummyIdent(); - else node.id = null; - node.params = []; - pushCx(); - expect(tt.parenL); - while (token.type == tt.name) { - node.params.push(parseIdent()); - eat(tt.comma); - } - popCx(); - eat(tt.parenR); + parseFunctionParams(node); node.body = parseBlock(); return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); } From b7367a2a8c12a3122adb42cac98960140247772e Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 20:21:39 +0200 Subject: [PATCH 09/40] Make setOptions non-destructive for original object. --- acorn.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acorn.js b/acorn.js index 14648d36a1..e6658d8c64 100644 --- a/acorn.js +++ b/acorn.js @@ -135,9 +135,9 @@ }; function setOptions(opts) { - options = opts || {}; - for (var opt in defaultOptions) if (!has(options, opt)) - options[opt] = defaultOptions[opt]; + options = {}; + for (var opt in defaultOptions) + options[opt] = has(opts, opt) ? opts[opt] : defaultOptions[opt]; sourceFile = options.sourceFile || null; if (isArray(options.onToken)) { var tokens = options.onToken; From 0abe4b64a804ca3fd78f80c9567948e83c37d04d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 20:23:45 +0200 Subject: [PATCH 10/40] Collect test stats separately for each mode. --- test/driver.js | 11 ++++++----- test/run.js | 50 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/test/driver.js b/test/driver.js index 5c71b1eeff..f6d35a1d6c 100644 --- a/test/driver.js +++ b/test/driver.js @@ -11,9 +11,8 @@ tests.push({code: code, assert: assert, options: options}); }; - exports.runTests = function(config) { - var parse = config.parse, callback = config.callback; - var comments; + exports.runTests = function(config, callback) { + var parse = config.parse, comments; function onComment(block, text, start, end, startLoc, endLoc) { comments.push({ @@ -32,9 +31,11 @@ var test = tests[i]; try { comments = []; - var testOpts = JSON.parse(JSON.stringify(test.options || opts)); - if (!testOpts.onComment) testOpts.onComment = onComment; + var testOpts = test.options || opts; + var oldOnComment = testOpts.onComment; + if (!oldOnComment) testOpts.onComment = onComment; var ast = parse(test.code, testOpts); + testOpts.onComment = oldOnComment; if (test.error) { if (config.loose) { callback("ok", test.code); diff --git a/test/run.js b/test/run.js index 2fa2c8dbff..fcd40680da 100644 --- a/test/run.js +++ b/test/run.js @@ -2,26 +2,50 @@ var driver = require("./driver.js"); require("./tests.js"); require("./tests-harmony.js"); -var testsRun = 0, failed = 0; +var stats, modes = { + Normal: { + config: { + parse: (typeof require === "undefined" ? window.acorn : require("../acorn.js")).parse + } + }, + Loose: { + config: { + parse: (typeof require === "undefined") ? window.acorn_loose : require("../acorn_loose").parse_dammit, + loose: true + } + } +}; + function report(state, code, message) { - if (state != "ok") {++failed; console.log(code, message);} - ++testsRun; + if (state != "ok") {++stats.failed; console.log(code, message);} + ++stats.testsRun; } -var t0 = +new Date; +for (var name in modes) { + var mode = modes[name]; + stats = mode.stats = {testsRun: 0, failed: 0}; + var t0 = +new Date; + driver.runTests(mode.config, report); + mode.stats.duration = +new Date - t0; +} -var parse = (typeof require === "undefined" ? window.acorn : require("../acorn.js")).parse; -var parse_dammit = (typeof require === "undefined") ? window.acorn_loose : require("../acorn_loose").parse_dammit; +function outputStats(name, stats) { + console.log(name + ": " + stats.testsRun + " tests run in " + stats.duration + "ms; " + + (stats.failed ? stats.failed + " failures." : "all passed.")); +} -driver.runTests({parse: parse, callback: report}); -driver.runTests({parse: parse_dammit, loose: true, callback: report}); -console.log(testsRun + " tests run in " + (+new Date - t0) + "ms"); +var total = {testsRun: 0, failed: 0, duration: 0}; -if (failed) { - console.log(failed + " failures."); +for (var name in modes) { + var stats = modes[name].stats; + outputStats(name + " parser", stats); + for (var key in stats) total[key] += stats[key]; +} + +outputStats("Total", total); + +if (total.failed) { process.stdout.write("", function() { process.exit(1); }); -} else { - console.log("All passed."); } From 6d6483435235e3c73cf507d7f66f1a454b6c71d8 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 20:32:49 +0200 Subject: [PATCH 11/40] Removed manual `onComment` test in favor of new argument. --- test/driver.js | 4 +-- test/tests.js | 76 +++++++++++++++++++------------------------------- 2 files changed, 29 insertions(+), 51 deletions(-) diff --git a/test/driver.js b/test/driver.js index f6d35a1d6c..d56f3b551b 100644 --- a/test/driver.js +++ b/test/driver.js @@ -32,10 +32,8 @@ try { comments = []; var testOpts = test.options || opts; - var oldOnComment = testOpts.onComment; - if (!oldOnComment) testOpts.onComment = onComment; + testOpts.onComment = onComment; var ast = parse(test.code, testOpts); - testOpts.onComment = oldOnComment; if (test.error) { if (config.loose) { callback("ok", test.code); diff --git a/test/tests.js b/test/tests.js index 287d98f84d..8d17a891a4 100644 --- a/test/tests.js +++ b/test/tests.js @@ -28666,55 +28666,35 @@ testFail("for(x of a);", "Unexpected token (1:6)"); testFail("for(var x of a);", "Unexpected token (1:10)"); // Assertion Tests -(function() { - var actualComments = [], - expectedComments = [ - " Bear class", - " Whatever", - [" 1", - " 2", - " 3" - ].join('\n'), - "stuff" - ]; - testAssert( - function TestComments() { - // Bear class - function Bear(x,y,z) { - this.position = [x||0,y||0,z||0] - } - - Bear.prototype.roar = function(message) { - return 'RAWWW: ' + message; // Whatever - }; - - function Cat() { - /* 1 - 2 - 3*/ - } - - Cat.prototype.roar = function(message) { - return 'MEOOWW: ' + /*stuff*/ message; - }; - }.toString().replace(/\r\n/g, '\n'), - function assert(ast) { - if (actualComments.length !== expectedComments.length) { - return JSON.stringify(actualComments) + " !== " + JSON.stringify(expectedComments); - } else { - for (var i=0, n=actualComments.length; i < n; i++) { - if (actualComments[i] !== expectedComments[i]) - return JSON.stringify(actualComments[i]) + ' !== ' + JSON.stringify(expectedComments[i]); - } - } - }, - { - onComment: function(isMultiline, text) { - actualComments.push(text); - } +test(function TestComments() { + // Bear class + function Bear(x,y,z) { + this.position = [x||0,y||0,z||0] } - ); -})(); + + Bear.prototype.roar = function(message) { + return 'RAWWW: ' + message; // Whatever + }; + + function Cat() { + /* 1 + 2 + 3*/ + } + + Cat.prototype.roar = function(message) { + return 'MEOOWW: ' + /*stuff*/ message; + }; +}.toString().replace(/\r\n/g, '\n'), {}, {}, [ + {block: false, text: " Bear class"}, + {block: false, text: " Whatever"}, + {block: true, text: [ + " 1", + " 2", + " 3" + ].join('\n')}, + {block: true, text: "stuff"} +]); test(" HTML comment", {}, { + locations: true, + onComment: [{ + type: "Line", + value: " HTML comment", + loc: { + start: { line: 2, column: 0 }, + end: { line: 2, column: 16 } + } + }] +}); - test(";\n--> HTML comment", {}, {locations: true}, - [{ - block: false, - text: " HTML comment", - startLoc: { line: 2, column: 0 }, - endLoc: { line: 2, column: 16 } - }]); -})(); +var tokTypes = acorn.tokTypes; -(function() { - var tokTypes = acorn.tokTypes; - - var actualTokens = [], - expectedTokens = [ - { - type: tokTypes._var, - value: "var", - loc: { - start: {line: 1, column: 0}, - end: {line: 1, column: 3} - } - }, - { - type: tokTypes.name, - value: "x", - loc: { - start: {line: 1, column: 4}, - end: {line: 1, column: 5} - } - }, - { - type: tokTypes.eq, - value: "=", - loc: { - start: {line: 1, column: 6}, - end: {line: 1, column: 7} - } - }, - { - type: tokTypes.parenL, - value: undefined, - loc: { - start: {line: 1, column: 8}, - end: {line: 1, column: 9} - } - }, - { - type: tokTypes.num, - value: 1, - loc: { - start: {line: 1, column: 9}, - end: {line: 1, column: 10} - } - }, - { - type: {binop: 9, prefix: true, beforeExpr: true}, - value: "+", - loc: { - start: {line: 1, column: 11}, - end: {line: 1, column: 12} - } - }, - { - type: tokTypes.num, - value: 2, - loc: { - start: {line: 1, column: 13}, - end: {line: 1, column: 14} - } - }, - { - type: tokTypes.parenR, - value: undefined, - loc: { - start: {line: 1, column: 14}, - end: {line: 1, column: 15} - } - }, - { - type: tokTypes.eof, - value: undefined, - loc: { - start: {line: 1, column: 15}, - end: {line: 1, column: 15} - } - } - ]; - testAssert('var x = (1 + 2)', function assert(ast) { - if (actualTokens.length !== expectedTokens.length) { - return "Bad token stream length: expected " + expectedTokens.length + ", got " + actualTokens.length; - } else { - for (var i=0, n=actualTokens.length; i < n; i++) { - var mis = misMatch(expectedTokens[i], actualTokens[i]); - if (mis) return mis; +test('var x = (1 + 2)', {}, { + locations: true, + onToken: [ + { + type: tokTypes._var, + value: "var", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 3} + } + }, + { + type: tokTypes.name, + value: "x", + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 5} + } + }, + { + type: tokTypes.eq, + value: "=", + loc: { + start: {line: 1, column: 6}, + end: {line: 1, column: 7} + } + }, + { + type: tokTypes.parenL, + value: undefined, + loc: { + start: {line: 1, column: 8}, + end: {line: 1, column: 9} + } + }, + { + type: tokTypes.num, + value: 1, + loc: { + start: {line: 1, column: 9}, + end: {line: 1, column: 10} + } + }, + { + type: {binop: 9, prefix: true, beforeExpr: true}, + value: "+", + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 12} + } + }, + { + type: tokTypes.num, + value: 2, + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 14} + } + }, + { + type: tokTypes.parenR, + value: undefined, + loc: { + start: {line: 1, column: 14}, + end: {line: 1, column: 15} + } + }, + { + type: tokTypes.eof, + value: undefined, + loc: { + start: {line: 1, column: 15}, + end: {line: 1, column: 15} } } - }, { - locations: true, - onToken: actualTokens - }); -})(); + ] +}); test("function f(f) { 'use strict'; }", {}); From 72df78cc883a565cfca5118cefa89b7ab576d86d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 20:58:05 +0200 Subject: [PATCH 13/40] Avoid separate handling of SpreadElement in favor of UnaryExpression parser. --- acorn.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/acorn.js b/acorn.js index e6658d8c64..e6a6e64c0e 100644 --- a/acorn.js +++ b/acorn.js @@ -401,8 +401,9 @@ var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; - var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _ellipsis = {type: "..."}, _question = {type: "?", beforeExpr: true}; + var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; var _arrow = {type: "=>", beforeExpr: true}, _bquote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; + var _ellipsis = {type: "...", prefix: true, beforeExpr: true}; // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is @@ -1949,6 +1950,8 @@ function parseMaybeUnary() { if (tokType.prefix) { var node = startNode(), update = tokType.isUpdate; + var nodeType = tokType === _ellipsis ? "SpreadElement" : + (update ? "UpdateExpression" : "UnaryExpression"); node.operator = tokVal; node.prefix = true; tokRegexpAllowed = true; @@ -1958,7 +1961,7 @@ else if (strict && node.operator === "delete" && node.argument.type === "Identifier") raise(node.start, "Deleting local variable in strict mode"); - return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + return finishNode(node, nodeType); } var start = storeCurrentPos(); var expr = parseExprSubscripts(); @@ -2115,9 +2118,6 @@ case _new: return parseNew(); - case _ellipsis: - return parseSpread(); - case _bquote: return parseTemplate(); @@ -2140,15 +2140,6 @@ return finishNode(node, "NewExpression"); } - // Parse spread element '...expr' - - function parseSpread() { - var node = startNode(); - next(); - node.argument = parseExpression(true); - return finishNode(node, "SpreadElement"); - } - // Parse template expression. function parseTemplate() { From b47696eecfa455d0c22b3161c1866106329236a3 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 21:03:35 +0200 Subject: [PATCH 14/40] Avoid UnaryExpression-specific properties in SpreadElement (just in case). --- acorn.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/acorn.js b/acorn.js index e6a6e64c0e..adf467542e 100644 --- a/acorn.js +++ b/acorn.js @@ -1949,11 +1949,14 @@ function parseMaybeUnary() { if (tokType.prefix) { - var node = startNode(), update = tokType.isUpdate; - var nodeType = tokType === _ellipsis ? "SpreadElement" : - (update ? "UpdateExpression" : "UnaryExpression"); - node.operator = tokVal; - node.prefix = true; + var node = startNode(), update = tokType.isUpdate, nodeType; + if (tokType === _ellipsis) { + nodeType = "SpreadElement"; + } else { + nodeType = update ? "UpdateExpression" : "UnaryExpression"; + node.operator = tokVal; + node.prefix = true; + } tokRegexpAllowed = true; next(); node.argument = parseMaybeUnary(); From a14a5c81926a2e0fd7ed8c1a754157bda1679a7a Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 21:29:30 +0200 Subject: [PATCH 15/40] Loose: Added support for rest parameters. Includes correction of mistype ".." vs "...". --- acorn_loose.js | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 39615aac63..b0b73b00df 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -81,7 +81,13 @@ function readToken() { for (;;) { try { - return fetchToken(); + var tok = fetchToken(); + if (tok.type === tt.dot && input.substr(tok.end, 1) === '.') { + tok = fetchToken(); + tok.start--; + tok.type = tt.ellipsis; + } + return tok; } catch(e) { if (!(e instanceof SyntaxError)) throw e; @@ -281,8 +287,17 @@ } function checkLVal(expr) { - if (expr.type === "Identifier" || expr.type === "MemberExpression") return expr; - return dummyIdent(); + switch (expr.type) { + case "Identifier": + case "MemberExpression": + case "ObjectPattern": + case "ArrayPattern": + case "SpreadElement": + return expr; + + default: + return dummyIdent(); + } } function parseTopLevel() { @@ -577,13 +592,20 @@ function parseMaybeUnary(noIn) { if (token.type.prefix) { - var node = startNode(), update = token.type.isUpdate; + var node = startNode(), update = token.type.isUpdate, nodeType; + if (token.type === tt.ellipsis) { + nodeType = "SpreadElement"; + } else { + nodeType = update ? "UpdateExpression" : "UnaryExpression"; + node.operator = token.value; + node.prefix = true; + } node.operator = token.value; node.prefix = true; next(); node.argument = parseMaybeUnary(noIn); if (update) node.argument = checkLVal(node.argument); - return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + return finishNode(node, nodeType); } var start = storeCurrentPos(); var expr = parseExprSubscripts(); @@ -810,15 +832,16 @@ for (var i = 0; i < params.length; i++) { var param = toAssignable(params[i]), defValue = null; if (param.type === "SpreadElement") { + param = param.argument; if (i === params.length - 1) { - params.length--; - node.rest = param.argument; + node.rest = param; + continue; } } else if (param.type === "AssignmentExpression") { defValue = param.right; param = param.left; } - node.params.push(param); + node.params.push(checkLVal(param)); defaults.push(defValue); if (defValue) hasDefaults = true; } From eba8a5646cde0bdd3f2d7a44c946f977cbe834f7 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 23:14:06 +0200 Subject: [PATCH 16/40] Loose: added support for holes in arrays (but disallows trailing comma). --- acorn_loose.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index b0b73b00df..87cdbd552c 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -702,7 +702,7 @@ case tt.bracketL: var node = startNode(); pushCx(); - node.elements = parseExprList(tt.bracketR); + node.elements = parseExprList(tt.bracketR, true); return finishNode(node, "ArrayExpression"); case tt.braceL: @@ -861,11 +861,15 @@ return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); } - function parseExprList(close) { + function parseExprList(close, allowEmpty) { var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart; next(); // Opening bracket if (curLineStart > continuedLine) continuedLine = curLineStart; while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) { + if (allowEmpty && eat(tt.comma)) { + elts.push(null); + continue; + } var elt = parseExpression(true); if (isDummy(elt)) { if (closes(close, indent, line)) break; @@ -873,7 +877,7 @@ } else { elts.push(elt); } - while (eat(tt.comma)) {} + eat(tt.comma); } popCx(); eat(close); From c5145cedb2c46fa17592305e4b5b14de9774a887 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 23:14:26 +0200 Subject: [PATCH 17/40] Fix no-options case for acorn. --- acorn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acorn.js b/acorn.js index adf467542e..ea4121ec88 100644 --- a/acorn.js +++ b/acorn.js @@ -137,7 +137,7 @@ function setOptions(opts) { options = {}; for (var opt in defaultOptions) - options[opt] = has(opts, opt) ? opts[opt] : defaultOptions[opt]; + options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt]; sourceFile = options.sourceFile || null; if (isArray(options.onToken)) { var tokens = options.onToken; From 4879af22d1e88f6b1b0ffd17bbff23a17038ff92 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 23:32:35 +0200 Subject: [PATCH 18/40] Loose: Added support for assignment patterns to expression and variables. --- acorn_loose.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 87cdbd552c..f3523b8829 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -498,13 +498,12 @@ function parseVar(node, noIn) { node.declarations = []; node.kind = "var"; - while (token.type === tt.name) { + do { var decl = startNode(); - decl.id = parseIdent(); + decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent(); decl.init = eat(tt.eq) ? parseExpression(true, noIn) : null; node.declarations.push(finishNode(decl, "VariableDeclarator")); - if (!eat(tt.comma)) break; - } + } while (eat(tt.comma)); if (!node.declarations.length) { var decl = startNode(); decl.id = dummyIdent(); @@ -541,7 +540,7 @@ if (token.type.isAssign) { var node = startNodeAt(start); node.operator = token.value; - node.left = checkLVal(left); + node.left = token.type === tt.eq ? toAssignable(left) : checkLVal(left); next(); node.right = parseMaybeAssign(noIn); return finishNode(node, "AssignmentExpression"); @@ -821,7 +820,7 @@ break; } } - return node; + return checkLVal(node); } function parseFunctionParams(node) { From 963a26e46f158b83de3c551743ebdff574a10281 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 26 Oct 2014 23:44:19 +0200 Subject: [PATCH 19/40] Loose: Added support for let and const. Fixes #146. --- acorn_loose.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index f3523b8829..4159309211 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -335,10 +335,8 @@ pushCx(); expect(tt.parenL); if (token.type === tt.semi) return parseFor(node, null); - if (token.type === tt._var) { - var init = startNode(); - next(); - parseVar(init, true); + if (token.type === tt._var || token.type === tt._let) { + var init = parseVar(true); if (init.declarations.length === 1 && eat(tt._in)) return parseForIn(node, init); return parseFor(node, init); @@ -421,9 +419,9 @@ return finishNode(node, "TryStatement"); case tt._var: - next(); - node = parseVar(node); - return node; + case tt._let: + case tt._const: + return parseVar(); case tt._while: next(); @@ -495,9 +493,11 @@ return finishNode(node, "ForInStatement"); } - function parseVar(node, noIn) { + function parseVar(noIn) { + var node = startNode(); + node.kind = token.type.keyword; + next(); node.declarations = []; - node.kind = "var"; do { var decl = startNode(); decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent(); @@ -803,7 +803,7 @@ node.type = "ObjectPattern"; var props = node.properties; for (var i = 0; i < props.length; i++) { - toAssignable(props[i].value); + props[i].value = toAssignable(props[i].value); } break; @@ -811,12 +811,12 @@ node.type = "ArrayPattern"; var elms = node.elements; for (var i = 0; i < elms.length; i++) { - toAssignable(elms[i]); + elms[i] = toAssignable(elms[i]); } break; case "SpreadElement": - toAssignable(node.argument); + node.argument = toAssignable(node.argument); break; } } From dda90580df211f3d8275d0c28d37d26253ef5e88 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 00:07:40 +0200 Subject: [PATCH 20/40] Loose: Added support for shorthand properties. --- acorn_loose.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 4159309211..9bc5320465 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -287,6 +287,7 @@ } function checkLVal(expr) { + if (!expr) return expr; switch (expr.type) { case "Identifier": case "MemberExpression": @@ -742,10 +743,14 @@ next(); if (curIndent + 1 < indent) { indent = curIndent; line = curLineStart; } while (!closes(tt.braceR, indent, line)) { - var name = parsePropertyName(); - if (!name) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } - var prop = startNode(); - prop.key = name; + var prop = startNode(), isGenerator; + if (options.ecmaVersion >= 6) { + prop.method = false; + prop.shorthand = false; + isGenerator = eat(tt.star); + } + parsePropertyName(prop); + if (!prop.key) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } if (eat(tt.colon)) { prop.value = parseExpression(true); prop.kind = "init"; @@ -755,7 +760,9 @@ prop.key = parsePropertyName() || dummyIdent(); prop.value = parseFunction(startNode(), false); } else { - prop.value = dummyIdent(); + prop.value = options.ecmaVersion >= 6 ? prop.key : dummyIdent(); + prop.kind = "init"; + prop.shorthand = true; } node.properties.push(finishNode(prop, "Property")); @@ -766,9 +773,18 @@ return finishNode(node, "ObjectExpression"); } - function parsePropertyName() { - if (token.type === tt.num || token.type === tt.string) return parseExprAtom(); - if (token.type === tt.name || token.type.keyword) return parseIdent(); + function parsePropertyName(prop) { + if (options.ecmaVersion >= 6) { + if (eat(tt.bracketL)) { + prop.computed = true; + prop.key = parseExpression(); + expect(tt.bracketR); + return; + } else { + prop.computed = false; + } + } + prop.key = (token.type === tt.num || token.type === tt.string) ? parseExprAtom() : parseIdent(); } function parsePropertyAccessor() { From 143066184cd2781b2f6769ccf6b56da46ae6d487 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 00:39:45 +0200 Subject: [PATCH 21/40] Emit full stack trace for unknown exceptions. --- test/driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index 5444cc5331..ea5dc421c3 100644 --- a/test/driver.js +++ b/test/driver.js @@ -56,7 +56,7 @@ else callback("fail", test.code, "Expected error message: " + test.error + "\nGot error message: " + e.message); } else { - callback("error", test.code, e.message || e.toString()); + callback("error", test.code, !(e instanceof SyntaxError) && e.stack || e.message || e.toString()); } } } From b46b53e1494611856bf4ca35cc9a6cf22ac23453 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 00:55:49 +0200 Subject: [PATCH 22/40] Loose: implement object methods; expose processed options from acorn. --- acorn.js | 1 + acorn_loose.js | 31 ++++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/acorn.js b/acorn.js index ea4121ec88..ab23dcd278 100644 --- a/acorn.js +++ b/acorn.js @@ -234,6 +234,7 @@ tokRegexpAllowed = reAllowed; skipSpace(); }; + getToken.options = options; return getToken; }; diff --git a/acorn_loose.js b/acorn_loose.js index 9bc5320465..0546d19882 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -40,14 +40,16 @@ var options, input, fetchToken, context; + acorn.defaultOptions.tabSize = 4; + exports.parse_dammit = function(inpt, opts) { if (!opts) opts = {}; input = String(inpt); if (/^#!.*/.test(input)) input = "//" + input.slice(2); - options = opts; if (!opts.tabSize) opts.tabSize = 4; fetchToken = acorn.tokenize(input, opts); + options = fetchToken.options; sourceFile = options.sourceFile || null; context = []; nextLineStart = 0; @@ -750,18 +752,22 @@ isGenerator = eat(tt.star); } parsePropertyName(prop); - if (!prop.key) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } + if (isDummy(prop.key)) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } if (eat(tt.colon)) { - prop.value = parseExpression(true); prop.kind = "init"; + prop.value = parseExpression(true); + } else if (options.ecmaVersion >= 6 && (token.type === tt.parenL || token.type === tt.braceL)) { + prop.kind = "init"; + prop.method = true; + prop.value = parseMethod(isGenerator); } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set")) { prop.kind = prop.key.name; - prop.key = parsePropertyName() || dummyIdent(); - prop.value = parseFunction(startNode(), false); + parsePropertyName(prop); + prop.value = parseMethod(false); } else { - prop.value = options.ecmaVersion >= 6 ? prop.key : dummyIdent(); prop.kind = "init"; + prop.value = options.ecmaVersion >= 6 ? prop.key : dummyIdent(); prop.shorthand = true; } @@ -784,7 +790,8 @@ prop.computed = false; } } - prop.key = (token.type === tt.num || token.type === tt.string) ? parseExprAtom() : parseIdent(); + var key = (token.type === tt.num || token.type === tt.string) ? parseExprAtom() : parseIdent(); + prop.key = key || dummyIdent(); } function parsePropertyAccessor() { @@ -876,6 +883,16 @@ return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); } + function parseMethod(isGenerator) { + var node = startNode(); + initFunction(node); + parseFunctionParams(node); + node.generator = isGenerator; + node.expression = options.ecmaVersion >= 6 && token.type !== tt.braceL; + node.body = node.expression ? parseExpression(true) : parseBlock(); + return finishNode(node, "FunctionExpression"); + } + function parseExprList(close, allowEmpty) { var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart; next(); // Opening bracket From 9cdc6809cede95e0447363abb60e06853dae248d Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 00:57:19 +0200 Subject: [PATCH 23/40] Loose: fix pattern+defaults case in function params. --- acorn_loose.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 0546d19882..c7243986f7 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -852,18 +852,20 @@ pushCx(); var params = parseExprList(tt.parenR); for (var i = 0; i < params.length; i++) { - var param = toAssignable(params[i]), defValue = null; + var param = params[i], defValue = null; + if (param.type === "AssignmentExpression") { + defValue = param.right; + param = param.left; + } + param = toAssignable(param); if (param.type === "SpreadElement") { param = param.argument; if (i === params.length - 1) { node.rest = param; continue; } - } else if (param.type === "AssignmentExpression") { - defValue = param.right; - param = param.left; } - node.params.push(checkLVal(param)); + node.params.push(param); defaults.push(defValue); if (defValue) hasDefaults = true; } From c6b6ef389ea42dfaabe082d22f32fab49346fdca Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 01:00:48 +0200 Subject: [PATCH 24/40] Loose: Remove own tabSize initialization in favor of defaultOptions. --- acorn_loose.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index c7243986f7..0934683b4f 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -46,8 +46,6 @@ if (!opts) opts = {}; input = String(inpt); if (/^#!.*/.test(input)) input = "//" + input.slice(2); - - if (!opts.tabSize) opts.tabSize = 4; fetchToken = acorn.tokenize(input, opts); options = fetchToken.options; sourceFile = options.sourceFile || null; From ede10a079c5f4ece9585d3e34f3950c581bc3d41 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 01:36:16 +0200 Subject: [PATCH 25/40] Loose: class support. --- acorn_loose.js | 58 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 0934683b4f..25f723544c 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -443,6 +443,9 @@ next(); return finishNode(node, "EmptyStatement"); + case tt._class: + return parseObj(true, true); + default: var expr = parseExpression(); if (isDummy(expr)) { @@ -708,6 +711,9 @@ case tt.braceL: return parseObj(); + case tt._class: + return parseObj(true); + case tt._function: var node = startNode(); next(); @@ -735,46 +741,76 @@ return finishNode(node, "NewExpression"); } - function parseObj() { + function parseObj(isClass, isStatement) { var node = startNode(); - node.properties = []; + if (isClass) { + next(); + if (token.type === tt.name) node.id = parseIdent(); + else if (isStatement) node.id = dummyIdent(); + node.superClass = eat(tt._extends) ? parseExpression() : null; + node.body = startNode(); + node.body.body = []; + } else { + node.properties = []; + } pushCx(); var indent = curIndent + 1, line = curLineStart; - next(); + eat(tt.braceL); if (curIndent + 1 < indent) { indent = curIndent; line = curLineStart; } while (!closes(tt.braceR, indent, line)) { var prop = startNode(), isGenerator; if (options.ecmaVersion >= 6) { - prop.method = false; - prop.shorthand = false; + if (isClass) { + if (prop['static'] = (token.type === tt.name && token.value === "static")) next(); + } else { + prop.method = false; + prop.shorthand = false; + } isGenerator = eat(tt.star); } parsePropertyName(prop); if (isDummy(prop.key)) { if (isDummy(parseExpression(true))) next(); eat(tt.comma); continue; } - if (eat(tt.colon)) { + if (!isClass && eat(tt.colon)) { prop.kind = "init"; prop.value = parseExpression(true); } else if (options.ecmaVersion >= 6 && (token.type === tt.parenL || token.type === tt.braceL)) { - prop.kind = "init"; - prop.method = true; + if (isClass) { + prop.kind = ""; + } else { + prop.kind = "init"; + prop.method = true; + } prop.value = parseMethod(isGenerator); } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set")) { prop.kind = prop.key.name; parsePropertyName(prop); prop.value = parseMethod(false); + } else if (isClass) { + prop.kind = ""; + prop.value = parseMethod(isGenerator); } else { prop.kind = "init"; prop.value = options.ecmaVersion >= 6 ? prop.key : dummyIdent(); prop.shorthand = true; } - node.properties.push(finishNode(prop, "Property")); - eat(tt.comma); + if (isClass) { + node.body.body.push(finishNode(prop, "MethodDefinition")); + semicolon(); + } else { + node.properties.push(finishNode(prop, "Property")); + eat(tt.comma); + } } popCx(); eat(tt.braceR); - return finishNode(node, "ObjectExpression"); + if (isClass) { + finishNode(node.body, "ClassBody"); + return finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); + } else { + return finishNode(node, "ObjectExpression"); + } } function parsePropertyName(prop) { From 992fc0503d022b803ed2a69bc6a1f5268af03a00 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 02:01:31 +0200 Subject: [PATCH 26/40] Loose: arrow functions. --- acorn_loose.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 25f723544c..4f2dc7ae31 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -674,8 +674,12 @@ var node = startNode(); next(); return finishNode(node, "ThisExpression"); + case tt.name: - return parseIdent(); + var start = storeCurrentPos(); + var id = parseIdent(); + return eat(tt.arrow) ? parseArrowExpression(startNodeAt(start), [id]) : id; + case tt.num: case tt.string: case tt.regexp: var node = startNode(); node.value = token.value; @@ -695,6 +699,9 @@ next(); var val = parseExpression(); expect(tt.parenR); + if (eat(tt.arrow)) { + return parseArrowExpression(startNodeAt(start), val.expressions || (isDummy(val) ? [] : [val])); + } if (options.preserveParens) { var par = startNodeAt(start); par.expression = val; @@ -880,11 +887,13 @@ return checkLVal(node); } - function parseFunctionParams(node) { + function parseFunctionParams(node, params) { var defaults = [], hasDefaults = false; - pushCx(); - var params = parseExprList(tt.parenR); + if (!params) { + pushCx(); + params = parseExprList(tt.parenR); + } for (var i = 0; i < params.length; i++) { var param = params[i], defValue = null; if (param.type === "AssignmentExpression") { @@ -923,12 +932,20 @@ var node = startNode(); initFunction(node); parseFunctionParams(node); - node.generator = isGenerator; + node.generator = isGenerator || false; node.expression = options.ecmaVersion >= 6 && token.type !== tt.braceL; node.body = node.expression ? parseExpression(true) : parseBlock(); return finishNode(node, "FunctionExpression"); } + function parseArrowExpression(node, params) { + initFunction(node); + parseFunctionParams(node, params); + node.expression = token.type !== tt.braceL; + node.body = node.expression ? parseExpression(true) : parseBlock(); + return finishNode(node, "ArrowFunctionExpression"); + } + function parseExprList(close, allowEmpty) { var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart; next(); // Opening bracket From bdce88c1842186ad0287cce397af981a895fb907 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 02:05:38 +0200 Subject: [PATCH 27/40] Loose: for-of statement. --- acorn_loose.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 4f2dc7ae31..ec2c44bd94 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -338,12 +338,17 @@ if (token.type === tt.semi) return parseFor(node, null); if (token.type === tt._var || token.type === tt._let) { var init = parseVar(true); - if (init.declarations.length === 1 && eat(tt._in)) - return parseForIn(node, init); + if (init.declarations.length === 1) { + if (eat(tt._in)) return parseForIn(node, init); + if (token.type === tt.name && token.value === "of") { + next(); + return parseForIn(node, init, true); + } + } return parseFor(node, init); } var init = parseExpression(false, true); - if (eat(tt._in)) {return parseForIn(node, checkLVal(init));} + if (eat(tt._in)) return parseForIn(node, checkLVal(init)); return parseFor(node, init); case tt._function: @@ -488,13 +493,13 @@ return finishNode(node, "ForStatement"); } - function parseForIn(node, init) { + function parseForIn(node, init, isOf) { node.left = init; node.right = parseExpression(); popCx(); expect(tt.parenR); node.body = parseStatement(); - return finishNode(node, "ForInStatement"); + return finishNode(node, isOf ? "ForOfStatement" : "ForInStatement"); } function parseVar(noIn) { @@ -680,7 +685,7 @@ var id = parseIdent(); return eat(tt.arrow) ? parseArrowExpression(startNodeAt(start), [id]) : id; - case tt.num: case tt.string: case tt.regexp: + case tt.num: case tt.string: case tt.regexp: var node = startNode(); node.value = token.value; node.raw = input.slice(token.start, token.end); From 80f8d527ff108406a54eb592076f2db2654529f6 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 02:25:23 +0200 Subject: [PATCH 28/40] Loose: Skip ES7 tests as we are targeting ES6 now. --- test/driver.js | 1 + test/run.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index ea5dc421c3..cbd6cc3a04 100644 --- a/test/driver.js +++ b/test/driver.js @@ -16,6 +16,7 @@ for (var i = 0; i < tests.length; ++i) { var test = tests[i]; + if (config.filter && !config.filter(test)) continue; try { var testOpts = test.options || {locations: true}; var expected = {}; diff --git a/test/run.js b/test/run.js index fcd40680da..adc5eb9f13 100644 --- a/test/run.js +++ b/test/run.js @@ -11,7 +11,11 @@ var stats, modes = { Loose: { config: { parse: (typeof require === "undefined") ? window.acorn_loose : require("../acorn_loose").parse_dammit, - loose: true + loose: true, + filter: function (test) { + var ecmaVersion = (test.options || {}).ecmaVersion || 5; + return ecmaVersion <= 6; + } } } }; From 1589a959fad68bf9719ea4309a6c46d1192cb8e6 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 02:37:27 +0200 Subject: [PATCH 29/40] Loose: yield support. --- acorn_loose.js | 14 +++++++++++++- test/run.js | 5 +++-- test/tests-harmony.js | 3 +++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index ec2c44bd94..8a75e3dd07 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -271,7 +271,7 @@ return (token.type === tt.eof || token.type === tt.braceR || newline.test(input.slice(lastEnd, token.start))); } function semicolon() { - eat(tt.semi); + return eat(tt.semi); } function expect(type) { @@ -734,6 +734,18 @@ case tt._new: return parseNew(); + case tt._yield: + var node = startNode(); + next(); + if (semicolon() || canInsertSemicolon()) { + node.delegate = false; + node.argument = null; + } else { + node.delegate = eat(tt.star); + node.argument = parseExpression(true); + } + return finishNode(node, "YieldExpression"); + default: return dummyIdent(); } diff --git a/test/run.js b/test/run.js index adc5eb9f13..04001c3d56 100644 --- a/test/run.js +++ b/test/run.js @@ -13,8 +13,9 @@ var stats, modes = { parse: (typeof require === "undefined") ? window.acorn_loose : require("../acorn_loose").parse_dammit, loose: true, filter: function (test) { - var ecmaVersion = (test.options || {}).ecmaVersion || 5; - return ecmaVersion <= 6; + var opts = test.options || {}; + if (opts.loose === false) return false; + return (opts.ecmaVersion || 5) <= 6; } } } diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 2302f58f60..84cf2be487 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -13714,6 +13714,7 @@ test("yield* 10", { } }, { ecmaVersion: 6, + loose: false, ranges: true, locations: true }); @@ -13778,6 +13779,7 @@ test("e => yield* 10", { } }, { ecmaVersion: 6, + loose: false, ranges: true, locations: true }); @@ -13851,6 +13853,7 @@ test("(function () { yield* 10 })", { } }, { ecmaVersion: 6, + loose: false, ranges: true, locations: true }); From aa96edf76932d7ed86a69246888b86728e042ee5 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 27 Oct 2014 02:43:33 +0200 Subject: [PATCH 30/40] Loose: support for-of without var. --- acorn_loose.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 8a75e3dd07..197e184369 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -338,17 +338,15 @@ if (token.type === tt.semi) return parseFor(node, null); if (token.type === tt._var || token.type === tt._let) { var init = parseVar(true); - if (init.declarations.length === 1) { - if (eat(tt._in)) return parseForIn(node, init); - if (token.type === tt.name && token.value === "of") { - next(); - return parseForIn(node, init, true); - } + if (init.declarations.length === 1 && (token.type === tt._in || token.type === tt.name && token.value === "of")) { + return parseForIn(node, init); } return parseFor(node, init); } var init = parseExpression(false, true); - if (eat(tt._in)) return parseForIn(node, checkLVal(init)); + if (token.type === tt._in || token.type === tt.name && token.value === "of") { + return parseForIn(node, checkLVal(init)); + } return parseFor(node, init); case tt._function: @@ -493,13 +491,15 @@ return finishNode(node, "ForStatement"); } - function parseForIn(node, init, isOf) { + function parseForIn(node, init) { + var type = token.type === tt._in ? "ForInStatement" : "ForOfStatement"; + next(); node.left = init; node.right = parseExpression(); popCx(); expect(tt.parenR); node.body = parseStatement(); - return finishNode(node, isOf ? "ForOfStatement" : "ForInStatement"); + return finishNode(node, type); } function parseVar(noIn) { From 4647f966eb291777af4da448af08def26730a24a Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 28 Oct 2014 13:12:18 +0200 Subject: [PATCH 31/40] Loose: don't silently skip missed elements in expr list. --- acorn_loose.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 197e184369..7585b83b28 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -968,8 +968,8 @@ next(); // Opening bracket if (curLineStart > continuedLine) continuedLine = curLineStart; while (!closes(close, indent + (curLineStart <= continuedLine ? 1 : 0), line)) { - if (allowEmpty && eat(tt.comma)) { - elts.push(null); + if (eat(tt.comma)) { + elts.push(allowEmpty ? null : dummyIdent()); continue; } var elt = parseExpression(true); From d4565fed53c387854f51dc18ce3fec7b7150e8ee Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 28 Oct 2014 16:55:30 +0200 Subject: [PATCH 32/40] Loose: ES6 import, export. Removed "kind" from tests for ES6 import/export as it's left only for backward compatibility. --- acorn_loose.js | 96 +++++++++++++++++++++++++++++++++++++++++++ test/tests-harmony.js | 10 +---- 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 7585b83b28..dd1ea902c0 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -449,6 +449,12 @@ case tt._class: return parseObj(true, true); + case tt._import: + return parseImport(); + + case tt._export: + return parseExport(); + default: var expr = parseExpression(); if (isDummy(expr)) { @@ -963,6 +969,96 @@ return finishNode(node, "ArrowFunctionExpression"); } + function parseExport() { + var node = startNode(); + next(); + node['default'] = eat(tt._default); + node.specifiers = node.source = null; + if (node['default']) { + node.declaration = parseExpression(); + semicolon(); + } else if (token.type.keyword) { + node.declaration = parseStatement(); + } else { + node.declaration = null; + parseSpecifierList(node, "Export"); + } + return finishNode(node, "ExportDeclaration"); + } + + function parseImport() { + var node = startNode(); + next(); + if (token.type === tt.string) { + node.specifiers = []; + node.source = parseExprAtom(); + node.kind = ''; + } else { + if (token.type === tt.name && token.value !== "from") { + var elt = startNode(); + elt.id = parseIdent(); + elt.name = null; + elt['default'] = true; + finishNode(elt, "ImportSpecifier"); + eat(tt.comma); + } + parseSpecifierList(node, "Import"); + var specs = node.specifiers; + for (var i = 0; i < specs.length; i++) specs[i]['default'] = false; + if (elt) node.specifiers.unshift(elt); + } + return finishNode(node, "ImportDeclaration"); + } + + function parseSpecifierList(node, prefix) { + var elts = node.specifiers = []; + if (token.type === tt.star) { + var elt = startNode(); + next(); + if (token.type === tt.name && token.value === "as") { + next(); + elt.name = parseIdent(); + } + elts.push(finishNode(elt, prefix + "BatchSpecifier")); + } else { + var indent = curIndent, line = curLineStart, continuedLine = nextLineStart; + pushCx(); + eat(tt.braceL); + if (curLineStart > continuedLine) continuedLine = curLineStart; + while (!closes(tt.braceR, indent + (curLineStart <= continuedLine ? 1 : 0), line)) { + var elt = startNode(); + if (token.type === tt.star) { + next(); + if (token.type === tt.name && token.value === "as") { + next(); + elt.name = parseIdent(); + } + finishNode(elt, prefix + "BatchSpecifier"); + } else { + if (token.type === tt.name && token.value === "from") break; + elt.id = parseIdent(); + if (token.type === tt.name && token.value === "as") { + next(); + elt.name = parseIdent(); + } else { + elt.name = null; + } + finishNode(elt, prefix + "Specifier"); + } + elts.push(elt); + eat(tt.comma); + } + eat(tt.braceR); + popCx(); + } + if (token.type === tt.name && token.value === "from") { + next(); + node.source = parseExprAtom(); + } else { + node.source = null; + } + } + function parseExprList(close, allowEmpty) { var indent = curIndent, line = curLineStart, elts = [], continuedLine = nextLineStart; next(); // Opening bracket diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 84cf2be487..00149c1ee7 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -5113,7 +5113,6 @@ test("import $ from \"jquery\"", { end: {line: 1, column: 8} } }], - kind: "default", source: { type: "Literal", value: "jquery", @@ -5176,7 +5175,6 @@ test("import { encrypt, decrypt } from \"crypto\"", { } } ], - kind: "named", source: { type: "Literal", value: "crypto", @@ -5228,7 +5226,6 @@ test("import { encrypt as enc } from \"crypto\"", { end: {line: 1, column: 23} } }], - kind: "named", source: { type: "Literal", value: "crypto", @@ -5333,8 +5330,7 @@ test("import crypto, { decrypt, encrypt as enc } from \"crypto\"", { }, value: "crypto", raw: "\"crypto\"" - }, - kind: "default" + } }] }, { ecmaVersion: 6, @@ -5371,7 +5367,6 @@ test("import { null as nil } from \"bar\"", { end: {line: 1, column: 20} } }], - kind: "named", source: { type: "Literal", value: "bar", @@ -5431,8 +5426,7 @@ test("import * as crypto from \"crypto\"", { }, value: "crypto", raw: "\"crypto\"" - }, - kind: "named" + } }] }, { ecmaVersion: 6, From fc2e96fa01ba6b24fea6e366e2946e4c41d13ccc Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 28 Oct 2014 18:43:37 +0200 Subject: [PATCH 33/40] Loose: respect optional semicolons in break/continue/class/import/export. --- acorn_loose.js | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index dd1ea902c0..f551679a58 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -315,8 +315,12 @@ case tt._break: case tt._continue: next(); var isBreak = starttype === tt._break; - node.label = token.type === tt.name ? parseIdent() : null; - semicolon(); + if (semicolon() || canInsertSemicolon()) { + node.label = null; + } else { + node.label = token.type === tt.name ? parseIdent() : null; + semicolon(); + } return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); case tt._debugger: @@ -771,6 +775,35 @@ return finishNode(node, "NewExpression"); } + function parseTemplate() { + var node = startNode(); + node.expressions = []; + node.quasis = []; + inTemplate = true; + next(); + for (;;) { + var elem = startNode(); + elem.value = {cooked: tokVal, raw: input.slice(tokStart, tokEnd)}; + elem.tail = false; + next(); + node.quasis.push(finishNode(elem, "TemplateElement")); + if (tokType === _bquote) { // '`', end of template + elem.tail = true; + break; + } + inTemplate = false; + expect(_dollarBraceL); + node.expressions.push(parseExpression()); + inTemplate = true; + // hack to include previously skipped space + tokPos = tokEnd; + expect(_braceR); + } + inTemplate = false; + next(); + return finishNode(node, "TemplateLiteral"); + } + function parseObj(isClass, isStatement) { var node = startNode(); if (isClass) { @@ -836,6 +869,7 @@ popCx(); eat(tt.braceR); if (isClass) { + semicolon(); finishNode(node.body, "ClassBody"); return finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); } else { @@ -983,6 +1017,7 @@ node.declaration = null; parseSpecifierList(node, "Export"); } + semicolon(); return finishNode(node, "ExportDeclaration"); } @@ -1007,6 +1042,7 @@ for (var i = 0; i < specs.length; i++) specs[i]['default'] = false; if (elt) node.specifiers.unshift(elt); } + semicolon(); return finishNode(node, "ImportDeclaration"); } From 2419de74dcf88d3a7a0b34a8845ea1d444f1538a Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 31 Oct 2014 00:47:33 +0200 Subject: [PATCH 34/40] Loose: Fix regex after tokenizer changes in #144. --- acorn_loose.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/acorn_loose.js b/acorn_loose.js index f551679a58..261d9e76b5 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -695,7 +695,16 @@ var id = parseIdent(); return eat(tt.arrow) ? parseArrowExpression(startNodeAt(start), [id]) : id; - case tt.num: case tt.string: case tt.regexp: + case tt.regexp: + var node = startNode(); + var val = token.value; + node.regex = {pattern: val.pattern, flags: val.flags}; + node.value = val.value; + node.raw = input.slice(token.start, token.end); + next(); + return finishNode(node, "Literal"); + + case tt.num: case tt.string: var node = startNode(); node.value = token.value; node.raw = input.slice(token.start, token.end); From 6bf8311061807e719bfa4c6efe156e8db7214bca Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 31 Oct 2014 01:09:21 +0200 Subject: [PATCH 35/40] Loose: fix #33. --- acorn_loose.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 261d9e76b5..4c5806a179 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -59,15 +59,14 @@ var lastEnd, token = {start: 0, end: 0}, ahead = []; var curLineStart, nextLineStart, curIndent, lastEndLoc, sourceFile; - function next() { + function next(forceRegexp) { lastEnd = token.end; if (options.locations) lastEndLoc = token.endLoc; + if (forceRegexp) + ahead.length = 0; - if (ahead.length) - token = ahead.shift(); - else - token = readToken(); + token = ahead.shift() || readToken(forceRegexp); if (token.start >= nextLineStart) { while (token.start >= nextLineStart) { @@ -78,10 +77,10 @@ } } - function readToken() { + function readToken(forceRegexp) { for (;;) { try { - var tok = fetchToken(); + var tok = fetchToken(forceRegexp); if (tok.type === tt.dot && input.substr(tok.end, 1) === '.') { tok = fetchToken(); tok.start--; @@ -309,6 +308,9 @@ } function parseStatement() { + if (token.type === tt.slash || token.type === tt.assign && token.value === "/=") + next(true); + var starttype = token.type, node = startNode(); switch (starttype) { From 96ccdb05faf175de545ea01085302bac5a590eeb Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 3 Nov 2014 20:07:19 +0200 Subject: [PATCH 36/40] Web-driver support for loose parser + small fixes. * Added support for acorn_loose and grouped log to web-driver. * Removed unused copy-pasted `parseTemplate` from loose parser. * Throw non-SyntaxError errors immediately (as those are generic). --- acorn_loose.js | 29 ---------- test/driver.js | 7 ++- test/index.html | 21 ++------ test/run.js | 138 ++++++++++++++++++++++++++++++++---------------- 4 files changed, 103 insertions(+), 92 deletions(-) diff --git a/acorn_loose.js b/acorn_loose.js index 4c5806a179..9b739da101 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -786,35 +786,6 @@ return finishNode(node, "NewExpression"); } - function parseTemplate() { - var node = startNode(); - node.expressions = []; - node.quasis = []; - inTemplate = true; - next(); - for (;;) { - var elem = startNode(); - elem.value = {cooked: tokVal, raw: input.slice(tokStart, tokEnd)}; - elem.tail = false; - next(); - node.quasis.push(finishNode(elem, "TemplateElement")); - if (tokType === _bquote) { // '`', end of template - elem.tail = true; - break; - } - inTemplate = false; - expect(_dollarBraceL); - node.expressions.push(parseExpression()); - inTemplate = true; - // hack to include previously skipped space - tokPos = tokEnd; - expect(_braceR); - } - inTemplate = false; - next(); - return finishNode(node, "TemplateLiteral"); - } - function parseObj(isClass, isStatement) { var node = startNode(); if (isClass) { diff --git a/test/driver.js b/test/driver.js index cbd6cc3a04..c76b8f91a4 100644 --- a/test/driver.js +++ b/test/driver.js @@ -52,12 +52,15 @@ else callback("ok", test.code); } } catch(e) { - if (test.error && e instanceof SyntaxError) { + if (!(e instanceof SyntaxError)) { + throw e; + } + if (test.error) { if (e.message == test.error) callback("ok", test.code); else callback("fail", test.code, "Expected error message: " + test.error + "\nGot error message: " + e.message); } else { - callback("error", test.code, !(e instanceof SyntaxError) && e.stack || e.message || e.toString()); + callback("error", test.code, e.message || e.toString()); } } } diff --git a/test/index.html b/test/index.html index 96a9d952c5..ef80eb7b15 100644 --- a/test/index.html +++ b/test/index.html @@ -3,23 +3,12 @@ Acorn test suite + - - + +
    + + diff --git a/test/run.js b/test/run.js index 04001c3d56..fefcaed6a7 100644 --- a/test/run.js +++ b/test/run.js @@ -1,56 +1,104 @@ -var driver = require("./driver.js"); -require("./tests.js"); -require("./tests-harmony.js"); +(function() { + var driver; -var stats, modes = { - Normal: { - config: { - parse: (typeof require === "undefined" ? window.acorn : require("../acorn.js")).parse + if (typeof require !== "undefined") { + driver = require("./driver.js"); + require("./tests.js"); + require("./tests-harmony.js"); + } else { + driver = window; + } + + var htmlLog = typeof document === "object" && document.getElementById('log'); + var htmlGroup; + + function group(name) { + if (htmlLog) { + htmlGroup = document.createElement("ul"); + var item = document.createElement("li"); + item.textContent = name; + item.appendChild(htmlGroup); + htmlLog.appendChild(item); } - }, - Loose: { - config: { - parse: (typeof require === "undefined") ? window.acorn_loose : require("../acorn_loose").parse_dammit, - loose: true, - filter: function (test) { - var opts = test.options || {}; - if (opts.loose === false) return false; - return (opts.ecmaVersion || 5) <= 6; - } + if (typeof console === "object" && console.group) { + console.group(name); } } -}; -function report(state, code, message) { - if (state != "ok") {++stats.failed; console.log(code, message);} - ++stats.testsRun; -} + function groupEnd() { + htmlGroup = null; + if (typeof console === "object" && console.groupEnd) { + console.groupEnd(name); + } + } -for (var name in modes) { - var mode = modes[name]; - stats = mode.stats = {testsRun: 0, failed: 0}; - var t0 = +new Date; - driver.runTests(mode.config, report); - mode.stats.duration = +new Date - t0; -} + function log(title, message) { + if (htmlGroup) { + var elem = document.createElement("li"); + elem.innerHTML = "" + title + " " + message; + htmlGroup.appendChild(elem); + } + if (typeof console === "object") console.log(title, message); + } -function outputStats(name, stats) { - console.log(name + ": " + stats.testsRun + " tests run in " + stats.duration + "ms; " + - (stats.failed ? stats.failed + " failures." : "all passed.")); -} + var stats, modes = { + Normal: { + config: { + parse: (typeof require === "undefined" ? window.acorn : require("../acorn.js")).parse + } + }, + Loose: { + config: { + parse: (typeof require === "undefined" ? window.acorn : require("../acorn_loose")).parse_dammit, + loose: true, + filter: function (test) { + var opts = test.options || {}; + if (opts.loose === false) return false; + return (opts.ecmaVersion || 5) <= 6; + } + } + } + }; -var total = {testsRun: 0, failed: 0, duration: 0}; + function report(state, code, message) { + if (state != "ok") {++stats.failed; log(code, message);} + ++stats.testsRun; + } -for (var name in modes) { - var stats = modes[name].stats; - outputStats(name + " parser", stats); - for (var key in stats) total[key] += stats[key]; -} + group("Errors"); -outputStats("Total", total); + for (var name in modes) { + var mode = modes[name]; + stats = mode.stats = {testsRun: 0, failed: 0}; + var t0 = +new Date; + driver.runTests(mode.config, report); + mode.stats.duration = +new Date - t0; + } -if (total.failed) { - process.stdout.write("", function() { - process.exit(1); - }); -} + groupEnd(); + + function outputStats(name, stats) { + log(name + ":", stats.testsRun + " tests run in " + stats.duration + "ms; " + + (stats.failed ? stats.failed + " failures." : "all passed.")); + } + + var total = {testsRun: 0, failed: 0, duration: 0}; + + group("Stats"); + + for (var name in modes) { + var stats = modes[name].stats; + outputStats(name + " parser", stats); + for (var key in stats) total[key] += stats[key]; + } + + outputStats("Total", total); + + groupEnd(); + + if (total.failed && typeof process === "object") { + process.stdout.write("", function() { + process.exit(1); + }); + } +})(); From bc64d3c5f4a96c7540697a7624addd7d563c0ed1 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 4 Nov 2014 02:01:02 +0200 Subject: [PATCH 37/40] Add support for nested groups in log (Chrome console + browser). --- test/run.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/run.js b/test/run.js index fefcaed6a7..04f752dc3b 100644 --- a/test/run.js +++ b/test/run.js @@ -10,15 +10,16 @@ } var htmlLog = typeof document === "object" && document.getElementById('log'); - var htmlGroup; + var htmlGroup = htmlLog; function group(name) { - if (htmlLog) { + if (htmlGroup) { + var parentGroup = htmlGroup; htmlGroup = document.createElement("ul"); var item = document.createElement("li"); item.textContent = name; item.appendChild(htmlGroup); - htmlLog.appendChild(item); + parentGroup.appendChild(item); } if (typeof console === "object" && console.group) { console.group(name); @@ -26,7 +27,9 @@ } function groupEnd() { - htmlGroup = null; + if (htmlGroup) { + htmlGroup = htmlGroup.parentElement.parentElement; + } if (typeof console === "object" && console.groupEnd) { console.groupEnd(name); } @@ -68,11 +71,13 @@ group("Errors"); for (var name in modes) { + group(name); var mode = modes[name]; stats = mode.stats = {testsRun: 0, failed: 0}; var t0 = +new Date; driver.runTests(mode.config, report); mode.stats.duration = +new Date - t0; + groupEnd(); } groupEnd(); From 249e6961f884e4660ec8fe2e4af8fb53b929ac13 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 12 Nov 2014 17:04:41 +0100 Subject: [PATCH 38/40] Make tests pass for loose parser Define Program node extent to be the whole program, make both parser and the tests conform to this. Fix a bunch of bugs in the loose parser's handling of corner cases. Issue #151 --- acorn.js | 24 +++++++++++++------ acorn_loose.js | 5 +++- test/run.js | 1 + test/tests.js | 62 +++++++++++++++++++++++++------------------------- 4 files changed, 53 insertions(+), 39 deletions(-) diff --git a/acorn.js b/acorn.js index ab23dcd278..97ccf2c993 100644 --- a/acorn.js +++ b/acorn.js @@ -43,8 +43,9 @@ input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); + var startPos = options.locations ? [tokStart, new Position] : tokStart; initParserState(); - return parseTopLevel(options.program); + return parseTopLevel(options.program || startNodeAt(startPos)); }; // A second optional argument can be given to further configure @@ -214,6 +215,7 @@ input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); + skipSpace(); function getToken(forceRegexp) { lastEnd = tokEnd; @@ -234,6 +236,9 @@ tokRegexpAllowed = reAllowed; skipSpace(); }; + getToken.noRegexp = function() { + tokRegexpAllowed = false; + }; getToken.options = options; return getToken; }; @@ -308,6 +313,7 @@ if (options.locations) lastEndLoc = new Position; inFunction = inGenerator = strict = false; labels = []; + skipSpace(); readToken(); } @@ -446,7 +452,8 @@ parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string, - arrow: _arrow, bquote: _bquote, dollarBraceL: _dollarBraceL, star: _star}; + arrow: _arrow, bquote: _bquote, dollarBraceL: _dollarBraceL, star: _star, + assign: _assign}; for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on @@ -592,7 +599,6 @@ tokRegexpAllowed = true; metParenL = 0; inTemplate = false; - skipSpace(); } // Called at the end of every token. Sets `tokEnd`, `tokVal`, and @@ -1505,15 +1511,19 @@ // `program` argument. If present, the statements will be appended // to its body instead of creating a new node. - function parseTopLevel(program) { - var node = program || startNode(), first = true; - if (!program) node.body = []; + function parseTopLevel(node) { + var first = true; + if (!node.body) node.body = []; while (tokType !== _eof) { var stmt = parseStatement(); node.body.push(stmt); if (first && isUseStrict(stmt)) setStrict(true); first = false; } + + lastStart = tokStart; + lastEnd = tokEnd; + lastEndLoc = tokEndLoc; return finishNode(node, "Program"); } @@ -2062,7 +2072,7 @@ case _parenL: var start = storeCurrentPos(); - var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart, val, exprList; + var val, exprList; next(); // check whether this is generator comprehension or regular expression if (options.ecmaVersion >= 7 && tokType === _for) { diff --git a/acorn_loose.js b/acorn_loose.js index 9b739da101..649aec6241 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -301,9 +301,11 @@ } function parseTopLevel() { - var node = startNode(); + var node = startNodeAt(options.locations ? [0, acorn.getLineInfo(input, 0)] : 0); node.body = []; while (token.type !== tt.eof) node.body.push(parseStatement()); + lastEnd = token.end; + lastEndLoc = token.endLoc; return finishNode(node, "Program"); } @@ -881,6 +883,7 @@ function parseIdent() { var node = startNode(); node.name = token.type === tt.name ? token.value : token.type.keyword; + fetchToken.noRegexp(); next(); return finishNode(node, "Identifier"); } diff --git a/test/run.js b/test/run.js index 04f752dc3b..e896397100 100644 --- a/test/run.js +++ b/test/run.js @@ -55,6 +55,7 @@ parse: (typeof require === "undefined" ? window.acorn : require("../acorn_loose")).parse_dammit, loose: true, filter: function (test) { + if (/`/.test(test.code)) return false; // FIXME remove this when the loose parse supports template strings var opts = test.options || {}; if (opts.loose === false) return false; return (opts.ecmaVersion || 5) <= 6; diff --git a/test/tests.js b/test/tests.js index f1d9467745..e50952826c 100644 --- a/test/tests.js +++ b/test/tests.js @@ -43,8 +43,8 @@ test("this\n", { column: 0 }, end: { - line: 1, - column: 4 + line: 2, + column: 0 } } }); @@ -86,8 +86,8 @@ test("null\n", { column: 0 }, end: { - line: 1, - column: 4 + line: 2, + column: 0 } } }); @@ -125,12 +125,12 @@ test("\n 42\n\n", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { - line: 2, - column: 6 + line: 4, + column: 0 } } }); @@ -4916,7 +4916,7 @@ test("/* block comment */ 42", { loc: { start: { line: 1, - column: 20 + column: 0 }, end: { line: 1, @@ -4963,7 +4963,7 @@ test("42 /*The*/ /*Answer*/", { }, end: { line: 1, - column: 2 + column: 21 } } }); @@ -5006,7 +5006,7 @@ test("42 /*the*/ /*answer*/", { }, end: { line: 1, - column: 2 + column: 21 } } }); @@ -5044,8 +5044,8 @@ test("/* multiline\ncomment\nshould\nbe\nignored */ 42", { ], loc: { start: { - line: 5, - column: 11 + line: 1, + column: 0 }, end: { line: 5, @@ -5087,8 +5087,8 @@ test("/*a\r\nb*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5130,8 +5130,8 @@ test("/*a\rb*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5173,8 +5173,8 @@ test("/*a\nb*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5216,8 +5216,8 @@ test("/*a\nc*/ 42", { ], loc: { start: { - line: 2, - column: 4 + line: 1, + column: 0 }, end: { line: 2, @@ -5259,7 +5259,7 @@ test("// line comment\n42", { ], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5307,7 +5307,7 @@ test("42 // line comment", { }, end: { line: 1, - column: 2 + column: 18 } } }); @@ -5345,7 +5345,7 @@ test("// Hello, world!\n42", { ], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5360,7 +5360,7 @@ test("// Hello, world!\n", { body: [], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5375,7 +5375,7 @@ test("// Hallo, world!\n", { body: [], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5418,7 +5418,7 @@ test("//\n42", { ], loc: { start: { - line: 2, + line: 1, column: 0 }, end: { @@ -5434,7 +5434,7 @@ test("//", { loc: { start: { line: 1, - column: 2 + column: 0 }, end: { line: 1, @@ -5449,7 +5449,7 @@ test("// ", { loc: { start: { line: 1, - column: 3 + column: 0 }, end: { line: 1, @@ -5492,7 +5492,7 @@ test("/**/42", { loc: { start: { line: 1, - column: 4 + column: 0 }, end: { line: 1, @@ -5534,7 +5534,7 @@ test("// Hello, world!\n\n// Another hello\n42", { ], loc: { start: { - line: 4, + line: 1, column: 0 }, end: { From 98691e5b808fc332aedabb47ad2c3ab66487bfb8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 12 Nov 2014 17:31:45 +0100 Subject: [PATCH 39/40] Properly initialize top node start position --- acorn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acorn.js b/acorn.js index 97ccf2c993..ca5e8eb731 100644 --- a/acorn.js +++ b/acorn.js @@ -43,7 +43,7 @@ input = String(inpt); inputLen = input.length; setOptions(opts); initTokenState(); - var startPos = options.locations ? [tokStart, new Position] : tokStart; + var startPos = options.locations ? [tokPos, new Position] : tokPos; initParserState(); return parseTopLevel(options.program || startNodeAt(startPos)); }; From f48a921e249392226387b3e6323b01e6b3f5c3c5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Nov 2014 12:02:47 -0500 Subject: [PATCH 40/40] allow `export { default } from "foo"` --- acorn.js | 2 +- test/tests-harmony.js | 53 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/acorn.js b/acorn.js index ca5e8eb731..526c3a4f35 100644 --- a/acorn.js +++ b/acorn.js @@ -2524,7 +2524,7 @@ } else first = false; var node = startNode(); - node.id = parseIdent(); + node.id = parseIdent(tokType === _default); if (tokType === _name && tokVal === "as") { next(); node.name = parseIdent(true); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 00149c1ee7..f5064c8a94 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -5064,6 +5064,59 @@ test("export { encrypt, decrypt as dec }", { locations: true }); +test("export { default } from \"other\"", { + type: "Program", + body: [{ + type: "ExportDeclaration", + declaration: null, + specifiers: [ + { + type: "ExportSpecifier", + id: { + type: "Identifier", + name: "default", + loc: { + start: {line: 1, column: 9}, + end: {line: 1, column: 16} + } + }, + name: null, + loc: { + start: {line: 1, column: 9}, + end: {line: 1, column: 16} + } + } + ], + source: { + type: "Literal", + loc: { + start: { + line: 1, + column: 24 + }, + end: { + line: 1, + column: 31 + } + }, + value: "other", + raw: "\"other\"" + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 31} + } + }], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 31} + } +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); + test("import \"jquery\"", { type: "Program", body: [{