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
This commit is contained in:
Nathan Shively-Sanders
2018-04-23 15:24:31 -07:00
committed by GitHub
parent ef8af93329
commit 905f9a02ad
18 changed files with 402 additions and 27 deletions

View File

@@ -2220,14 +2220,14 @@ namespace ts {
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String);
}
function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
function bindExportAssignment(node: ExportAssignment) {
if (!container.symbol || !container.symbol.exports) {
// Export assignment in some sort of block construct
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
}
else {
const flags = node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node)
// An export default clause with an EntityNameExpression exports all meanings of that identifier
// An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression;
? SymbolFlags.Alias
// An export default clause with any other expression exports a value
: SymbolFlags.Property;
@@ -2322,7 +2322,10 @@ namespace ts {
// 'module.exports = expr' assignment
setCommonJsModuleIndicator(node);
declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule, SymbolFlags.None);
const flags = exportAssignmentIsAlias(node)
? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class
: SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule;
declareSymbol(file.symbol.exports, file.symbol, node, flags, SymbolFlags.None);
}
function bindThisPropertyAssignment(node: BinaryExpression | PropertyAccessExpression) {

View File

@@ -1926,13 +1926,17 @@ namespace ts {
resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias);
}
function getTargetOfExportAssignment(node: ExportAssignment, dontResolveAlias: boolean): Symbol | undefined {
const aliasLike = resolveEntityName(<EntityNameExpression>node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias);
function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined {
const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression;
if (isClassExpression(expression)) {
return checkExpression(expression).symbol;
}
const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias);
if (aliasLike) {
return aliasLike;
}
checkExpression(node.expression);
return getNodeLinks(node.expression).resolvedSymbol;
checkExpression(expression);
return getNodeLinks(expression).resolvedSymbol;
}
function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve?: boolean): Symbol | undefined {
@@ -1948,7 +1952,8 @@ namespace ts {
case SyntaxKind.ExportSpecifier:
return getTargetOfExportSpecifier(<ExportSpecifier>node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve);
case SyntaxKind.ExportAssignment:
return getTargetOfExportAssignment(<ExportAssignment>node, dontRecursivelyResolve);
case SyntaxKind.BinaryExpression:
return getTargetOfExportAssignment((<ExportAssignment | BinaryExpression>node), dontRecursivelyResolve);
case SyntaxKind.NamespaceExportDeclaration:
return getTargetOfNamespaceExportDeclaration(<NamespaceExportDeclaration>node, dontRecursivelyResolve);
}
@@ -2229,20 +2234,28 @@ namespace ts {
// An external module with an 'export =' declaration resolves to the target of the 'export =' declaration,
// and an external module with no 'export =' declaration resolves to the module itself.
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol {
return moduleSymbol && getMergedSymbol(resolveSymbol(getCommonJsExportEquals(moduleSymbol), dontResolveAlias)) || moduleSymbol;
return moduleSymbol && getMergedSymbol(getCommonJsExportEquals(resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.ExportEquals), dontResolveAlias), moduleSymbol)) || moduleSymbol;
}
function getCommonJsExportEquals(moduleSymbol: Symbol): Symbol {
const exported = moduleSymbol.exports.get(InternalSymbolName.ExportEquals);
if (!exported || !exported.exports || moduleSymbol.exports.size === 1) {
function getCommonJsExportEquals(exported: Symbol, moduleSymbol: Symbol): Symbol {
if (!exported || moduleSymbol.exports.size === 1) {
return exported;
}
const merged = cloneSymbol(exported);
if (merged.exports === undefined) {
merged.flags = merged.flags | SymbolFlags.ValueModule;
merged.exports = createSymbolTable();
}
moduleSymbol.exports.forEach((s, name) => {
if (name === InternalSymbolName.ExportEquals) return;
if (!merged.exports.has(name)) {
merged.exports.set(name, s);
}
else {
const ms = cloneSymbol(merged.exports.get(name));
mergeSymbol(ms, s);
merged.exports.set(name, ms);
}
});
return merged;
}

View File

@@ -2140,11 +2140,13 @@ namespace ts {
node.kind === SyntaxKind.NamespaceImport ||
node.kind === SyntaxKind.ImportSpecifier ||
node.kind === SyntaxKind.ExportSpecifier ||
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node);
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node) ||
isBinaryExpression(node) && getSpecialPropertyAssignmentKind(node) === SpecialPropertyAssignmentKind.ModuleExports;
}
export function exportAssignmentIsAlias(node: ExportAssignment): boolean {
return isEntityNameExpression(node.expression);
export function exportAssignmentIsAlias(node: ExportAssignment | BinaryExpression): boolean {
const e = isExportAssignment(node) ? node.expression : node.right;
return isEntityNameExpression(e) || isClassExpression(e);
}
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) {

View File

@@ -0,0 +1,25 @@
//// [tests/cases/compiler/commonJsImportClassExpression.ts] ////
//// [mod1.ts]
export = class {
chunk = 1
}
//// [use.ts]
import Chunk = require('./mod1')
declare var c: Chunk;
c.chunk;
//// [mod1.js]
"use strict";
module.exports = /** @class */ (function () {
function class_1() {
this.chunk = 1;
}
return class_1;
}());
//// [use.js]
"use strict";
exports.__esModule = true;
c.chunk;

View File

@@ -0,0 +1,19 @@
=== tests/cases/compiler/use.ts ===
import Chunk = require('./mod1')
>Chunk : Symbol(Chunk, Decl(use.ts, 0, 0))
declare var c: Chunk;
>c : Symbol(c, Decl(use.ts, 1, 11))
>Chunk : Symbol(Chunk, Decl(use.ts, 0, 0))
c.chunk;
>c.chunk : Symbol(Chunk.chunk, Decl(mod1.ts, 0, 16))
>c : Symbol(c, Decl(use.ts, 1, 11))
>chunk : Symbol(Chunk.chunk, Decl(mod1.ts, 0, 16))
=== tests/cases/compiler/mod1.ts ===
export = class {
chunk = 1
>chunk : Symbol((Anonymous class).chunk, Decl(mod1.ts, 0, 16))
}

View File

@@ -0,0 +1,22 @@
=== tests/cases/compiler/use.ts ===
import Chunk = require('./mod1')
>Chunk : typeof Chunk
declare var c: Chunk;
>c : Chunk
>Chunk : Chunk
c.chunk;
>c.chunk : number
>c : Chunk
>chunk : number
=== tests/cases/compiler/mod1.ts ===
export = class {
>class { chunk = 1} : typeof (Anonymous class)
chunk = 1
>chunk : number
>1 : 1
}

View File

@@ -1,6 +1,6 @@
=== tests/cases/compiler/es5ExportEquals.ts ===
export function f() { }
>f : () => void
>f : typeof f
export = f;
>f : () => void

View File

@@ -1,6 +1,6 @@
=== tests/cases/compiler/es6ExportEquals.ts ===
export function f() { }
>f : () => void
>f : typeof f
export = f;
>f : () => void

View File

@@ -0,0 +1,56 @@
=== tests/cases/conformance/jsdoc/use.js ===
/// <reference path='./types.d.ts'/>
/** @typedef {import("./mod1")} C
* @type {C} */
var c;
>c : Symbol(c, Decl(use.js, 3, 3))
c.chunk;
>c.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
>c : Symbol(c, Decl(use.js, 3, 3))
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
const D = require("./mod1");
>D : Symbol(D, Decl(use.js, 6, 5))
>require : Symbol(require, Decl(types.d.ts, 0, 0))
>"./mod1" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0))
/** @type {D} */
var d;
>d : Symbol(d, Decl(use.js, 8, 3))
d.chunk;
>d.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
>d : Symbol(d, Decl(use.js, 8, 3))
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
=== tests/cases/conformance/jsdoc/types.d.ts ===
declare function require(name: string): any;
>require : Symbol(require, Decl(types.d.ts, 0, 0))
>name : Symbol(name, Decl(types.d.ts, 0, 25))
declare var exports: any;
>exports : Symbol(exports, Decl(types.d.ts, 1, 11))
declare var module: { exports: any };
>module : Symbol(module, Decl(types.d.ts, 2, 11))
>exports : Symbol(exports, Decl(types.d.ts, 2, 21))
=== tests/cases/conformance/jsdoc/mod1.js ===
/// <reference path='./types.d.ts'/>
class Chunk {
>Chunk : Symbol(Chunk, Decl(mod1.js, 0, 0))
constructor() {
this.chunk = 1;
>this.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
>this : Symbol(Chunk, Decl(mod1.js, 0, 0))
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
}
}
module.exports = Chunk;
>module.exports : Symbol(exports, Decl(types.d.ts, 2, 21))
>module : Symbol(export=, Decl(mod1.js, 5, 1))
>exports : Symbol(export=, Decl(mod1.js, 5, 1))
>Chunk : Symbol(Chunk, Decl(mod1.js, 0, 0))

