Skip to content

Commit 905f9a0

Browse files
authored
module.exports = Entity is an alias, just like export = Entity (#23570)
* Make `module.export =` an alias like `export=` is This breaks a couple of tests for previous workarounds. Fix in upcoming commits. * Basically fixes all the breaks, but needs cleanup * More notes to myself * Clean up TODOs * Call mergeSymbolTable and delete export= afterward instead of basically copying the code myself. * More cleanup * Remove unnecessary check in import type checking * Revert to DIY code. It is more correct and will go away in a few days. * Exported class expressions can be used as type In both JS and TS * Do not require named class expressions
1 parent ef8af93 commit 905f9a0

18 files changed

+402
-27
lines changed

src/compiler/binder.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2220,14 +2220,14 @@ namespace ts {
22202220
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String);
22212221
}
22222222

2223-
function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
2223+
function bindExportAssignment(node: ExportAssignment) {
22242224
if (!container.symbol || !container.symbol.exports) {
22252225
// Export assignment in some sort of block construct
22262226
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
22272227
}
22282228
else {
22292229
const flags = node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node)
2230-
// An export default clause with an EntityNameExpression exports all meanings of that identifier
2230+
// An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression;
22312231
? SymbolFlags.Alias
22322232
// An export default clause with any other expression exports a value
22332233
: SymbolFlags.Property;
@@ -2322,7 +2322,10 @@ namespace ts {
23222322

23232323
// 'module.exports = expr' assignment
23242324
setCommonJsModuleIndicator(node);
2325-
declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule, SymbolFlags.None);
2325+
const flags = exportAssignmentIsAlias(node)
2326+
? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class
2327+
: SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule;
2328+
declareSymbol(file.symbol.exports, file.symbol, node, flags, SymbolFlags.None);
23262329
}
23272330

23282331
function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {

src/compiler/checker.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,13 +1926,17 @@ namespace ts {
19261926
resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias);
19271927
}
19281928

1929-
function getTargetOfExportAssignment(node: ExportAssignment, dontResolveAlias: boolean): Symbol | undefined {
1930-
const aliasLike = resolveEntityName(<EntityNameExpression>node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias);
1929+
function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined {
1930+
const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression;
1931+
if (isClassExpression(expression)) {
1932+
return checkExpression(expression).symbol;
1933+
}
1934+
const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias);
19311935
if (aliasLike) {
19321936
return aliasLike;
19331937
}
1934-
checkExpression(node.expression);
1935-
return getNodeLinks(node.expression).resolvedSymbol;
1938+
checkExpression(expression);
1939+
return getNodeLinks(expression).resolvedSymbol;
19361940
}
19371941

19381942
function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve?: boolean): Symbol | undefined {
@@ -1948,7 +1952,8 @@ namespace ts {
19481952
case SyntaxKind.ExportSpecifier:
19491953
return getTargetOfExportSpecifier(<ExportSpecifier>node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve);
19501954
case SyntaxKind.ExportAssignment:
1951-
return getTargetOfExportAssignment(<ExportAssignment>node, dontRecursivelyResolve);
1955+
case SyntaxKind.BinaryExpression:
1956+
return getTargetOfExportAssignment((<ExportAssignment | BinaryExpression>node), dontRecursivelyResolve);
19521957
case SyntaxKind.NamespaceExportDeclaration:
19531958
return getTargetOfNamespaceExportDeclaration(<NamespaceExportDeclaration>node, dontRecursivelyResolve);
19541959
}
@@ -2229,20 +2234,28 @@ namespace ts {
22292234
// An external module with an 'export =' declaration resolves to the target of the 'export =' declaration,
22302235
// and an external module with no 'export =' declaration resolves to the module itself.
22312236
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol {
2232-
return moduleSymbol && getMergedSymbol(resolveSymbol(getCommonJsExportEquals(moduleSymbol), dontResolveAlias)) || moduleSymbol;
2237+
return moduleSymbol && getMergedSymbol(getCommonJsExportEquals(resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.ExportEquals), dontResolveAlias), moduleSymbol)) || moduleSymbol;
22332238
}
22342239

