From c62cb40b9554f85561aabc3b04b7c1fddbb4567d Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 29 Oct 2019 10:51:22 -0700 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20recognize=20non-top-level=20mod?= =?UTF-8?q?ule.exports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compiler/binder.ts | 5 +- src/compiler/utilities.ts | 16 +++- .../reference/nestedModuleExports.errors.txt | 36 ++++++++ .../reference/nestedModuleExports.symbols | 50 +++++++++++ .../reference/nestedModuleExports.types | 84 +++++++++++++++++++ .../conformance/jsdoc/nestedModuleExports.ts | 25 ++++++ 6 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/nestedModuleExports.errors.txt create mode 100644 tests/baselines/reference/nestedModuleExports.symbols create mode 100644 tests/baselines/reference/nestedModuleExports.types create mode 100644 tests/cases/conformance/jsdoc/nestedModuleExports.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1cc293266605b..9166c607cc969 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2365,7 +2365,10 @@ namespace ts { } break; case SyntaxKind.BinaryExpression: - const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); + const binaryExpression = node as BinaryExpression; + binaryExpression.left.parent = binaryExpression; + binaryExpression.right.parent = binaryExpression; + const specialKind = getAssignmentDeclarationKind(binaryExpression); switch (specialKind) { case AssignmentDeclarationKind.ExportsProperty: bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4bff443754b3d..f571bad82230f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2044,6 +2044,15 @@ namespace ts { && getElementOrPropertyAccessName(node) === "exports"; } + export function isTopLevelInSourceFileOrIIFE(node: Node): boolean { + const container = getEnclosingBlockScopeContainer(node); + if (container.kind === SyntaxKind.SourceFile) { + return true; + } + const iife = getImmediatelyInvokedFunctionExpression(container); + return !!iife && isTopLevelInSourceFileOrIIFE(iife); + } + /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property /// assignments we treat as special in the binder export function getAssignmentDeclarationKind(expr: BinaryExpression | CallExpression): AssignmentDeclarationKind { @@ -2104,7 +2113,7 @@ namespace ts { return AssignmentDeclarationKind.None; } const entityName = expr.arguments[0]; - if (isExportsIdentifier(entityName) || isModuleExportsAccessExpression(entityName)) { + if ((isExportsIdentifier(entityName) || isModuleExportsAccessExpression(entityName)) && isTopLevelInSourceFileOrIIFE(entityName)) { return AssignmentDeclarationKind.ObjectDefinePropertyExports; } if (isBindableStaticAccessExpression(entityName) && getElementOrPropertyAccessName(entityName) === "prototype") { @@ -2161,7 +2170,7 @@ namespace ts { if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return AssignmentDeclarationKind.ThisProperty; } - else if (isModuleExportsAccessExpression(lhs)) { + else if (isModuleExportsAccessExpression(lhs) && isTopLevelInSourceFileOrIIFE(lhs)) { // module.exports = expr return AssignmentDeclarationKind.ModuleExports; } @@ -2179,7 +2188,8 @@ namespace ts { if ((id.escapedText === "exports" || id.escapedText === "module" && getElementOrPropertyAccessName(nextToLast) === "exports") && // ExportsProperty does not support binding with computed names - isBindableStaticAccessExpression(lhs)) { + isBindableStaticAccessExpression(lhs) && + isTopLevelInSourceFileOrIIFE(lhs)) { // exports.name = expr OR module.exports.name = expr OR exports["name"] = expr ... return AssignmentDeclarationKind.ExportsProperty; } diff --git a/tests/baselines/reference/nestedModuleExports.errors.txt b/tests/baselines/reference/nestedModuleExports.errors.txt new file mode 100644 index 0000000000000..6f0438000d641 --- /dev/null +++ b/tests/baselines/reference/nestedModuleExports.errors.txt @@ -0,0 +1,36 @@ +tests/cases/conformance/jsdoc/not-iife.js(5,9): error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i @types/node`. +tests/cases/conformance/jsdoc/not-iife.js(6,9): error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i @types/node`. +tests/cases/conformance/jsdoc/not-iife.js(8,9): error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i @types/node`. +tests/cases/conformance/jsdoc/test.js(2,1): error TS2304: Cannot find name 'ShouldNotBeGlobal'. + + +==== tests/cases/conformance/jsdoc/not-iife.js (3 errors) ==== + var ShouldBeGlobal = 0; + function f(type, ctor, exports) { + if (typeof exports !== "undefined") { + exports.a = ctor; + module.exports.b = ctor; + ~~~~~~ +!!! error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i @types/node`. + module.exports = { ctor }; + ~~~~~~ +!!! error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i @types/node`. + exports = { ctor }; + module.exports["c"] = type; + ~~~~~~ +!!! error TS2580: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i @types/node`. + } + } + +==== tests/cases/conformance/jsdoc/iife.js (0 errors) ==== + var ShouldNotBeGlobal = 0; + (function () { + exports.a = "b"; + })(); + +==== tests/cases/conformance/jsdoc/test.js (1 errors) ==== + ShouldBeGlobal; + ShouldNotBeGlobal; + ~~~~~~~~~~~~~~~~~ +!!! error TS2304: Cannot find name 'ShouldNotBeGlobal'. + \ No newline at end of file diff --git a/tests/baselines/reference/nestedModuleExports.symbols b/tests/baselines/reference/nestedModuleExports.symbols new file mode 100644 index 0000000000000..4d798ad58091c --- /dev/null +++ b/tests/baselines/reference/nestedModuleExports.symbols @@ -0,0 +1,50 @@ +=== tests/cases/conformance/jsdoc/not-iife.js === +var ShouldBeGlobal = 0; +>ShouldBeGlobal : Symbol(ShouldBeGlobal, Decl(not-iife.js, 0, 3)) + +function f(type, ctor, exports) { +>f : Symbol(f, Decl(not-iife.js, 0, 23)) +>type : Symbol(type, Decl(not-iife.js, 1, 11)) +>ctor : Symbol(ctor, Decl(not-iife.js, 1, 16)) +>exports : Symbol(exports, Decl(not-iife.js, 1, 22)) + + if (typeof exports !== "undefined") { +>exports : Symbol(exports, Decl(not-iife.js, 1, 22)) + + exports.a = ctor; +>exports : Symbol(exports, Decl(not-iife.js, 1, 22)) +>ctor : Symbol(ctor, Decl(not-iife.js, 1, 16)) + + module.exports.b = ctor; +>ctor : Symbol(ctor, Decl(not-iife.js, 1, 16)) + + module.exports = { ctor }; +>ctor : Symbol(ctor, Decl(not-iife.js, 5, 26)) + + exports = { ctor }; +>exports : Symbol(exports, Decl(not-iife.js, 1, 22)) +>ctor : Symbol(ctor, Decl(not-iife.js, 6, 19)) + + module.exports["c"] = type; +>type : Symbol(type, Decl(not-iife.js, 1, 11)) + } +} + +=== tests/cases/conformance/jsdoc/iife.js === +var ShouldNotBeGlobal = 0; +>ShouldNotBeGlobal : Symbol(ShouldNotBeGlobal, Decl(iife.js, 0, 3)) + +(function () { + exports.a = "b"; +>exports.a : Symbol(a, Decl(iife.js, 1, 14)) +>exports : Symbol(a, Decl(iife.js, 1, 14)) +>a : Symbol(a, Decl(iife.js, 1, 14)) + +})(); + +=== tests/cases/conformance/jsdoc/test.js === +ShouldBeGlobal; +>ShouldBeGlobal : Symbol(ShouldBeGlobal, Decl(not-iife.js, 0, 3)) + +ShouldNotBeGlobal; + diff --git a/tests/baselines/reference/nestedModuleExports.types b/tests/baselines/reference/nestedModuleExports.types new file mode 100644 index 0000000000000..c6450c158dd7c --- /dev/null +++ b/tests/baselines/reference/nestedModuleExports.types @@ -0,0 +1,84 @@ +=== tests/cases/conformance/jsdoc/not-iife.js === +var ShouldBeGlobal = 0; +>ShouldBeGlobal : number +>0 : 0 + +function f(type, ctor, exports) { +>f : (type: any, ctor: any, exports: any) => void +>type : any +>ctor : any +>exports : any + + if (typeof exports !== "undefined") { +>typeof exports !== "undefined" : boolean +>typeof exports : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>exports : any +>"undefined" : "undefined" + + exports.a = ctor; +>exports.a = ctor : any +>exports.a : any +>exports : any +>a : any +>ctor : any + + module.exports.b = ctor; +>module.exports.b = ctor : any +>module.exports.b : any +>module.exports : any +>module : any +>exports : any +>b : any +>ctor : any + + module.exports = { ctor }; +>module.exports = { ctor } : { ctor: any; } +>module.exports : any +>module : any +>exports : any +>{ ctor } : { ctor: any; } +>ctor : any + + exports = { ctor }; +>exports = { ctor } : { ctor: any; } +>exports : any +>{ ctor } : { ctor: any; } +>ctor : any + + module.exports["c"] = type; +>module.exports["c"] = type : any +>module.exports["c"] : any +>module.exports : any +>module : any +>exports : any +>"c" : "c" +>type : any + } +} + +=== tests/cases/conformance/jsdoc/iife.js === +var ShouldNotBeGlobal = 0; +>ShouldNotBeGlobal : number +>0 : 0 + +(function () { +>(function () { exports.a = "b";})() : void +>(function () { exports.a = "b";}) : () => void +>function () { exports.a = "b";} : () => void + + exports.a = "b"; +>exports.a = "b" : "b" +>exports.a : string +>exports : typeof import("tests/cases/conformance/jsdoc/iife") +>a : string +>"b" : "b" + +})(); + +=== tests/cases/conformance/jsdoc/test.js === +ShouldBeGlobal; +>ShouldBeGlobal : number + +ShouldNotBeGlobal; +>ShouldNotBeGlobal : any + diff --git a/tests/cases/conformance/jsdoc/nestedModuleExports.ts b/tests/cases/conformance/jsdoc/nestedModuleExports.ts new file mode 100644 index 0000000000000..02522397288a9 --- /dev/null +++ b/tests/cases/conformance/jsdoc/nestedModuleExports.ts @@ -0,0 +1,25 @@ +// @noEmit: true +// @checkJs: true +// @allowJs: true + +// @Filename: not-iife.js +var ShouldBeGlobal = 0; +function f(type, ctor, exports) { + if (typeof exports !== "undefined") { + exports.a = ctor; + module.exports.b = ctor; + module.exports = { ctor }; + exports = { ctor }; + module.exports["c"] = type; + } +} + +// @Filename: iife.js +var ShouldNotBeGlobal = 0; +(function () { + exports.a = "b"; +})(); + +// @Filename: test.js +ShouldBeGlobal; +ShouldNotBeGlobal;