Skip to content

Commit 3f63804

Browse files
authored
Make direct assignments to cjs exports considered literal contexts (microsoft#39816)
* Make direct assignments to cjs exports considered literal contexts * Style feedback from PR * Trailing whitespaaaaace
1 parent f76452c commit 3f63804

35 files changed

+300
-199
lines changed

src/compiler/checker.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -7014,9 +7014,13 @@ namespace ts {
70147014
else {
70157015
// A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_
70167016
// `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property`
7017-
const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) ? undefined
7018-
: isConstVariable(symbol) ? NodeFlags.Const
7019-
: NodeFlags.Let;
7017+
const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable)
7018+
? symbol.parent?.valueDeclaration && isSourceFile(symbol.parent?.valueDeclaration)
7019+
? NodeFlags.Const // exports are immutable in es6, which is what we emulate and check; so it's safe to mark all exports as `const` (there's no difference to consumers, but it allows unique symbol type declarations)
7020+
: undefined
7021+
: isConstVariable(symbol)
7022+
? NodeFlags.Const
7023+
: NodeFlags.Let;
70207024
const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol);
70217025
let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d));
70227026
if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) {
@@ -9180,7 +9184,10 @@ namespace ts {
91809184
if (containsSameNamedThisProperty(expression.left, expression.right)) {
91819185
return anyType;
91829186
}
9183-
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : getWidenedLiteralType(checkExpressionCached(expression.right));
9187+
const isDirectExport = kind === AssignmentDeclarationKind.ExportsProperty && (isPropertyAccessExpression(expression.left) || isElementAccessExpression(expression.left)) && (isModuleExportsAccessExpression(expression.left.expression) || (isIdentifier(expression.left.expression) && isExportsIdentifier(expression.left.expression)));
9188+
const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol)
9189+
: isDirectExport ? getRegularTypeOfLiteralType(checkExpressionCached(expression.right))
9190+
: getWidenedLiteralType(checkExpressionCached(expression.right));
91849191
if (type.flags & TypeFlags.Object &&
91859192
kind === AssignmentDeclarationKind.ModuleExports &&
91869193
symbol.escapedName === InternalSymbolName.ExportEquals) {
@@ -16418,9 +16425,11 @@ namespace ts {
1641816425

1641916426
function getESSymbolLikeTypeForNode(node: Node) {
1642016427
if (isValidESSymbolDeclaration(node)) {
16421-
const symbol = getSymbolOfNode(node);
16422-
const links = getSymbolLinks(symbol);
16423-
return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol));
16428+
const symbol = isCommonJsExportPropertyAssignment(node) ? getSymbolOfNode((node as BinaryExpression).left) : getSymbolOfNode(node);
16429+
if (symbol) {
16430+
const links = getSymbolLinks(symbol);
16431+
return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol));
16432+
}
1642416433
}
1642516434
return esSymbolType;
1642616435
}
@@ -34209,7 +34218,7 @@ namespace ts {
3420934218

3421034219
function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type {
3421134220
const type = checkExpression(node, checkMode, forceTuple);
34212-
return isConstContext(node) ? getRegularTypeOfLiteralType(type) :
34221+
return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) :
3421334222
isTypeAssertion(node) ? type :
3421434223
getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node));
3421534224
}

src/compiler/utilities.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -1520,10 +1520,21 @@ namespace ts {
15201520
&& node.parent.parent.kind === SyntaxKind.VariableStatement;
15211521
}
15221522