2235-
function getCommonJsExportEquals(moduleSymbol: Symbol): Symbol {
2236-
const exported = moduleSymbol.exports.get(InternalSymbolName.ExportEquals);
2237-
if (!exported || !exported.exports || moduleSymbol.exports.size === 1) {
2240+
function getCommonJsExportEquals(exported: Symbol, moduleSymbol: Symbol): Symbol {
2241+
if (!exported || moduleSymbol.exports.size === 1) {
22382242
return exported;
22392243
}
22402244
const merged = cloneSymbol(exported);
2245+
if (merged.exports === undefined) {
2246+
merged.flags = merged.flags | SymbolFlags.ValueModule;
2247+
merged.exports = createSymbolTable();
2248+
}
22412249
moduleSymbol.exports.forEach((s, name) => {
22422250
if (name === InternalSymbolName.ExportEquals) return;
22432251
if (!merged.exports.has(name)) {
22442252
merged.exports.set(name, s);
22452253
}
2254+
else {
2255+
const ms = cloneSymbol(merged.exports.get(name));
2256+
mergeSymbol(ms, s);
2257+
merged.exports.set(name, ms);
2258+
}
22462259
});
22472260
return merged;
22482261
}

src/compiler/utilities.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,11 +2140,13 @@ namespace ts {
21402140
node.kind === SyntaxKind.NamespaceImport ||
21412141
node.kind === SyntaxKind.ImportSpecifier ||
21422142
node.kind === SyntaxKind.ExportSpecifier ||
2143-
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node);
2143+
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node) ||
2144+
isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) === SpecialPropertyAssignmentKind.ModuleExports;
21442145
}
21452146