View File

@@ -0,0 +1,60 @@
=== tests/cases/conformance/jsdoc/use.js ===
/// <reference path='./types.d.ts'/>
/** @typedef {import("./mod1")} C
* @type {C} */
var c;
>c : Chunk
c.chunk;
>c.chunk : number
>c : Chunk
>chunk : number
const D = require("./mod1");
>D : typeof Chunk
>require("./mod1") : typeof Chunk
>require : (name: string) => any
>"./mod1" : "./mod1"
/** @type {D} */
var d;
>d : Chunk
d.chunk;
>d.chunk : number
>d : Chunk
>chunk : number
=== tests/cases/conformance/jsdoc/types.d.ts ===
declare function require(name: string): any;
>require : (name: string) => any
>name : string
declare var exports: any;
>exports : any
declare var module: { exports: any };
>module : { exports: any; }
>exports : any
=== tests/cases/conformance/jsdoc/mod1.js ===
/// <reference path='./types.d.ts'/>
class Chunk {
>Chunk : Chunk
constructor() {
this.chunk = 1;
>this.chunk = 1 : 1
>this.chunk : number
>this : this
>chunk : number
>1 : 1
}
}
module.exports = Chunk;
>module.exports = Chunk : typeof Chunk
>module.exports : any
>module : { exports: any; }
>exports : any
>Chunk : typeof Chunk

