From 5946c473f3088913be5b3b0240577e2e8493e11e Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 09:34:00 -0500 Subject: [PATCH 01/10] test: imported function calls --- test/imports-test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/imports-test.ts b/test/imports-test.ts index 3d65480f..6e1b883b 100644 --- a/test/imports-test.ts +++ b/test/imports-test.ts @@ -393,6 +393,48 @@ var _moduleName = require('moduleName'); ); }); + it("never breaks weird call expressions", () => { + assertResult( + ` + import a from 'a'; + import b from 'b'; + a(); + (a)(); + ((a))(); + a(b)(); + (a + b)(); + `, + `"use strict";${IMPORT_DEFAULT_PREFIX} + var _a = require('a'); var _a2 = _interopRequireDefault(_a); + var _b = require('b'); var _b2 = _interopRequireDefault(_b); + (0, _a2.default)(); + ((0, _a2.default))(); + (((0, _a2.default)))(); + (0, _a2.default)((0, _b2.default))(); + (_a2.default + (0, _b2.default))(); + `, + ); + }); + + describe("ASI interop", () => { + it("prevents accidental invocation for imported functions", () => { + assertResult( + ` + import {fun} from 'my-module' + + let arr = [] + fun() + `, + `"use strict"; + var _mymodule = require('my-module'); + + let arr = [] + (0, _mymodule.fun)() + `, + ); + }); + }); + it("uses wildcard name on default access when possible", () => { assertResult( ` From 082cfd977083e40315ebecf1fad57a754730d23e Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 09:55:05 -0500 Subject: [PATCH 02/10] fix: imported function calls --- src/transformers/CJSImportTransformer.ts | 33 ++++++++++-------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/transformers/CJSImportTransformer.ts b/src/transformers/CJSImportTransformer.ts index 4b0c115f..5f1a62c5 100644 --- a/src/transformers/CJSImportTransformer.ts +++ b/src/transformers/CJSImportTransformer.ts @@ -174,27 +174,22 @@ export default class CJSImportTransformer extends Transformer { const replacement = this.importProcessor.getIdentifierReplacement( this.tokens.identifierNameForToken(token), ); - if (!replacement) { - return false; - } - // We need to change to (0, f) if this is a function call, so that it won't be interpreted as a - // method access. This can also happen in situations like (f)(), so a following close-paren - // should trigger this behavior as well if it eventually has an open-paren. In some cases, like - // export assignees, we must NOT turn the identifier into a normal expression, so we need to - // just to the regular replacement. - let possibleOpenParenIndex = this.tokens.currentIndex() + 1; - while ( - possibleOpenParenIndex < this.tokens.tokens.length && - this.tokens.tokens[possibleOpenParenIndex].type === tt.parenR - ) { - possibleOpenParenIndex++; - } - if (this.tokens.tokens[possibleOpenParenIndex].type === tt.parenL) { - this.tokens.replaceToken(`(0, ${replacement})`); - } else { + if (replacement) { this.tokens.replaceToken(replacement); + + // Any number of closing parens is tolerated. + while (!this.tokens.isAtEnd() && this.tokens.currentToken().type === tt.parenR) { + this.rootTransformer.processToken(); + } + + // Avoid treating imported functions as methods of their `exports` object by using + // the `call` method with a falsy context (which is coerced into the global context). + if (!this.tokens.isAtEnd() && this.tokens.currentToken().type === tt.parenL) { + this.tokens.replaceToken(`.call(0, `); + return true; + } } - return true; + return false; } processObjectShorthand(): boolean { From 44e56bccd21fe8e05ac2799242e3b80a2a4d173f Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 16:06:05 -0500 Subject: [PATCH 03/10] fix: imported function calls (take 2) --- src/transformers/CJSImportTransformer.ts | 56 ++++++++++++++++++------ src/transformers/RootTransformer.ts | 6 +-- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/transformers/CJSImportTransformer.ts b/src/transformers/CJSImportTransformer.ts index 5f1a62c5..abeda56e 100644 --- a/src/transformers/CJSImportTransformer.ts +++ b/src/transformers/CJSImportTransformer.ts @@ -174,22 +174,52 @@ export default class CJSImportTransformer extends Transformer { const replacement = this.importProcessor.getIdentifierReplacement( this.tokens.identifierNameForToken(token), ); - if (replacement) { - this.tokens.replaceToken(replacement); - - // Any number of closing parens is tolerated. - while (!this.tokens.isAtEnd() && this.tokens.currentToken().type === tt.parenR) { - this.rootTransformer.processToken(); + if (!replacement) { + return false; + } + let openParenIndex = this.tokens.currentIndex(); + while (++openParenIndex < this.tokens.tokens.length) { + const token = this.tokens.tokens[openParenIndex]; + if (token.type === tt.parenL) { + // Avoid treating imported functions as methods of their `exports` object + // by using `(0, f)` when the identifier is in a paren expression. Else + // use `Function.prototype.call` when the identifier is a guaranteed + // function call. When using `call`, pass undefined as the context. + switch (this.tokens.tokenAtRelativeIndex(1).type) { + case tt.parenL: { + // We can use `(0, f)` when the previous token is an open brace, + // semicolon, or equal operator. This saves space for function calls + // with arguments, because then `void 0` is omitted. + const prevToken = this.tokens.tokenAtRelativeIndex(-1); + if ( + prevToken.type !== tt.braceL && + prevToken.type !== tt.semi && + prevToken.type !== tt.eq + ) { + // NOTE: This line inserts a new parenthesis, which must be accounted + // for by recursively calling the `processBalancedCode` method of the + // root transformer. + const hasArgs = this.tokens.tokenAtRelativeIndex(2).type !== tt.parenR; + this.tokens.replaceToken(`${replacement}.call(` + (hasArgs ? "void 0, " : "")); + this.tokens.removeToken(); // Remove the old paren. + this.rootTransformer.processBalancedCode(0, 1); + return true; + } + } + case tt.parenR: + // See here: http://2ality.com/2015/12/references.html + this.tokens.replaceToken(`(0, ${replacement})`); + return true; + } + break; } - - // Avoid treating imported functions as methods of their `exports` object by using - // the `call` method with a falsy context (which is coerced into the global context). - if (!this.tokens.isAtEnd() && this.tokens.currentToken().type === tt.parenL) { - this.tokens.replaceToken(`.call(0, `); - return true; + // Tolerate any number of closing parens. + if (token.type !== tt.parenR) { + break; } } - return false; + this.tokens.replaceToken(replacement); + return true; } processObjectShorthand(): boolean { diff --git a/src/transformers/RootTransformer.ts b/src/transformers/RootTransformer.ts index 331405c9..76114633 100644 --- a/src/transformers/RootTransformer.ts +++ b/src/transformers/RootTransformer.ts @@ -101,9 +101,9 @@ export default class RootTransformer { } } - processBalancedCode(): void { - let braceDepth = 0; - let parenDepth = 0; + processBalancedCode(initialBraceDepth: number = 0, initialParenDepth: number = 0): void { + let braceDepth = initialBraceDepth; + let parenDepth = initialParenDepth; while (!this.tokens.isAtEnd()) { if (this.tokens.matches1(tt.braceL) || this.tokens.matches1(tt.dollarBraceL)) { braceDepth++; From a38694502fafa747896cb143a86eed88e61c7051 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 16:33:16 -0500 Subject: [PATCH 04/10] test: imported identifiers --- test/imports-test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/imports-test.ts b/test/imports-test.ts index 6e1b883b..3082bd29 100644 --- a/test/imports-test.ts +++ b/test/imports-test.ts @@ -398,7 +398,6 @@ var _moduleName = require('moduleName'); ` import a from 'a'; import b from 'b'; - a(); (a)(); ((a))(); a(b)(); @@ -407,7 +406,6 @@ var _moduleName = require('moduleName'); `"use strict";${IMPORT_DEFAULT_PREFIX} var _a = require('a'); var _a2 = _interopRequireDefault(_a); var _b = require('b'); var _b2 = _interopRequireDefault(_b); - (0, _a2.default)(); ((0, _a2.default))(); (((0, _a2.default)))(); (0, _a2.default)((0, _b2.default))(); @@ -424,12 +422,14 @@ var _moduleName = require('moduleName'); let arr = [] fun() + fun(1, 2) `, `"use strict"; var _mymodule = require('my-module'); let arr = [] - (0, _mymodule.fun)() + _mymodule.fun.call() + _mymodule.fun.call(void 0, 1, 2) `, ); }); From 70e141c39fc1908bd5625962748643c9d0948d34 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 19:01:14 -0500 Subject: [PATCH 05/10] fix: imported function calls (take 3) --- src/transformers/CJSImportTransformer.ts | 64 +++++++++--------------- src/transformers/RootTransformer.ts | 6 +-- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/src/transformers/CJSImportTransformer.ts b/src/transformers/CJSImportTransformer.ts index abeda56e..2a61920e 100644 --- a/src/transformers/CJSImportTransformer.ts +++ b/src/transformers/CJSImportTransformer.ts @@ -177,46 +177,32 @@ export default class CJSImportTransformer extends Transformer { if (!replacement) { return false; } - let openParenIndex = this.tokens.currentIndex(); - while (++openParenIndex < this.tokens.tokens.length) { - const token = this.tokens.tokens[openParenIndex]; - if (token.type === tt.parenL) { - // Avoid treating imported functions as methods of their `exports` object - // by using `(0, f)` when the identifier is in a paren expression. Else - // use `Function.prototype.call` when the identifier is a guaranteed - // function call. When using `call`, pass undefined as the context. - switch (this.tokens.tokenAtRelativeIndex(1).type) { - case tt.parenL: { - // We can use `(0, f)` when the previous token is an open brace, - // semicolon, or equal operator. This saves space for function calls - // with arguments, because then `void 0` is omitted. - const prevToken = this.tokens.tokenAtRelativeIndex(-1); - if ( - prevToken.type !== tt.braceL && - prevToken.type !== tt.semi && - prevToken.type !== tt.eq - ) { - // NOTE: This line inserts a new parenthesis, which must be accounted - // for by recursively calling the `processBalancedCode` method of the - // root transformer. - const hasArgs = this.tokens.tokenAtRelativeIndex(2).type !== tt.parenR; - this.tokens.replaceToken(`${replacement}.call(` + (hasArgs ? "void 0, " : "")); - this.tokens.removeToken(); // Remove the old paren. - this.rootTransformer.processBalancedCode(0, 1); - return true; - } - } - case tt.parenR: - // See here: http://2ality.com/2015/12/references.html - this.tokens.replaceToken(`(0, ${replacement})`); - return true; - } - break; - } - // Tolerate any number of closing parens. - if (token.type !== tt.parenR) { - break; + // Tolerate any number of closing parens while looking for an opening paren + // that indicates a function call. + let possibleOpenParenIndex = this.tokens.currentIndex() + 1; + while ( + possibleOpenParenIndex < this.tokens.tokens.length && + this.tokens.tokens[possibleOpenParenIndex].type === tt.parenR + ) { + possibleOpenParenIndex++; + } + // Avoid treating imported functions as methods of their `exports` object + // by using `(0, f)` when the identifier is in a paren expression. Else + // use `Function.prototype.call` when the identifier is a guaranteed + // function call. When using `call`, pass undefined as the context. + if (this.tokens.tokens[possibleOpenParenIndex].type === tt.parenL) { + if (this.tokens.tokenAtRelativeIndex(1).type == tt.parenL) { + this.tokens.replaceToken(`${replacement}.call(void 0, `); + // Remove the old paren. + this.tokens.removeToken(); + // Balance out the new paren. + this.rootTransformer.processBalancedCode(); + this.tokens.copyExpectedToken(tt.parenR); + return true; } + // See here: http://2ality.com/2015/12/references.html + this.tokens.replaceToken(`(0, ${replacement})`); + return true; } this.tokens.replaceToken(replacement); return true; diff --git a/src/transformers/RootTransformer.ts b/src/transformers/RootTransformer.ts index 76114633..331405c9 100644 --- a/src/transformers/RootTransformer.ts +++ b/src/transformers/RootTransformer.ts @@ -101,9 +101,9 @@ export default class RootTransformer { } } - processBalancedCode(initialBraceDepth: number = 0, initialParenDepth: number = 0): void { - let braceDepth = initialBraceDepth; - let parenDepth = initialParenDepth; + processBalancedCode(): void { + let braceDepth = 0; + let parenDepth = 0; while (!this.tokens.isAtEnd()) { if (this.tokens.matches1(tt.braceL) || this.tokens.matches1(tt.dollarBraceL)) { braceDepth++; From cfe72f0a92283d8d402d6bd52a9cf920494e26de Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 19:39:01 -0500 Subject: [PATCH 06/10] test: imported function call --- test/imports-test.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/imports-test.ts b/test/imports-test.ts index 3082bd29..c5ded4d4 100644 --- a/test/imports-test.ts +++ b/test/imports-test.ts @@ -388,7 +388,7 @@ var _moduleName = require('moduleName'); `"use strict"; var _mymodule = require('my-module'); - (0, _mymodule.bar)(); + _mymodule.bar.call(void 0, ); `, ); }); @@ -408,7 +408,7 @@ var _moduleName = require('moduleName'); var _b = require('b'); var _b2 = _interopRequireDefault(_b); ((0, _a2.default))(); (((0, _a2.default)))(); - (0, _a2.default)((0, _b2.default))(); + _a2.default.call(void 0, ((0, _b2.default))(); (_a2.default + (0, _b2.default))(); `, ); @@ -428,7 +428,7 @@ var _moduleName = require('moduleName'); var _mymodule = require('my-module'); let arr = [] - _mymodule.fun.call() + _mymodule.fun.call(void 0, ) _mymodule.fun.call(void 0, 1, 2) `, ); @@ -495,7 +495,7 @@ var _moduleName = require('moduleName'); class A { constructor() { - this.val = (0, _foo2.default)(); + this.val = _foo2.default.call(void 0, ); } } `, @@ -749,13 +749,13 @@ module.exports = exports.default; const o = { f() { - (0, _foo.foo)(3); + _foo.foo.call(void 0, 3); } } class C { g() { - (0, _foo.foo)(4); + _foo.foo.call(void 0, 4); } } `, @@ -808,7 +808,7 @@ module.exports = exports.default; return \`interpolated \${value}\`; } m2(id) { - (0, _things.foo)(); + _things.foo.call(void 0, ); } } `, @@ -843,7 +843,7 @@ module.exports = exports.default; var _a = require('a'); { - (0, _a.a)(); + _a.a.call(void 0, ); } `, ); @@ -865,7 +865,7 @@ module.exports = exports.default; switch (foo) { case 1: { - (0, _a.a)(); + _a.a.call(void 0, ); } } `, @@ -888,7 +888,7 @@ module.exports = exports.default; switch (foo) { default: { - (0, _a.a)(); + _a.a.call(void 0, ); } } `, From 55b80ac25e19ac1db3c96fc56ced667a9d724310 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 19:53:47 -0500 Subject: [PATCH 07/10] tidy up --- src/transformers/CJSImportTransformer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/transformers/CJSImportTransformer.ts b/src/transformers/CJSImportTransformer.ts index 2a61920e..9824b8e3 100644 --- a/src/transformers/CJSImportTransformer.ts +++ b/src/transformers/CJSImportTransformer.ts @@ -198,13 +198,13 @@ export default class CJSImportTransformer extends Transformer { // Balance out the new paren. this.rootTransformer.processBalancedCode(); this.tokens.copyExpectedToken(tt.parenR); - return true; + } else { + // See here: http://2ality.com/2015/12/references.html + this.tokens.replaceToken(`(0, ${replacement})`); } - // See here: http://2ality.com/2015/12/references.html - this.tokens.replaceToken(`(0, ${replacement})`); - return true; + } else { + this.tokens.replaceToken(replacement); } - this.tokens.replaceToken(replacement); return true; } From 96a20b20926946a601b03971aed22134b77d1103 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 20:10:29 -0500 Subject: [PATCH 08/10] fix: imported function calls w/ new expressions --- src/transformers/CJSImportTransformer.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/transformers/CJSImportTransformer.ts b/src/transformers/CJSImportTransformer.ts index 9824b8e3..144ef31c 100644 --- a/src/transformers/CJSImportTransformer.ts +++ b/src/transformers/CJSImportTransformer.ts @@ -191,7 +191,10 @@ export default class CJSImportTransformer extends Transformer { // use `Function.prototype.call` when the identifier is a guaranteed // function call. When using `call`, pass undefined as the context. if (this.tokens.tokens[possibleOpenParenIndex].type === tt.parenL) { - if (this.tokens.tokenAtRelativeIndex(1).type == tt.parenL) { + if ( + this.tokens.tokenAtRelativeIndex(1).type === tt.parenL && + this.tokens.tokenAtRelativeIndex(-1).type !== tt._new + ) { this.tokens.replaceToken(`${replacement}.call(void 0, `); // Remove the old paren. this.tokens.removeToken(); From e8abaef43d86aca313f9cdd55cca04748759bc68 Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 20:17:57 -0500 Subject: [PATCH 09/10] test: imported function call --- test/imports-test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/imports-test.ts b/test/imports-test.ts index c5ded4d4..3d24dd73 100644 --- a/test/imports-test.ts +++ b/test/imports-test.ts @@ -402,14 +402,16 @@ var _moduleName = require('moduleName'); ((a))(); a(b)(); (a + b)(); + new (a)(); `, `"use strict";${IMPORT_DEFAULT_PREFIX} var _a = require('a'); var _a2 = _interopRequireDefault(_a); var _b = require('b'); var _b2 = _interopRequireDefault(_b); ((0, _a2.default))(); (((0, _a2.default)))(); - _a2.default.call(void 0, ((0, _b2.default))(); + _a2.default.call(void 0, (0, _b2.default))(); (_a2.default + (0, _b2.default))(); + new ((0, _a2.default))(); `, ); }); From 049f9a1dfa0c840b1e6dc0856e6857244a15da0b Mon Sep 17 00:00:00 2001 From: aleclarson Date: Sun, 18 Nov 2018 20:45:47 -0500 Subject: [PATCH 10/10] fix: parseNew --- benchmark/sample/expression.ts | 2 +- src/parser/traverser/expression.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark/sample/expression.ts b/benchmark/sample/expression.ts index 32cb0bdb..568eb864 100644 --- a/benchmark/sample/expression.ts +++ b/benchmark/sample/expression.ts @@ -633,7 +633,7 @@ function parseParenItem(): void { // argument to parseSubscripts to prevent it from consuming the // argument list. function parseNew(): void { - parseIdentifier(); + expect(tt._new); if (eat(tt.dot)) { // new.target parseMetaProperty(); diff --git a/src/parser/traverser/expression.ts b/src/parser/traverser/expression.ts index 1c3d430b..f067cdea 100644 --- a/src/parser/traverser/expression.ts +++ b/src/parser/traverser/expression.ts @@ -652,7 +652,7 @@ function parseParenItem(): void { // argument to parseSubscripts to prevent it from consuming the // argument list. function parseNew(): void { - parseIdentifier(); + expect(tt._new); if (eat(tt.dot)) { // new.target parseMetaProperty();