2146-
export function exportAssignmentIsAlias(node: ExportAssignment): boolean {
2147-
return isEntityNameExpression(node.expression);
2147+
export function exportAssignmentIsAlias(node: ExportAssignment | BinaryExpression): boolean {
2148+
const e = isExportAssignment(node) ? node.expression : node.right;
2149+
return isEntityNameExpression(e) || isClassExpression(e);
21482150
}
21492151

21502152
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [tests/cases/compiler/commonJsImportClassExpression.ts] ////
2+
3+
//// [mod1.ts]
4+
export = class {
5+
chunk = 1
6+
}
7+
8+
//// [use.ts]
9+
import Chunk = require('./mod1')
10+
declare var c: Chunk;
11+
c.chunk;
12+
13+
14+
//// [mod1.js]
15+
"use strict";
16+
module.exports = /** @class */ (function () {
17+
function class_1() {
18+
this.chunk = 1;
19+
}
20+
return class_1;
21+
}());
22+
//// [use.js]
23+
"use strict";
24+
exports.__esModule = true;
25+
c.chunk;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/compiler/use.ts ===
2+
import Chunk = require('./mod1')
3+
>Chunk : Symbol(Chunk, Decl(use.ts, 0, 0))
4+
5+
declare var c: Chunk;
6+
>c : Symbol(c, Decl(use.ts, 1, 11))
7+
>Chunk : Symbol(Chunk, Decl(use.ts, 0, 0))
8+
9+
c.chunk;
10+
>c.chunk : Symbol(Chunk.chunk, Decl(mod1.ts, 0, 16))
11+
>c : Symbol(c, Decl(use.ts, 1, 11))
12+
>chunk : Symbol(Chunk.chunk, Decl(mod1.ts, 0, 16))
13+
14+
=== tests/cases/compiler/mod1.ts ===
15+
export = class {
16+
chunk = 1
17+
>chunk : Symbol((Anonymous class).chunk, Decl(mod1.ts, 0, 16))
18+
}
19+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/compiler/use.ts ===
2+
import Chunk = require('./mod1')
3+
>Chunk : typeof Chunk
4+
5+
declare var c: Chunk;
6+
>c : Chunk
7+
>Chunk : Chunk
8+
9+
c.chunk;
10+
>c.chunk : number
11+
>c : Chunk
12+
>chunk : number
13+
14+
=== tests/cases/compiler/mod1.ts ===
15+
export = class {
16+
>class { chunk = 1} : typeof (Anonymous class)
17+
18+
chunk = 1
19+
>chunk : number
20+
>1 : 1
21+
}
22+

tests/baselines/reference/es5ExportEquals.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
=== tests/cases/compiler/es5ExportEquals.ts ===
22
export function f() { }
3-
>f : () => void
3+
>f : typeof f
44

55
export = f;
66
>f : () => void

tests/baselines/reference/es6ExportEquals.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
=== tests/cases/compiler/es6ExportEquals.ts ===
22
export function f() { }
3-
>f : () => void
3+
>f : typeof f
44

55
export = f;
66
>f : () => void
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/conformance/jsdoc/use.js ===
2+
/// <reference path='./types.d.ts'/>
3+
/** @typedef {import("./mod1")} C
4+
* @type {C} */
5+
var c;
6+
>c : Symbol(c, Decl(use.js, 3, 3))
7+
8+
c.chunk;
9+
>c.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
10+
>c : Symbol(c, Decl(use.js, 3, 3))
11+
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
12+
13+
const D = require("./mod1");
14+
>D : Symbol(D, Decl(use.js, 6, 5))
15+
>require : Symbol(require, Decl(types.d.ts, 0, 0))
16+
>"./mod1" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0))
17+
18+
/** @type {D} */
19+
var d;
20+
>d : Symbol(d, Decl(use.js, 8, 3))
21+
22+
d.chunk;
23+
>d.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
24+
>d : Symbol(d, Decl(use.js, 8, 3))
25+
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
26+
27+
=== tests/cases/conformance/jsdoc/types.d.ts ===
28+
declare function require(name: string): any;
29+
>require : Symbol(require, Decl(types.d.ts, 0, 0))
30+
>name : Symbol(name, Decl(types.d.ts, 0, 25))
31+
32+
declare var exports: any;
33+
>exports : Symbol(exports, Decl(types.d.ts, 1, 11))
34+
35+
declare var module: { exports: any };
36+
>module : Symbol(module, Decl(types.d.ts, 2, 11))
37+
>exports : Symbol(exports, Decl(types.d.ts, 2, 21))
38+
39+
=== tests/cases/conformance/jsdoc/mod1.js ===
40+
/// <reference path='./types.d.ts'/>
41+
class Chunk {
42+
>Chunk : Symbol(Chunk, Decl(mod1.js, 0, 0))
43+
44+
constructor() {
45+
this.chunk = 1;
46+
>this.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
47+
>this : Symbol(Chunk, Decl(mod1.js, 0, 0))
48+
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
49+
}
50+
}
51+
module.exports = Chunk;
52+
>module.exports : Symbol(exports, Decl(types.d.ts, 2, 21))
53+
>module : Symbol(export=, Decl(mod1.js, 5, 1))
54+
>exports : Symbol(export=, Decl(mod1.js, 5, 1))
55+
>Chunk : Symbol(Chunk, Decl(mod1.js, 0, 0))
56+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
=== tests/cases/conformance/jsdoc/use.js ===
2+
/// <reference path='./types.d.ts'/>
3+
/** @typedef {import("./mod1")} C
4+
* @type {C} */
5+
var c;
6+
>c : Chunk
7+
8+
c.chunk;
9+
>c.chunk : number
10+
>c : Chunk
11+
>chunk : number
12+
13+
const D = require("./mod1");
14+
>D : typeof Chunk
15+
>require("./mod1") : typeof Chunk
16+
>require : (name: string) => any
17+
>"./mod1" : "./mod1"
18+
19+
/** @type {D} */
20+
var d;
21+
>d : Chunk
22+
23+
d.chunk;
24+
>d.chunk : number
25+
>d : Chunk
26+
>chunk : number
27+
28+
=== tests/cases/conformance/jsdoc/types.d.ts ===
29+
declare function require(name: string): any;
30+
>require : (name: string) => any
31+
>name : string
32+
33+
declare var exports: any;
34+
>exports : any
35+
36+
declare var module: { exports: any };
37+
>module : { exports: any; }
38+
>exports : any
39+
40+
=== tests/cases/conformance/jsdoc/mod1.js ===
41+
/// <reference path='./types.d.ts'/>
42+
class Chunk {
43+
>Chunk : Chunk
44+
45+
constructor() {
46+
this.chunk = 1;
47+
>this.chunk = 1 : 1
48+
>this.chunk : number
49+
>this : this
50+
>chunk : number
51+
>1 : 1
52+
}
53+
}
54+
module.exports = Chunk;
55+
>module.exports = Chunk : typeof Chunk
56+
>module.exports : any
57+
>module : { exports: any; }
58+
>exports : any
59+
>Chunk : typeof Chunk
60+

0 commit comments

Comments
 (0)