View File

@@ -0,0 +1,54 @@
=== tests/cases/conformance/jsdoc/use.js ===
/// <reference path='./types.d.ts'/>
/** @typedef {import("./mod1")} C
* @type {C} */
var c;
>c : Symbol(c, Decl(use.js, 3, 3))
c.chunk;
>c.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
>c : Symbol(c, Decl(use.js, 3, 3))
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
const D = require("./mod1");
>D : Symbol(D, Decl(use.js, 6, 5))
>require : Symbol(require, Decl(types.d.ts, 0, 0))
>"./mod1" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0))
/** @type {D} */
var d;
>d : Symbol(d, Decl(use.js, 8, 3))
d.chunk;
>d.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
>d : Symbol(d, Decl(use.js, 8, 3))
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
=== tests/cases/conformance/jsdoc/types.d.ts ===
declare function require(name: string): any;
>require : Symbol(require, Decl(types.d.ts, 0, 0))
>name : Symbol(name, Decl(types.d.ts, 0, 25))
declare var exports: any;
>exports : Symbol(exports, Decl(types.d.ts, 1, 11))
declare var module: { exports: any };
>module : Symbol(module, Decl(types.d.ts, 2, 11))
>exports : Symbol(exports, Decl(types.d.ts, 2, 21))
=== tests/cases/conformance/jsdoc/mod1.js ===
/// <reference path='./types.d.ts'/>
module.exports = class Chunk {
>module.exports : Symbol(exports, Decl(types.d.ts, 2, 21))
>module : Symbol(export=, Decl(mod1.js, 0, 0))
>exports : Symbol(export=, Decl(mod1.js, 0, 0))
>Chunk : Symbol(Chunk, Decl(mod1.js, 1, 16))
constructor() {
this.chunk = 1;
>this.chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
>this : Symbol(Chunk, Decl(mod1.js, 1, 16))
>chunk : Symbol(Chunk.chunk, Decl(mod1.js, 2, 19))
}
}

View File

@@ -0,0 +1,59 @@
=== tests/cases/conformance/jsdoc/use.js ===
/// <reference path='./types.d.ts'/>
/** @typedef {import("./mod1")} C
* @type {C} */
var c;
>c : Chunk
c.chunk;
>c.chunk : number
>c : Chunk
>chunk : number
const D = require("./mod1");
>D : typeof Chunk
>require("./mod1") : typeof Chunk
>require : (name: string) => any
>"./mod1" : "./mod1"
/** @type {D} */
var d;
>d : Chunk
d.chunk;
>d.chunk : number
>d : Chunk
>chunk : number
=== tests/cases/conformance/jsdoc/types.d.ts ===
declare function require(name: string): any;
>require : (name: string) => any
>name : string
declare var exports: any;
>exports : any
declare var module: { exports: any };
>module : { exports: any; }
>exports : any
=== tests/cases/conformance/jsdoc/mod1.js ===
/// <reference path='./types.d.ts'/>
module.exports = class Chunk {
>module.exports = class Chunk { constructor() { this.chunk = 1; }} : typeof Chunk
>module.exports : any
>module : { exports: any; }
>exports : any
>class Chunk { constructor() { this.chunk = 1; }} : typeof Chunk
>Chunk : typeof Chunk
constructor() {
this.chunk = 1;
>this.chunk = 1 : 1
>this.chunk : number
>this : this
>chunk : number
>1 : 1
}
}

View File

