From 7d19ee6178b68d42206851b0fc7d1f5502259fe1 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sat, 30 May 2020 19:41:05 +0300 Subject: [PATCH 01/17] Added optional chaining support WIP --- escodegen.js | 18 +++- package.json | 2 +- test/compare-acorn-es2020.js | 92 +++++++++++++++++++ .../optional-chaining.expected.js | 4 + .../optional-chaining.expected.min.js | 4 + .../compare-acorn-es2020/optional-chaining.js | 4 + 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 test/compare-acorn-es2020.js create mode 100644 test/compare-acorn-es2020/optional-chaining.expected.js create mode 100644 test/compare-acorn-es2020/optional-chaining.expected.min.js create mode 100644 test/compare-acorn-es2020/optional-chaining.js diff --git a/escodegen.js b/escodegen.js index 4c055b88..09d1e617 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1873,6 +1873,11 @@ var result, i, iz; // F_ALLOW_UNPARATH_NEW becomes false. result = [this.generateExpression(expr.callee, Precedence.Call, E_TTF)]; + + if (expr.optional) { + result.push('?.'); + } + result.push('('); for (i = 0, iz = expr['arguments'].length; i < iz; ++i) { result.push(this.generateExpression(expr['arguments'][i], Precedence.Assignment, E_TTT)); @@ -1922,6 +1927,10 @@ result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; if (expr.computed) { + if (expr.optional) { + result.push('?.'); + } + result.push('['); result.push(this.generateExpression(expr.property, Precedence.Sequence, flags & F_ALLOW_CALL ? E_TTT : E_TFT)); result.push(']'); @@ -1943,7 +1952,7 @@ result.push(' '); } } - result.push('.'); + result.push(expr.optional ? '?.' : '.'); result.push(generateIdentifier(expr.property)); } @@ -2459,6 +2468,13 @@ ], Precedence.Call, precedence); }, + OptionalMemberExpression: function(expr, precedence, flag) { + return this.MemberExpression(expr, precedence, flag); + }, + + OptionalCallExpression: function(expr, precedence, flag) { + return this.CallExpression(expr, precedence, flag); + }, }; merge(CodeGenerator.prototype, CodeGenerator.Expression); diff --git a/package.json b/package.json index a2adedd6..c5b0bab1 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "source-map": "~0.6.1" }, "devDependencies": { - "acorn": "^7.1.0", + "acorn": "^7.2.0", "bluebird": "^3.4.7", "bower-registry-client": "^1.0.0", "chai": "^3.5.0", diff --git a/test/compare-acorn-es2020.js b/test/compare-acorn-es2020.js new file mode 100644 index 00000000..af537e99 --- /dev/null +++ b/test/compare-acorn-es2020.js @@ -0,0 +1,92 @@ +/* + Copyright (C) 2012-2013 Yusuke Suzuki + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +'use strict'; + +var fs = require('fs'), + acorn = require('acorn'), + escodegen = require('./loader'), + chai = require('chai'), + expect = chai.expect; + +function test(code, expected) { + var tree, actual, options, StringObject; + + // alias, so that JSLint does not complain. + StringObject = String; + + options = { + ranges: true, + locations: false, + ecmaVersion: 11 + }; + + tree = acorn.parse(code, options); + + // for UNIX text comment + actual = escodegen.generate(tree).replace(/[\n\r]$/, '') + '\n'; + expect(actual).to.be.equal(expected); +} + +function testMin(code, expected) { + var tree, actual, options, StringObject; + + // alias, so that JSLint does not complain. + StringObject = String; + + options = { + ranges: true, + locations: false, + ecmaVersion: 11 + }; + + tree = acorn.parse(code, options); + + // for UNIX text comment + actual = escodegen.generate(tree, { + format: escodegen.FORMAT_MINIFY, + raw: false + }).replace(/[\n\r]$/, '') + '\n'; + expect(actual).to.be.equal(expected); +} + +describe('compare acorn es2020 test', function () { + fs.readdirSync(__dirname + '/compare-acorn-es2020').sort().forEach(function(file) { + var code, expected, exp, min; + if (/\.js$/.test(file) && !/expected\.js$/.test(file) && !/expected\.min\.js$/.test(file)) { + it(file, function () { + exp = file.replace(/\.js$/, '.expected.js'); + min = file.replace(/\.js$/, '.expected.min.js'); + code = fs.readFileSync(__dirname + '/compare-acorn-es2020/' + file, 'utf-8'); + expected = fs.readFileSync(__dirname + '/compare-acorn-es2020/' + exp, 'utf-8'); + test(code, expected); + if (fs.existsSync(__dirname + '/compare-acorn-es2020/' + min)) { + expected = fs.readFileSync(__dirname + '/compare-acorn-es2020/' + min, 'utf-8'); + testMin(code, expected); + } + }); + } + }); +}); +/* vim: set sw=4 ts=4 et tw=80 : */ \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js new file mode 100644 index 00000000..0caa9601 --- /dev/null +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -0,0 +1,4 @@ +foo?.bar(); +foo.bar?.baz(); +foo?.bar?.(); +foo?.[bar]; \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js new file mode 100644 index 00000000..0caa9601 --- /dev/null +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -0,0 +1,4 @@ +foo?.bar(); +foo.bar?.baz(); +foo?.bar?.(); +foo?.[bar]; \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js new file mode 100644 index 00000000..0caa9601 --- /dev/null +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -0,0 +1,4 @@ +foo?.bar(); +foo.bar?.baz(); +foo?.bar?.(); +foo?.[bar]; \ No newline at end of file From 7f5d20fd3a5f79bc4645415f2e8f0a060b9da341 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Thu, 11 Jun 2020 14:39:08 +0300 Subject: [PATCH 02/17] Updated optional chaining support to the latest spec --- escodegen.js | 16 +++++++--------- package.json | 2 +- .../optional-chaining.expected.js | 2 +- .../optional-chaining.expected.min.js | 5 +---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/escodegen.js b/escodegen.js index 09d1e617..52aa14cc 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1893,6 +1893,12 @@ return parenthesize(result, Precedence.Call, precedence); }, + ChainExpression: function (expr, precedence, flags) { + var expression = expr.expression; + + return this[expression.type](expression, precedence, flags); + }, + NewExpression: function (expr, precedence, flags) { var result, length, i, iz, itemFlags; length = expr['arguments'].length; @@ -2466,15 +2472,7 @@ this.generateExpression(expr.source, Precedence.Assignment, E_TTT), ')' ], Precedence.Call, precedence); - }, - - OptionalMemberExpression: function(expr, precedence, flag) { - return this.MemberExpression(expr, precedence, flag); - }, - - OptionalCallExpression: function(expr, precedence, flag) { - return this.CallExpression(expr, precedence, flag); - }, + } }; merge(CodeGenerator.prototype, CodeGenerator.Expression); diff --git a/package.json b/package.json index c5b0bab1..a3c93a1f 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "source-map": "~0.6.1" }, "devDependencies": { - "acorn": "^7.2.0", + "acorn": "^7.3.1", "bluebird": "^3.4.7", "bower-registry-client": "^1.0.0", "chai": "^3.5.0", diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 0caa9601..dadf6788 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -1,4 +1,4 @@ foo?.bar(); foo.bar?.baz(); foo?.bar?.(); -foo?.[bar]; \ No newline at end of file +foo?.[bar]; diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index 0caa9601..f3f094cd 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1,4 +1 @@ -foo?.bar(); -foo.bar?.baz(); -foo?.bar?.(); -foo?.[bar]; \ No newline at end of file +foo?.bar();foo.bar?.baz();foo?.bar?.();foo?.[bar] From 97262e25074c2fcab7cbc128fdc4f596d149c4e0 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Thu, 11 Jun 2020 14:58:42 +0300 Subject: [PATCH 03/17] Updated tests to match examples from spec --- .../optional-chaining.expected.js | 20 +++++++++++++++---- .../optional-chaining.expected.min.js | 2 +- .../compare-acorn-es2020/optional-chaining.js | 20 +++++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index dadf6788..c2f79fbe 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -1,4 +1,16 @@ -foo?.bar(); -foo.bar?.baz(); -foo?.bar?.(); -foo?.[bar]; +obj.aaa.bbb; +obj.aaa?.bbb; +obj?.aaa.bbb; +obj?.aaa?.bbb; +obj.aaa.bbb; +obj.aaa?.bbb; +obj?.aaa.bbb; +obj?.aaa?.bbb; +obj[aaa][bbb]; +obj[aaa]?.[bbb]; +obj?.[aaa][bbb]; +obj?.[aaa]?.[bbb]; +obj[aaa][bb]; +obj[aaa]?.[bbb]; +obj?.[aaa][bbb]; +obj?.[aaa]?.[bbb]; diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index f3f094cd..ba7756ae 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -foo?.bar();foo.bar?.baz();foo?.bar?.();foo?.[bar] +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb] diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index 0caa9601..6b63d9af 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -1,4 +1,16 @@ -foo?.bar(); -foo.bar?.baz(); -foo?.bar?.(); -foo?.[bar]; \ No newline at end of file +obj.aaa.bbb; +obj.aaa?.bbb; +obj?.aaa.bbb; +obj?.aaa?.bbb; +(obj.aaa).bbb; +(obj.aaa)?.bbb; +(obj?.aaa).bbb; +(obj?.aaa)?.bbb; +obj[aaa][bbb]; +obj[aaa]?.[bbb]; +obj?.[aaa][bbb]; +obj?.[aaa]?.[bbb]; +(obj[aaa])[bb]; +(obj[aaa])?.[bbb]; +(obj?.[aaa])[bbb]; +(obj?.[aaa])?.[bbb]; From 8b56419090f308bb111cf2bebfcc393ebcd26743 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Thu, 11 Jun 2020 22:05:17 +0300 Subject: [PATCH 04/17] Updated tests with correct expectations --- test/compare-acorn-es2020/optional-chaining.expected.js | 4 ++-- test/compare-acorn-es2020/optional-chaining.expected.min.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index c2f79fbe..21f56dd9 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -4,7 +4,7 @@ obj?.aaa.bbb; obj?.aaa?.bbb; obj.aaa.bbb; obj.aaa?.bbb; -obj?.aaa.bbb; +(obj?.aaa).bbb; obj?.aaa?.bbb; obj[aaa][bbb]; obj[aaa]?.[bbb]; @@ -12,5 +12,5 @@ obj?.[aaa][bbb]; obj?.[aaa]?.[bbb]; obj[aaa][bb]; obj[aaa]?.[bbb]; -obj?.[aaa][bbb]; +(obj?.[aaa])[bbb]; obj?.[aaa]?.[bbb]; diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index ba7756ae..6555fb73 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb] +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;obj?.aaa?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];obj?.[aaa]?.[bbb] From 5af1f7b9acf1db2af90acb50615a1095427b74ee Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Fri, 12 Jun 2020 21:56:36 +0300 Subject: [PATCH 05/17] Updated optional chaining parenthesis and added more tests --- escodegen.js | 18 ++++++++++++++---- .../optional-chaining.expected.js | 6 ++++++ .../optional-chaining.expected.min.js | 2 +- test/compare-acorn-es2020/optional-chaining.js | 6 ++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/escodegen.js b/escodegen.js index 52aa14cc..51ea4c16 100644 --- a/escodegen.js +++ b/escodegen.js @@ -102,8 +102,9 @@ Call: 16, New: 17, TaggedTemplate: 18, - Member: 19, - Primary: 20 + OptionalMember: 19, + Member: 20, + Primary: 21 }; BinaryPrecedence = { @@ -1929,8 +1930,13 @@ MemberExpression: function (expr, precedence, flags) { var result, fragment; - // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; + if (!expr.optional && expr.object.type === 'ChainExpression') { + // F_ALLOW_UNPARATH_NEW becomes false. + result = [this.generateExpression(expr.object, Precedence.Member, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; + } else { + // F_ALLOW_UNPARATH_NEW becomes false. + result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; + } if (expr.computed) { if (expr.optional) { @@ -1962,6 +1968,10 @@ result.push(generateIdentifier(expr.property)); } + if (precedence === Precedence.Member) { + return parenthesize(result, Precedence.OptionalMember, precedence); + } + return parenthesize(result, Precedence.Member, precedence); }, diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 21f56dd9..7c237aa3 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -6,11 +6,17 @@ obj.aaa.bbb; obj.aaa?.bbb; (obj?.aaa).bbb; obj?.aaa?.bbb; +(obj?.aaa).bbb.ccc.ddd; +((obj?.aaa).bbb?.ccc).ddd; +obj?.aaa?.bbb; obj[aaa][bbb]; obj[aaa]?.[bbb]; obj?.[aaa][bbb]; obj?.[aaa]?.[bbb]; obj[aaa][bb]; obj[aaa]?.[bbb]; +obj[aaa]?.[bbb]; (obj?.[aaa])[bbb]; obj?.[aaa]?.[bbb]; +obj[aaa][bbb][ccc][ddd]; +((obj?.[aaa])[bbb]?.[ccc])[ddd]; diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index 6555fb73..ebe3d7f0 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;obj?.aaa?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];obj?.[aaa]?.[bbb] +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;obj?.aaa?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;obj?.aaa?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bb];obj[aaa]?.[bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd] diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index 6b63d9af..1ccd7ddd 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -6,11 +6,17 @@ obj?.aaa?.bbb; (obj.aaa)?.bbb; (obj?.aaa).bbb; (obj?.aaa)?.bbb; +((obj?.aaa).bbb.ccc).ddd; +((obj?.aaa).bbb?.ccc).ddd; +(obj?.aaa)?.bbb; obj[aaa][bbb]; obj[aaa]?.[bbb]; obj?.[aaa][bbb]; obj?.[aaa]?.[bbb]; (obj[aaa])[bb]; (obj[aaa])?.[bbb]; +(obj[aaa])?.[bbb]; (obj?.[aaa])[bbb]; (obj?.[aaa])?.[bbb]; +((obj[aaa])[bbb][ccc])[ddd]; +((obj?.[aaa])[bbb]?.[ccc])[ddd]; From 6e9867a5489c32ccb57041a1f4f50186d1373ce0 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sat, 13 Jun 2020 00:44:12 +0300 Subject: [PATCH 06/17] Updated optional chaining support, added AST comparison tests --- escodegen.js | 2 +- package.json | 1 + test/compare-acorn-es2020.js | 25 +++++++++++-------- .../optional-chaining.expected.js | 11 ++++---- .../optional-chaining.expected.min.js | 2 +- .../compare-acorn-es2020/optional-chaining.js | 3 +-- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/escodegen.js b/escodegen.js index 51ea4c16..71ac2186 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1930,7 +1930,7 @@ MemberExpression: function (expr, precedence, flags) { var result, fragment; - if (!expr.optional && expr.object.type === 'ChainExpression') { + if (expr.object.type === 'ChainExpression' && expr.object.expression.optional) { // F_ALLOW_UNPARATH_NEW becomes false. result = [this.generateExpression(expr.object, Precedence.Member, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; } else { diff --git a/package.json b/package.json index a3c93a1f..dfde536c 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "bluebird": "^3.4.7", "bower-registry-client": "^1.0.0", "chai": "^3.5.0", + "chai-exclude": "^2.0.2", "commonjs-everywhere": "^0.9.7", "gulp": "^3.8.10", "gulp-eslint": "^3.0.1", diff --git a/test/compare-acorn-es2020.js b/test/compare-acorn-es2020.js index af537e99..f5bfd123 100644 --- a/test/compare-acorn-es2020.js +++ b/test/compare-acorn-es2020.js @@ -28,16 +28,16 @@ var fs = require('fs'), acorn = require('acorn'), escodegen = require('./loader'), chai = require('chai'), + chaiExclude = require('chai-exclude'), expect = chai.expect; -function test(code, expected) { - var tree, actual, options, StringObject; +chai.use(chaiExclude); - // alias, so that JSLint does not complain. - StringObject = String; +function test(code, expected) { + var tree, actual, actualTree, options; options = { - ranges: true, + ranges: false, locations: false, ecmaVersion: 11 }; @@ -45,18 +45,18 @@ function test(code, expected) { tree = acorn.parse(code, options); // for UNIX text comment - actual = escodegen.generate(tree).replace(/[\n\r]$/, '') + '\n'; + actual = escodegen.generate(tree); + actualTree = acorn.parse(actual, options); + expect(actual).to.be.equal(expected); + expect(tree).excludingEvery(['start', 'end']).to.deep.equal(actualTree); } function testMin(code, expected) { - var tree, actual, options, StringObject; - - // alias, so that JSLint does not complain. - StringObject = String; + var tree, actual, actualTree, options; options = { - ranges: true, + ranges: false, locations: false, ecmaVersion: 11 }; @@ -68,7 +68,10 @@ function testMin(code, expected) { format: escodegen.FORMAT_MINIFY, raw: false }).replace(/[\n\r]$/, '') + '\n'; + actualTree = acorn.parse(actual, options); + expect(actual).to.be.equal(expected); + expect(tree).excludingEvery(['start', 'end']).to.deep.equal(actualTree); } describe('compare acorn es2020 test', function () { diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 7c237aa3..1387d01f 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -5,18 +5,17 @@ obj?.aaa?.bbb; obj.aaa.bbb; obj.aaa?.bbb; (obj?.aaa).bbb; -obj?.aaa?.bbb; +(obj?.aaa)?.bbb; (obj?.aaa).bbb.ccc.ddd; ((obj?.aaa).bbb?.ccc).ddd; -obj?.aaa?.bbb; +(obj?.aaa)?.bbb; obj[aaa][bbb]; obj[aaa]?.[bbb]; obj?.[aaa][bbb]; obj?.[aaa]?.[bbb]; -obj[aaa][bb]; -obj[aaa]?.[bbb]; +obj[aaa][bbb]; obj[aaa]?.[bbb]; (obj?.[aaa])[bbb]; -obj?.[aaa]?.[bbb]; +(obj?.[aaa])?.[bbb]; obj[aaa][bbb][ccc][ddd]; -((obj?.[aaa])[bbb]?.[ccc])[ddd]; +((obj?.[aaa])[bbb]?.[ccc])[ddd]; \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index ebe3d7f0..94800281 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;obj?.aaa?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;obj?.aaa?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bb];obj[aaa]?.[bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd] +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd] diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index 1ccd7ddd..a55807eb 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -13,8 +13,7 @@ obj[aaa][bbb]; obj[aaa]?.[bbb]; obj?.[aaa][bbb]; obj?.[aaa]?.[bbb]; -(obj[aaa])[bb]; -(obj[aaa])?.[bbb]; +(obj[aaa])[bbb]; (obj[aaa])?.[bbb]; (obj?.[aaa])[bbb]; (obj?.[aaa])?.[bbb]; From c66f83d981fc407af9abd61de88d4aa92632e121 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sat, 13 Jun 2020 00:52:05 +0300 Subject: [PATCH 07/17] Updated Chai to version 4.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dfde536c..11ed564d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "acorn": "^7.3.1", "bluebird": "^3.4.7", "bower-registry-client": "^1.0.0", - "chai": "^3.5.0", + "chai": "^4.2.0", "chai-exclude": "^2.0.2", "commonjs-everywhere": "^0.9.7", "gulp": "^3.8.10", From be2d6c652525b9dde23e9c1c539229b5a4c843b1 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Mon, 20 Jul 2020 13:34:10 +0300 Subject: [PATCH 08/17] Moved expression object flags to the variable --- escodegen.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/escodegen.js b/escodegen.js index 71ac2186..63281e86 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1928,14 +1928,16 @@ }, MemberExpression: function (expr, precedence, flags) { - var result, fragment; + var result, + fragment, + exprObjectFlags = (flags & F_ALLOW_CALL) ? E_TTF : E_TFF; if (expr.object.type === 'ChainExpression' && expr.object.expression.optional) { // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Member, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; + result = [this.generateExpression(expr.object, Precedence.Member, exprObjectFlags)]; } else { // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; + result = [this.generateExpression(expr.object, Precedence.Call, exprObjectFlags)]; } if (expr.computed) { From 201b45102b609a103dbd943e208d66670a13d102 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sat, 1 Aug 2020 11:00:41 +0300 Subject: [PATCH 09/17] Fixed optional CallExpression's. Added tests. --- escodegen.js | 23 +++++++++++++------ .../optional-chaining.expected.js | 12 +++++++++- .../optional-chaining.expected.min.js | 2 +- .../compare-acorn-es2020/optional-chaining.js | 10 ++++++++ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/escodegen.js b/escodegen.js index 63281e86..5b4b62bf 100644 --- a/escodegen.js +++ b/escodegen.js @@ -102,9 +102,8 @@ Call: 16, New: 17, TaggedTemplate: 18, - OptionalMember: 19, - Member: 20, - Primary: 21 + Member: 19, + Primary: 20 }; BinaryPrecedence = { @@ -1871,9 +1870,18 @@ }, CallExpression: function (expr, precedence, flags) { - var result, i, iz; - // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.callee, Precedence.Call, E_TTF)]; + var result, + i, + iz, + exprCalleeFlags = E_TTF; + + if (expr.callee.type === 'ChainExpression' && expr.callee.expression.optional) { + // F_ALLOW_UNPARATH_NEW becomes false. + result = [this.generateExpression(expr.callee, Precedence.Member, exprCalleeFlags)]; + } else { + // F_ALLOW_UNPARATH_NEW becomes false. + result = [this.generateExpression(expr.callee, Precedence.Call, exprCalleeFlags)]; + } if (expr.optional) { result.push('?.'); @@ -1891,6 +1899,7 @@ if (!(flags & F_ALLOW_CALL)) { return ['(', result, ')']; } + return parenthesize(result, Precedence.Call, precedence); }, @@ -1971,7 +1980,7 @@ } if (precedence === Precedence.Member) { - return parenthesize(result, Precedence.OptionalMember, precedence); + return parenthesize(result, Precedence.Call, precedence); } return parenthesize(result, Precedence.Member, precedence); diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 1387d01f..154152f5 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -18,4 +18,14 @@ obj[aaa]?.[bbb]; (obj?.[aaa])[bbb]; (obj?.[aaa])?.[bbb]; obj[aaa][bbb][ccc][ddd]; -((obj?.[aaa])[bbb]?.[ccc])[ddd]; \ No newline at end of file +((obj?.[aaa])[bbb]?.[ccc])[ddd]; +obj()(); +obj()?.(); +obj?.()(); +obj?.()?.(); +obj()(); +obj()?.(); +(obj?.())(); +(obj?.())?.(); +obj()()()(); +((obj?.())()?.())(); \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index 94800281..ff9c70c2 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd] +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())() diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index a55807eb..a3767bd1 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -19,3 +19,13 @@ obj?.[aaa]?.[bbb]; (obj?.[aaa])?.[bbb]; ((obj[aaa])[bbb][ccc])[ddd]; ((obj?.[aaa])[bbb]?.[ccc])[ddd]; +obj()(); +obj()?.(); +obj?.()(); +obj?.()?.(); +(obj())(); +(obj())?.(); +(obj?.())(); +(obj?.())?.(); +((obj())()())(); +((obj?.())()?.())(); From 215c838422ade228f7315b168c8405b1d1dba1e9 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sat, 1 Aug 2020 11:09:55 +0300 Subject: [PATCH 10/17] Fixed optional chaining with literal member expression object --- escodegen.js | 2 +- test/compare-acorn-es2020/optional-chaining.expected.js | 1 + test/compare-acorn-es2020/optional-chaining.expected.min.js | 2 +- test/compare-acorn-es2020/optional-chaining.js | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/escodegen.js b/escodegen.js index 5b4b62bf..4d218e7e 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1958,7 +1958,7 @@ result.push(this.generateExpression(expr.property, Precedence.Sequence, flags & F_ALLOW_CALL ? E_TTT : E_TFT)); result.push(']'); } else { - if (expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { + if (!expr.optional && expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { fragment = toSourceNodeWhenNeeded(result).toString(); // When the following conditions are all true, // 1. No floating point diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 154152f5..0097725c 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -19,6 +19,7 @@ obj[aaa]?.[bbb]; (obj?.[aaa])?.[bbb]; obj[aaa][bbb][ccc][ddd]; ((obj?.[aaa])[bbb]?.[ccc])[ddd]; +1?.a; obj()(); obj()?.(); obj?.()(); diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index ff9c70c2..fb257d6f 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())() +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())() diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index a3767bd1..702ffada 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -19,6 +19,7 @@ obj?.[aaa]?.[bbb]; (obj?.[aaa])?.[bbb]; ((obj[aaa])[bbb][ccc])[ddd]; ((obj?.[aaa])[bbb]?.[ccc])[ddd]; +1?.a; obj()(); obj()?.(); obj?.()(); From 361ffd144977d47eac058dc81b5e051fc56c23fd Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 2 Aug 2020 00:09:25 +0300 Subject: [PATCH 11/17] Fixed mixed optional chaining precedence. Added more tests. --- escodegen.js | 18 +++++++++++++++--- .../optional-chaining.expected.js | 13 ++++++++++++- .../optional-chaining.expected.min.js | 2 +- test/compare-acorn-es2020/optional-chaining.js | 11 +++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/escodegen.js b/escodegen.js index 4d218e7e..af5cbef6 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1875,9 +1875,15 @@ iz, exprCalleeFlags = E_TTF; - if (expr.callee.type === 'ChainExpression' && expr.callee.expression.optional) { + if (expr.callee.type === 'ChainExpression') { // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.callee, Precedence.Member, exprCalleeFlags)]; + result = [this.generateExpression( + expr.callee, + expr.callee.expression.optional + ? Precedence.Member + : Precedence.Primary, + exprCalleeFlags + )]; } else { // F_ALLOW_UNPARATH_NEW becomes false. result = [this.generateExpression(expr.callee, Precedence.Call, exprCalleeFlags)]; @@ -1943,7 +1949,13 @@ if (expr.object.type === 'ChainExpression' && expr.object.expression.optional) { // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Member, exprObjectFlags)]; + result = [this.generateExpression( + expr.object, + expr.object.expression.type === 'MemberExpression' + ? Precedence.Member + : Precedence.Primary, + exprObjectFlags + )]; } else { // F_ALLOW_UNPARATH_NEW becomes false. result = [this.generateExpression(expr.object, Precedence.Call, exprObjectFlags)]; diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 0097725c..1b32cc69 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -29,4 +29,15 @@ obj()?.(); (obj?.())(); (obj?.())?.(); obj()()()(); -((obj?.())()?.())(); \ No newline at end of file +((obj?.())()?.())(); +(a?.b)(); +a?.b(); +a?.b?.(); +(a?.b)?.(); +a?.().b; +(a?.()).b; +a?.b.c(); +(a?.b.c)(); +a.b?.().c; +(a.b?.()).c; +(a.b?.())?.c; \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index fb257d6f..e959e604 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())() +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index 702ffada..34627980 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -30,3 +30,14 @@ obj?.()?.(); (obj?.())?.(); ((obj())()())(); ((obj?.())()?.())(); +(a?.b)(); +a?.b(); +a?.b?.(); +(a?.b)?.(); +a?.().b; +(a?.()).b; +a?.b.c(); +(a?.b.c)(); +a.b?.().c; +(a.b?.()).c; +(a.b?.())?.c; From afb6f7649f50e24a680b0bc856ce0ff63a3bb442 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 2 Aug 2020 00:27:23 +0300 Subject: [PATCH 12/17] Simplified logic of ChainExpression precedence detection --- escodegen.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/escodegen.js b/escodegen.js index af5cbef6..eb3fe631 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1877,13 +1877,7 @@ if (expr.callee.type === 'ChainExpression') { // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression( - expr.callee, - expr.callee.expression.optional - ? Precedence.Member - : Precedence.Primary, - exprCalleeFlags - )]; + result = [this.generateExpression(expr.callee, Precedence.Member, exprCalleeFlags)]; } else { // F_ALLOW_UNPARATH_NEW becomes false. result = [this.generateExpression(expr.callee, Precedence.Call, exprCalleeFlags)]; @@ -1947,15 +1941,9 @@ fragment, exprObjectFlags = (flags & F_ALLOW_CALL) ? E_TTF : E_TFF; - if (expr.object.type === 'ChainExpression' && expr.object.expression.optional) { + if (expr.object.type === 'ChainExpression') { // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression( - expr.object, - expr.object.expression.type === 'MemberExpression' - ? Precedence.Member - : Precedence.Primary, - exprObjectFlags - )]; + result = [this.generateExpression(expr.object, Precedence.Member, exprObjectFlags)]; } else { // F_ALLOW_UNPARATH_NEW becomes false. result = [this.generateExpression(expr.object, Precedence.Call, exprObjectFlags)]; From 339e49dd8313df43657d301c3ded8c4d774972f5 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 2 Aug 2020 09:21:16 +0300 Subject: [PATCH 13/17] Added OptionalChaining precedence and simplified logic --- escodegen.js | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/escodegen.js b/escodegen.js index eb3fe631..e3741f06 100644 --- a/escodegen.js +++ b/escodegen.js @@ -99,11 +99,12 @@ Await: 14, Unary: 14, Postfix: 15, - Call: 16, - New: 17, - TaggedTemplate: 18, - Member: 19, - Primary: 20 + OptionalChaining: 16, + Call: 17, + New: 18, + TaggedTemplate: 19, + Member: 20, + Primary: 21 }; BinaryPrecedence = { @@ -1875,13 +1876,8 @@ iz, exprCalleeFlags = E_TTF; - if (expr.callee.type === 'ChainExpression') { - // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.callee, Precedence.Member, exprCalleeFlags)]; - } else { - // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.callee, Precedence.Call, exprCalleeFlags)]; - } + // F_ALLOW_UNPARATH_NEW becomes false. + result = [this.generateExpression(expr.callee, Precedence.Call, exprCalleeFlags)]; if (expr.optional) { result.push('?.'); @@ -1905,8 +1901,9 @@ ChainExpression: function (expr, precedence, flags) { var expression = expr.expression; + var result = this[expression.type](expression, precedence, flags); - return this[expression.type](expression, precedence, flags); + return parenthesize(result, Precedence.OptionalChaining, precedence); }, NewExpression: function (expr, precedence, flags) { @@ -1941,13 +1938,8 @@ fragment, exprObjectFlags = (flags & F_ALLOW_CALL) ? E_TTF : E_TFF; - if (expr.object.type === 'ChainExpression') { - // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Member, exprObjectFlags)]; - } else { - // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Call, exprObjectFlags)]; - } + // F_ALLOW_UNPARATH_NEW becomes false. + result = [this.generateExpression(expr.object, Precedence.Call, exprObjectFlags)]; if (expr.computed) { if (expr.optional) { @@ -1979,10 +1971,6 @@ result.push(generateIdentifier(expr.property)); } - if (precedence === Precedence.Member) { - return parenthesize(result, Precedence.Call, precedence); - } - return parenthesize(result, Precedence.Member, precedence); }, From 2782a3ed4dea1f352cae8c594473a5ff7c960608 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 2 Aug 2020 09:24:48 +0300 Subject: [PATCH 14/17] Moved flags from variables to the calls --- escodegen.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/escodegen.js b/escodegen.js index e3741f06..db1be0d1 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1871,13 +1871,10 @@ }, CallExpression: function (expr, precedence, flags) { - var result, - i, - iz, - exprCalleeFlags = E_TTF; + var result, i, iz; // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.callee, Precedence.Call, exprCalleeFlags)]; + result = [this.generateExpression(expr.callee, Precedence.Call, E_TTF)]; if (expr.optional) { result.push('?.'); @@ -1934,12 +1931,10 @@ }, MemberExpression: function (expr, precedence, flags) { - var result, - fragment, - exprObjectFlags = (flags & F_ALLOW_CALL) ? E_TTF : E_TFF; + var result, fragment; // F_ALLOW_UNPARATH_NEW becomes false. - result = [this.generateExpression(expr.object, Precedence.Call, exprObjectFlags)]; + result = [this.generateExpression(expr.object, Precedence.Call, (flags & F_ALLOW_CALL) ? E_TTF : E_TFF)]; if (expr.computed) { if (expr.optional) { From f672353afd66c3dcb35ea4dc79e84199316583a6 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Mon, 3 Aug 2020 10:17:07 +0300 Subject: [PATCH 15/17] Added tests with optional chaining as a part of `NewExpression` `callee` --- escodegen.js | 7 +++++-- test/compare-acorn-es2020/optional-chaining.expected.js | 6 +++++- .../compare-acorn-es2020/optional-chaining.expected.min.js | 2 +- test/compare-acorn-es2020/optional-chaining.js | 4 ++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/escodegen.js b/escodegen.js index db1be0d1..847fc370 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1897,8 +1897,11 @@ }, ChainExpression: function (expr, precedence, flags) { - var expression = expr.expression; - var result = this[expression.type](expression, precedence, flags); + if (Precedence.OptionalChaining < precedence) { + flags |= F_ALLOW_CALL; + } + + var result = this.generateExpression(expr.expression, Precedence.OptionalChaining, flags); return parenthesize(result, Precedence.OptionalChaining, precedence); }, diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 1b32cc69..7428f592 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -40,4 +40,8 @@ a?.b.c(); (a?.b.c)(); a.b?.().c; (a.b?.()).c; -(a.b?.())?.c; \ No newline at end of file +(a.b?.())?.c; +new (a?.b().c)(); +new (a?.b())(); +new (a?.b().c)(); +new (a?.b())(); \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index e959e604..cfabe2b8 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c;new(a?.b().c);new(a?.b());new(a?.b().c);new(a?.b()) diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index 34627980..584776c9 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -41,3 +41,7 @@ a?.b.c(); a.b?.().c; (a.b?.()).c; (a.b?.())?.c; +new (a?.b().c); +new (a?.b()); +new (a?.b().c)(); +new (a?.b())(); From 8f05fb08753c49aa1a0cc6e14c593694e3b06db8 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Wed, 5 Aug 2020 23:46:46 +0300 Subject: [PATCH 16/17] Removed redundant trailing parenthesis from NewExpression with ChainExpression callee without arguments --- escodegen.js | 6 ++++++ package.json | 2 +- .../compare-acorn-es2020/optional-chaining.expected.js | 10 ++++++---- .../optional-chaining.expected.min.js | 2 +- test/compare-acorn-es2020/optional-chaining.js | 2 ++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/escodegen.js b/escodegen.js index 847fc370..972ef78d 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1919,6 +1919,12 @@ this.generateExpression(expr.callee, Precedence.New, itemFlags) ); + var isChainExpressionCalleeWithoutArguments = expr.callee.type === Syntax.ChainExpression && !length; + + if (isChainExpressionCalleeWithoutArguments) { + return parenthesize(result, Precedence.New, precedence); + } + if (!(flags & F_ALLOW_UNPARATH_NEW) || parentheses || length > 0) { result.push('('); for (i = 0, iz = length; i < iz; ++i) { diff --git a/package.json b/package.json index 11ed564d..5e5c8e7a 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "url": "http://github.com/estools/escodegen.git" }, "dependencies": { - "estraverse": "^4.2.0", + "estraverse": "^5.2.0", "esutils": "^2.0.2", "esprima": "^4.0.1", "optionator": "^0.8.1" diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 7428f592..0a9ff55a 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -41,7 +41,9 @@ a?.b.c(); a.b?.().c; (a.b?.()).c; (a.b?.())?.c; -new (a?.b().c)(); -new (a?.b())(); -new (a?.b().c)(); -new (a?.b())(); \ No newline at end of file +new (a?.b().c); +new (a?.b()); +new (a?.b().c); +new (a?.b()); +new (a?.b().c)(d, e); +new (a?.b())(d, e); \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index cfabe2b8..b5fa4c6c 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c;new(a?.b().c);new(a?.b());new(a?.b().c);new(a?.b()) +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c;new(a?.b().c);new(a?.b());new(a?.b().c);new(a?.b());new(a?.b().c)(d,e);new(a?.b())(d,e) diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index 584776c9..a24dbc52 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -45,3 +45,5 @@ new (a?.b().c); new (a?.b()); new (a?.b().c)(); new (a?.b())(); +new (a?.b().c)(d, e); +new (a?.b())(d, e); From eb9cbb0bfd1ea745c1a12632f66b31f602573ded Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Thu, 6 Aug 2020 00:15:23 +0300 Subject: [PATCH 17/17] Reverted logic to keep parenthesis of NewExpression with ChainExpression callee without arguments --- escodegen.js | 6 ------ .../compare-acorn-es2020/optional-chaining.expected.js | 10 ++++------ .../optional-chaining.expected.min.js | 2 +- test/compare-acorn-es2020/optional-chaining.js | 2 -- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/escodegen.js b/escodegen.js index 972ef78d..847fc370 100644 --- a/escodegen.js +++ b/escodegen.js @@ -1919,12 +1919,6 @@ this.generateExpression(expr.callee, Precedence.New, itemFlags) ); - var isChainExpressionCalleeWithoutArguments = expr.callee.type === Syntax.ChainExpression && !length; - - if (isChainExpressionCalleeWithoutArguments) { - return parenthesize(result, Precedence.New, precedence); - } - if (!(flags & F_ALLOW_UNPARATH_NEW) || parentheses || length > 0) { result.push('('); for (i = 0, iz = length; i < iz; ++i) { diff --git a/test/compare-acorn-es2020/optional-chaining.expected.js b/test/compare-acorn-es2020/optional-chaining.expected.js index 0a9ff55a..7428f592 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.js @@ -41,9 +41,7 @@ a?.b.c(); a.b?.().c; (a.b?.()).c; (a.b?.())?.c; -new (a?.b().c); -new (a?.b()); -new (a?.b().c); -new (a?.b()); -new (a?.b().c)(d, e); -new (a?.b())(d, e); \ No newline at end of file +new (a?.b().c)(); +new (a?.b())(); +new (a?.b().c)(); +new (a?.b())(); \ No newline at end of file diff --git a/test/compare-acorn-es2020/optional-chaining.expected.min.js b/test/compare-acorn-es2020/optional-chaining.expected.min.js index b5fa4c6c..cfabe2b8 100644 --- a/test/compare-acorn-es2020/optional-chaining.expected.min.js +++ b/test/compare-acorn-es2020/optional-chaining.expected.min.js @@ -1 +1 @@ -obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c;new(a?.b().c);new(a?.b());new(a?.b().c);new(a?.b());new(a?.b().c)(d,e);new(a?.b())(d,e) +obj.aaa.bbb;obj.aaa?.bbb;obj?.aaa.bbb;obj?.aaa?.bbb;obj.aaa.bbb;obj.aaa?.bbb;(obj?.aaa).bbb;(obj?.aaa)?.bbb;(obj?.aaa).bbb.ccc.ddd;((obj?.aaa).bbb?.ccc).ddd;(obj?.aaa)?.bbb;obj[aaa][bbb];obj[aaa]?.[bbb];obj?.[aaa][bbb];obj?.[aaa]?.[bbb];obj[aaa][bbb];obj[aaa]?.[bbb];(obj?.[aaa])[bbb];(obj?.[aaa])?.[bbb];obj[aaa][bbb][ccc][ddd];((obj?.[aaa])[bbb]?.[ccc])[ddd];1?.a;obj()();obj()?.();obj?.()();obj?.()?.();obj()();obj()?.();(obj?.())();(obj?.())?.();obj()()()();((obj?.())()?.())();(a?.b)();a?.b();a?.b?.();(a?.b)?.();a?.().b;(a?.()).b;a?.b.c();(a?.b.c)();a.b?.().c;(a.b?.()).c;(a.b?.())?.c;new(a?.b().c);new(a?.b());new(a?.b().c);new(a?.b()) diff --git a/test/compare-acorn-es2020/optional-chaining.js b/test/compare-acorn-es2020/optional-chaining.js index a24dbc52..584776c9 100644 --- a/test/compare-acorn-es2020/optional-chaining.js +++ b/test/compare-acorn-es2020/optional-chaining.js @@ -45,5 +45,3 @@ new (a?.b().c); new (a?.b()); new (a?.b().c)(); new (a?.b())(); -new (a?.b().c)(d, e); -new (a?.b())(d, e);