1523-
export function isValidESSymbolDeclaration(node: Node): node is VariableDeclaration | PropertyDeclaration | SignatureDeclaration {
1524-
return isVariableDeclaration(node) ? isVarConst(node) && isIdentifier(node.name) && isVariableDeclarationInVariableStatement(node) :
1523+
export function isCommonJsExportedExpression(node: Node) {
1524+
if (!isInJSFile(node)) return false;
1525+
return (isObjectLiteralExpression(node.parent) && isBinaryExpression(node.parent.parent) && getAssignmentDeclarationKind(node.parent.parent) === AssignmentDeclarationKind.ModuleExports) ||
1526+
isCommonJsExportPropertyAssignment(node.parent);
1527+
}
1528+
1529+
export function isCommonJsExportPropertyAssignment(node: Node) {
1530+
if (!isInJSFile(node)) return false;
1531+
return (isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ExportsProperty);
1532+
}
1533+
1534+
export function isValidESSymbolDeclaration(node: Node): boolean {
1535+
return (isVariableDeclaration(node) ? isVarConst(node) && isIdentifier(node.name) && isVariableDeclarationInVariableStatement(node) :
15251536
isPropertyDeclaration(node) ? hasEffectiveReadonlyModifier(node) && hasStaticModifier(node) :
1526-
isPropertySignature(node) && hasEffectiveReadonlyModifier(node);
1537+
isPropertySignature(node) && hasEffectiveReadonlyModifier(node)) || isCommonJsExportPropertyAssignment(node);
15271538
}
15281539

15291540
export function introducesArgumentsExoticObject(node: Node) {

tests/baselines/reference/assignmentToVoidZero1.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ exports.y = 2;
1313

1414

1515
//// [assignmentToVoidZero1.d.ts]
16-
export var x: number;
17-
export var y: number;
16+
export const x: 1;
17+
export const y: 2;

tests/baselines/reference/assignmentToVoidZero1.types

+8-8
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@
22
// #38552
33
exports.y = exports.x = void 0;
44
>exports.y = exports.x = void 0 : undefined
5-
>exports.y : number
5+
>exports.y : 2
66
>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1")
7-
>y : number
7+
>y : 2
88
>exports.x = void 0 : undefined
9-
>exports.x : number
9+
>exports.x : 1
1010
>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1")
11-
>x : number
11+
>x : 1
1212
>void 0 : undefined
1313
>0 : 0
1414

1515
exports.x = 1;
1616
>exports.x = 1 : 1
17-
>exports.x : number
17+
>exports.x : 1
1818
>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1")
19-
>x : number
19+
>x : 1
2020
>1 : 1
2121

2222
exports.y = 2;
2323
>exports.y = 2 : 2
24-
>exports.y : number
24+
>exports.y : 2
2525
>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1")
26-
>y : number
26+
>y : 2
2727
>2 : 2
2828

tests/baselines/reference/assignmentToVoidZero2.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ assignmentToVoidZero2_1.j + assignmentToVoidZero2_1.k;
4141

4242

4343
//// [assignmentToVoidZero2.d.ts]
44-
export var j: number;
44+
export const j: 1;
4545
//// [importer.d.ts]
4646
export {};

tests/baselines/reference/assignmentToVoidZero2.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
=== tests/cases/conformance/salsa/assignmentToVoidZero2.js ===
22
exports.j = 1;
33
>exports.j = 1 : 1
4-
>exports.j : number
4+
>exports.j : 1
55
>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero2")
6-
>j : number
6+
>j : 1
77
>1 : 1
88

99
exports.k = void 0;
@@ -76,11 +76,11 @@ c.p + c.q
7676

7777
=== tests/cases/conformance/salsa/importer.js ===
7878
import { j, k } from './assignmentToVoidZero2'
79-
>j : number
79+
>j : 1
8080
>k : any
8181

8282
j + k
8383
>j + k : any
84-
>j : number
84+
>j : 1
8585
>k : any
8686

tests/baselines/reference/commonJsUnusedLocals.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ const x = 0;
55

66
exports.y = 1;
77
>exports.y = 1 : 1
8-
>exports.y : number
8+
>exports.y : 1
99
>exports : typeof import("/a")
10-
>y : number
10+
>y : 1
1111
>1 : 1
1212

tests/baselines/reference/commonjsAccessExports.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
=== /a.js ===
22
exports.x = 0;
33
>exports.x = 0 : 0
4-
>exports.x : number
4+
>exports.x : 0
55
>exports : typeof import("/a")
6-
>x : number
6+
>x : 0
77
>0 : 0
88

99
exports.x;
10-
>exports.x : number
10+
>exports.x : 0
1111
>exports : typeof import("/a")
12-
>x : number
12+
>x : 0
1313

1414
// Works nested
1515
{

tests/baselines/reference/declarationEmitOutFileBundlePaths.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export {
1717

1818
//// [index.d.ts]
1919
declare module "versions.static" {
20-
var _default: {
20+
const _default: {
2121
"@a/b": string;
2222
"@a/c": string;
2323
};

tests/baselines/reference/jsDeclarationsCrossfileMerge.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ module.exports = m.default;
1010
>module.exports : typeof m.default
1111
>module : { exports: typeof m.default; }
1212
>exports : typeof m.default
13-
>m.default : { (): void; memberName: string; }
13+
>m.default : { (): void; memberName: "thing"; }
1414
>m : typeof m
15-
>default : { (): void; memberName: string; }
15+
>default : { (): void; memberName: "thing"; }
1616

1717
module.exports.memberName = "thing";
1818
>module.exports.memberName = "thing" : "thing"
19-
>module.exports.memberName : string
19+
>module.exports.memberName : "thing"
2020
>module.exports : typeof m.default
2121
>module : { exports: typeof m.default; }
2222
>exports : typeof m.default
23-
>memberName : string
23+
>memberName : "thing"
2424
>"thing" : "thing"
2525

2626
=== tests/cases/conformance/jsdoc/declarations/exporter.js ===

tests/baselines/reference/jsDeclarationsDefault.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ exports.default = func;
117117

118118

119119
//// [index1.d.ts]
120-
declare var _default: 12;
120+
declare const _default: 12;
121121
export default _default;
122122
//// [index2.d.ts]
123123
export default function foo(): typeof foo;
@@ -137,7 +137,7 @@ declare class Bar extends Fab {
137137
import Fab from "./index3";
138138
//// [index5.d.ts]
139139
type _default = string | number;
140-
declare var _default: 12;
140+
declare const _default: 12;
141141
export default _default;
142142
//// [index6.d.ts]
143143
declare function func(): void;

tests/baselines/reference/jsDeclarationsExportAssignedClassInstance3.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ module.exports.additional = 20;
2222

2323
//// [index.d.ts]
2424
export const member: number;
25-
export const additional: number;
25+
export const additional: 20;

tests/baselines/reference/jsDeclarationsExportAssignedClassInstance3.types

+9-9
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ class Foo {
1212
}
1313

1414
module.exports = new Foo();
15-
>module.exports = new Foo() : { member: number; additional: number; }
16-
>module.exports : { member: number; additional: number; }
17-
>module : { exports: { member: number; additional: number; }; }
18-
>exports : { member: number; additional: number; }
15+
>module.exports = new Foo() : { member: number; additional: 20; }
16+
>module.exports : { member: number; additional: 20; }
17+
>module : { exports: { member: number; additional: 20; }; }
18+
>exports : { member: number; additional: 20; }
1919
>new Foo() : Foo
2020
>Foo : typeof Foo
2121

2222
module.exports.additional = 20;
2323
>module.exports.additional = 20 : 20
24-
>module.exports.additional : number
25-
>module.exports : { member: number; additional: number; }
26-
>module : { exports: { member: number; additional: number; }; }
27-
>exports : { member: number; additional: number; }
28-
>additional : number
24+
>module.exports.additional : 20
25+
>module.exports : { member: number; additional: 20; }
26+
>module : { exports: { member: number; additional: 20; }; }
27+
>exports : { member: number; additional: 20; }
28+
>additional : 20
2929
>20 : 20
3030

tests/baselines/reference/jsDeclarationsImportNamespacedType.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export var dummy = 1
1414

1515
//// [mod1.d.ts]
1616
/** @typedef {number} Dotted.Name */
17-
export var dummy: number;
17+
export const dummy: number;
1818
export namespace Dotted {
1919
type Name = number;
2020
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [file.js]
2+
const customSymbol = Symbol("custom");
3+
4+
// This is a common pattern in Node’s built-in modules:
5+
module.exports = {
6+
customSymbol,
7+
};
8+
9+
exports.customSymbol2 = Symbol("custom");
10+
11+
12+
13+
//// [file.d.ts]
14+
export const customSymbol2: unique symbol;
15+
export const customSymbol: unique symbol;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/compiler/file.js ===
2+
const customSymbol = Symbol("custom");
3+
>customSymbol : Symbol(customSymbol, Decl(file.js, 0, 5))
4+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
5+
6+
// This is a common pattern in Node’s built-in modules:
7+
module.exports = {
8+
>module.exports : Symbol(module.exports, Decl(file.js, 0, 0))
9+
>module : Symbol(module, Decl(file.js, 0, 38))
10+
>exports : Symbol(module.exports, Decl(file.js, 0, 0))
11+
12+
customSymbol,
13+
>customSymbol : Symbol(customSymbol, Decl(file.js, 3, 18))
14+
15+
};
16+
17+
exports.customSymbol2 = Symbol("custom");
18+
>exports.customSymbol2 : Symbol(customSymbol2, Decl(file.js, 5, 2))
19+
>exports : Symbol(customSymbol2, Decl(file.js, 5, 2))
20+
>customSymbol2 : Symbol(customSymbol2, Decl(file.js, 5, 2))
21+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/compiler/file.js ===
2+
const customSymbol = Symbol("custom");
3+
>customSymbol : unique symbol
4+
>Symbol("custom") : unique symbol
5+
>Symbol : SymbolConstructor
6+
>"custom" : "custom"
7+
8+
// This is a common pattern in Node’s built-in modules:
9+
module.exports = {
10+
>module.exports = { customSymbol,} : typeof module.exports
11+
>module.exports : typeof module.exports
12+
>module : { exports: typeof module.exports; }
13+
>exports : typeof module.exports
14+
>{ customSymbol,} : { customSymbol: symbol; }
15+
16+
customSymbol,
17+
>customSymbol : symbol
18+
19+
};
20+
21+
exports.customSymbol2 = Symbol("custom");
22+
>exports.customSymbol2 = Symbol("custom") : unique symbol
23+
>exports.customSymbol2 : unique symbol
24+
>exports : typeof import("tests/cases/compiler/file")
25+
>customSymbol2 : unique symbol
26+
>Symbol("custom") : unique symbol
27+
>Symbol : SymbolConstructor
28+
>"custom" : "custom"
29+

tests/baselines/reference/jsExportMemberMergedWithModuleAugmentation3.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
=== /x.js ===
22
module.exports.x = 1;
33
>module.exports.x = 1 : 1
4-
>module.exports.x : number
4+
>module.exports.x : 1
55
>module.exports : typeof import("/y")
66
>module : { exports: typeof import("/y"); }
77
>exports : typeof import("/y")
8-
>x : number
8+
>x : 1
99
>1 : 1
1010

1111
module.exports = require("./y.js");

tests/baselines/reference/jsFileCompilationExternalPackageError.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ var a = 10;
2121
=== tests/cases/compiler/node_modules/c.js ===
2222
exports.a = 10;
2323
>exports.a = 10 : 10
24-
>exports.a : number
24+
>exports.a : 10
2525
>exports : typeof import("tests/cases/compiler/node_modules/c")
26-
>a : number
26+
>a : 10
2727
>10 : 10
2828

2929
c = 10;

0 commit comments

Comments
 (0)