@@ -13,14 +13,14 @@ mod1.justExport.toFixed()
>toFixed : Symbol(Number.toFixed, Decl(lib.d.ts, --, --))
mod1.bothBefore.toFixed() // error
>mod1.bothBefore : Symbol(bothBefore)
>mod1.bothBefore : Symbol(A.bothBefore, Decl(mod1.js, 2, 16), Decl(mod1.js, 0, 0))
>mod1 : Symbol(mod1, Decl(a.js, 1, 3))
>bothBefore : Symbol(bothBefore)
>bothBefore : Symbol(A.bothBefore, Decl(mod1.js, 2, 16), Decl(mod1.js, 0, 0))
mod1.bothAfter.toFixed()
>mod1.bothAfter : Symbol(bothAfter)
>mod1.bothAfter : Symbol(A.bothAfter, Decl(mod1.js, 3, 16), Decl(mod1.js, 8, 1))
>mod1 : Symbol(mod1, Decl(a.js, 1, 3))
>bothAfter : Symbol(bothAfter)
>bothAfter : Symbol(A.bothAfter, Decl(mod1.js, 3, 16), Decl(mod1.js, 8, 1))
mod1.justProperty.length
>mod1.justProperty.length : Symbol(String.length, Decl(lib.d.ts, --, --))
@@ -41,10 +41,10 @@ declare function require(name: string): any;
=== tests/cases/conformance/salsa/mod1.js ===
/// <reference path='./requires.d.ts' />
module.exports.bothBefore = 'string'
>module.exports : Symbol(bothBefore, Decl(mod1.js, 0, 0))
>module.exports : Symbol(A.bothBefore, Decl(mod1.js, 2, 16), Decl(mod1.js, 0, 0))
>module : Symbol(module, Decl(requires.d.ts, 0, 11))
>exports : Symbol(exports, Decl(requires.d.ts, 0, 21))
>bothBefore : Symbol(bothBefore, Decl(mod1.js, 0, 0))
>bothBefore : Symbol(A.bothBefore, Decl(mod1.js, 2, 16), Decl(mod1.js, 0, 0))
A.justExport = 4
>A.justExport : Symbol(A.justExport, Decl(mod1.js, 1, 36))
@@ -74,10 +74,10 @@ function A() {
>p : Symbol(A.p, Decl(mod1.js, 6, 14))
}
module.exports.bothAfter = 'string'
>module.exports : Symbol(bothAfter, Decl(mod1.js, 8, 1))
>module.exports : Symbol(A.bothAfter, Decl(mod1.js, 3, 16), Decl(mod1.js, 8, 1))
>module : Symbol(module, Decl(requires.d.ts, 0, 11))
>exports : Symbol(exports, Decl(requires.d.ts, 0, 21))
>bothAfter : Symbol(bothAfter, Decl(mod1.js, 8, 1))
>bothAfter : Symbol(A.bothAfter, Decl(mod1.js, 3, 16), Decl(mod1.js, 8, 1))
module.exports.justProperty = 'string'
>module.exports : Symbol(justProperty, Decl(mod1.js, 9, 35))

View File

@@ -0,0 +1,9 @@
// @Filename: mod1.ts
export = class {
chunk = 1
}
// @Filename: use.ts
import Chunk = require('./mod1')
declare var c: Chunk;
c.chunk;

View File

@@ -0,0 +1,27 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @Filename: types.d.ts
declare function require(name: string): any;
declare var exports: any;
declare var module: { exports: any };
// @Filename: mod1.js
/// <reference path='./types.d.ts'/>
class Chunk {
constructor() {
this.chunk = 1;
}
}
module.exports = Chunk;
// @Filename: use.js
/// <reference path='./types.d.ts'/>
/** @typedef {import("./mod1")} C
* @type {C} */
var c;
c.chunk;
const D = require("./mod1");
/** @type {D} */
var d;
d.chunk;

View File

@@ -0,0 +1,26 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @Filename: types.d.ts
declare function require(name: string): any;
declare var exports: any;
declare var module: { exports: any };
// @Filename: mod1.js
/// <reference path='./types.d.ts'/>
module.exports = class Chunk {
constructor() {
this.chunk = 1;
}
}
// @Filename: use.js
/// <reference path='./types.d.ts'/>
/** @typedef {import("./mod1")} C
* @type {C} */
var c;
c.chunk;
const D = require("./mod1");
/** @type {D} */
var d;
d.chunk;

View File

@@ -11,6 +11,6 @@
const [r0, r1, r2, r3] = test.ranges();
const defs = { definition: "(local class) A", ranges: [r0, r1] };
const imports = { definition: 'import A = require("./a")', ranges: [r2, r3] };
const imports = { definition: '(alias) (local class) A\nimport A = require("./a")', ranges: [r2, r3] };
verify.referenceGroups([r0, r1], [defs, imports]);
verify.referenceGroups([r2, r3], [imports, defs]);

View File

@@ -11,7 +11,7 @@
const [r0, r1, r2] = test.ranges();
const defs = { definition: "(local class) A", ranges: [r0] };
const imports = { definition: 'import A = require("./a")', ranges: [r1, r2] };
const imports = { definition: '(alias) (local class) A\nimport A = require("./a")', ranges: [r1, r2] };
verify.referenceGroups([r0], [defs, imports]);
verify.referenceGroups([r1, r2], [imports, defs]);