Fix 'this' capturing for dynamic import

This commit is contained in:
Ron Buckton 2017-10-09 17:15:13 -07:00
parent 1db762356e
commit dc607c29b4
10 changed files with 292 additions and 23 deletions

View file

@ -2699,6 +2699,12 @@ namespace ts {
if (expression.kind === SyntaxKind.ImportKeyword) {
transformFlags |= TransformFlags.ContainsDynamicImport;
// A dynamic 'import()' call that contains a lexical 'this' will
// require a captured 'this' when emitting down-level.
if (subtreeFlags & TransformFlags.ContainsLexicalThis) {
transformFlags |= TransformFlags.ContainsCapturedLexicalThis;
}
}
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;

View file

@ -561,46 +561,89 @@ namespace ts {
// });
const resolve = createUniqueName("resolve");
const reject = createUniqueName("reject");
return createNew(
createIdentifier("Promise"),
/*typeArguments*/ undefined,
[createFunctionExpression(
const parameters = [
createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ resolve),
createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ reject)
];
const body = createBlock([
createStatement(
createCall(
createIdentifier("require"),
/*typeArguments*/ undefined,
[createArrayLiteral([firstOrUndefined(node.arguments) || createOmittedExpression()]), resolve, reject]
)
)
]);
let func: FunctionExpression | ArrowFunction;
if (languageVersion >= ScriptTarget.ES2015) {
func = createArrowFunction(
/*modifiers*/ undefined,
/*typeParameters*/ undefined,
parameters,
/*type*/ undefined,
/*equalsGreaterThanToken*/ undefined,
body);
}
else {
func = createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
[createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ resolve),
createParameter(/*decorator*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, /*name*/ reject)],
parameters,
/*type*/ undefined,
createBlock([createStatement(
createCall(
createIdentifier("require"),
/*typeArguments*/ undefined,
[createArrayLiteral([firstOrUndefined(node.arguments) || createOmittedExpression()]), resolve, reject]
))])
)]);
body);
// if there is a lexical 'this' in the import call arguments, ensure we indicate
// that this new function expression indicates it captures 'this' so that the
// es2015 transformer will properly substitute 'this' with '_this'.
if (node.transformFlags & TransformFlags.ContainsLexicalThis) {
setEmitFlags(func, EmitFlags.CapturesThis);
}
}
return createNew(createIdentifier("Promise"), /*typeArguments*/ undefined, [func]);
}
function transformImportCallExpressionCommonJS(node: ImportCall): Expression {
function transformImportCallExpressionCommonJS(node: ImportCall): Expression {
// import("./blah")
// emit as
// Promise.resolve().then(function () { return require(x); }) /*CommonJs Require*/
// We have to wrap require in then callback so that require is done in asynchronously
// if we simply do require in resolve callback in Promise constructor. We will execute the loading immediately
return createCall(
createPropertyAccess(
createCall(createPropertyAccess(createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []),
"then"),
/*typeArguments*/ undefined,
[createFunctionExpression(
const promiseResolveCall = createCall(createPropertyAccess(createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []);
const requireCall = createCall(createIdentifier("require"), /*typeArguments*/ undefined, node.arguments);
let func: FunctionExpression | ArrowFunction;
if (languageVersion >= ScriptTarget.ES2015) {
func = createArrowFunction(
/*modifiers*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
/*equalsGreaterThanToken*/ undefined,
requireCall);
}
else {
func = createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
createBlock([createReturn(createCall(createIdentifier("require"), /*typeArguments*/ undefined, node.arguments))])
)]);
createBlock([createReturn(requireCall)]));
// if there is a lexical 'this' in the import call arguments, ensure we indicate
// that this new function expression indicates it captures 'this' so that the
// es2015 transformer will properly substitute 'this' with '_this'.
if (node.transformFlags & TransformFlags.ContainsLexicalThis) {
setEmitFlags(func, EmitFlags.CapturesThis);
}
}
return createCall(createPropertyAccess(promiseResolveCall, "then"), /*typeArguments*/ undefined, [func]);
}
/**

View file

@ -0,0 +1,37 @@
//// [dynamicImportWithNestedThis_es2015.ts]
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
private _path = './other';
dynamic() {
return import(this._path);
}
}
const c = new C();
c.dynamic();
//// [dynamicImportWithNestedThis_es2015.js]
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
var __syncRequire = typeof module === "object" && typeof module.exports === "object";
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
constructor() {
this._path = './other';
}
dynamic() {
return __syncRequire ? Promise.resolve().then(() => require(this._path)) : new Promise((resolve_1, reject_1) => { require([this._path], resolve_1, reject_1); });
}
}
const c = new C();
c.dynamic();
});

View file

@ -0,0 +1,27 @@
=== tests/cases/compiler/dynamicImportWithNestedThis_es2015.ts ===
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
>C : Symbol(C, Decl(dynamicImportWithNestedThis_es2015.ts, 0, 0))
private _path = './other';
>_path : Symbol(C._path, Decl(dynamicImportWithNestedThis_es2015.ts, 1, 9))
dynamic() {
>dynamic : Symbol(C.dynamic, Decl(dynamicImportWithNestedThis_es2015.ts, 2, 27))
return import(this._path);
>this._path : Symbol(C._path, Decl(dynamicImportWithNestedThis_es2015.ts, 1, 9))
>this : Symbol(C, Decl(dynamicImportWithNestedThis_es2015.ts, 0, 0))
>_path : Symbol(C._path, Decl(dynamicImportWithNestedThis_es2015.ts, 1, 9))
}
}
const c = new C();
>c : Symbol(c, Decl(dynamicImportWithNestedThis_es2015.ts, 9, 5))
>C : Symbol(C, Decl(dynamicImportWithNestedThis_es2015.ts, 0, 0))
c.dynamic();
>c.dynamic : Symbol(C.dynamic, Decl(dynamicImportWithNestedThis_es2015.ts, 2, 27))
>c : Symbol(c, Decl(dynamicImportWithNestedThis_es2015.ts, 9, 5))
>dynamic : Symbol(C.dynamic, Decl(dynamicImportWithNestedThis_es2015.ts, 2, 27))

View file

@ -0,0 +1,31 @@
=== tests/cases/compiler/dynamicImportWithNestedThis_es2015.ts ===
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
>C : C
private _path = './other';
>_path : string
>'./other' : "./other"
dynamic() {
>dynamic : () => Promise<any>
return import(this._path);
>import(this._path) : Promise<any>
>this._path : string
>this : this
>_path : string
}
}
const c = new C();
>c : C
>new C() : C
>C : typeof C
c.dynamic();
>c.dynamic() : Promise<any>
>c.dynamic : () => Promise<any>
>c : C
>dynamic : () => Promise<any>

View file

@ -0,0 +1,39 @@
//// [dynamicImportWithNestedThis_es5.ts]
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
private _path = './other';
dynamic() {
return import(this._path);
}
}
const c = new C();
c.dynamic();
//// [dynamicImportWithNestedThis_es5.js]
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
var __syncRequire = typeof module === "object" && typeof module.exports === "object";
// https://github.com/Microsoft/TypeScript/issues/17564
var C = /** @class */ (function () {
function C() {
this._path = './other';
}
C.prototype.dynamic = function () {
var _this = this;
return __syncRequire ? Promise.resolve().then(function () { return require(_this._path); }) : new Promise(function (resolve_1, reject_1) { require([_this._path], resolve_1, reject_1); });
};
return C;
}());
var c = new C();
c.dynamic();
});

View file

@ -0,0 +1,27 @@
=== tests/cases/compiler/dynamicImportWithNestedThis_es5.ts ===
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
>C : Symbol(C, Decl(dynamicImportWithNestedThis_es5.ts, 0, 0))
private _path = './other';
>_path : Symbol(C._path, Decl(dynamicImportWithNestedThis_es5.ts, 1, 9))
dynamic() {
>dynamic : Symbol(C.dynamic, Decl(dynamicImportWithNestedThis_es5.ts, 2, 27))
return import(this._path);
>this._path : Symbol(C._path, Decl(dynamicImportWithNestedThis_es5.ts, 1, 9))
>this : Symbol(C, Decl(dynamicImportWithNestedThis_es5.ts, 0, 0))
>_path : Symbol(C._path, Decl(dynamicImportWithNestedThis_es5.ts, 1, 9))
}
}
const c = new C();
>c : Symbol(c, Decl(dynamicImportWithNestedThis_es5.ts, 9, 5))
>C : Symbol(C, Decl(dynamicImportWithNestedThis_es5.ts, 0, 0))
c.dynamic();
>c.dynamic : Symbol(C.dynamic, Decl(dynamicImportWithNestedThis_es5.ts, 2, 27))
>c : Symbol(c, Decl(dynamicImportWithNestedThis_es5.ts, 9, 5))
>dynamic : Symbol(C.dynamic, Decl(dynamicImportWithNestedThis_es5.ts, 2, 27))

View file

@ -0,0 +1,31 @@
=== tests/cases/compiler/dynamicImportWithNestedThis_es5.ts ===
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
>C : C
private _path = './other';
>_path : string
>'./other' : "./other"
dynamic() {
>dynamic : () => Promise<any>
return import(this._path);
>import(this._path) : Promise<any>
>this._path : string
>this : this
>_path : string
}
}
const c = new C();
>c : C
>new C() : C
>C : typeof C
c.dynamic();
>c.dynamic() : Promise<any>
>c.dynamic : () => Promise<any>
>c : C
>dynamic : () => Promise<any>

View file

@ -0,0 +1,14 @@
// @lib: es2015
// @target: es2015
// @module: umd
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
private _path = './other';
dynamic() {
return import(this._path);
}
}
const c = new C();
c.dynamic();

View file

@ -0,0 +1,14 @@
// @lib: es2015
// @target: es5
// @module: umd
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
private _path = './other';
dynamic() {
return import(this._path);
}
}
const c = new C();
c.dynamic();