Skip to content

Commit 9ceb113

Browse files
authored
Allow exports assignments (#23319)
1. Allow assignment to `exports`. 2. The type of the rhs is not checked against the type of `exports` since they are aliased declarations. To support more complex patterns like `exports = c.name = c`, we may have to treat `c.name` as a declaration. That will be more complicated than this PR.
1 parent 01b22ff commit 9ceb113

9 files changed

+153
-42
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13882,7 +13882,8 @@ namespace ts {
1388213882
const assignmentKind = getAssignmentTargetKind(node);
1388313883

1388413884
if (assignmentKind) {
13885-
if (!(localOrExportSymbol.flags & SymbolFlags.Variable)) {
13885+
if (!(localOrExportSymbol.flags & SymbolFlags.Variable) &&
13886+
!(isInJavaScriptFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) {
1388613887
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable, symbolToString(symbol));
1388713888
return unknownType;
1388813889
}
@@ -19858,8 +19859,9 @@ namespace ts {
1985819859
// VarExpr = ValueExpr
1985919860
// requires VarExpr to be classified as a reference
1986019861
// A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1)
19861-
// and the type of the non - compound operation to be assignable to the type of VarExpr.
19862-
if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)) {
19862+
// and the type of the non-compound operation to be assignable to the type of VarExpr.
19863+
if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)
19864+
&& (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
1986319865
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
1986419866
checkTypeAssignableTo(valueType, leftType, left, /*headMessage*/ undefined);
1986519867
}

tests/baselines/reference/exportNestedNamespaces2.errors.txt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
1-
tests/cases/conformance/salsa/first.js(1,1): error TS2539: Cannot assign to '"tests/cases/conformance/salsa/first"' because it is not a variable.
21
tests/cases/conformance/salsa/first.js(1,11): error TS2304: Cannot find name 'require'.
32
tests/cases/conformance/salsa/first.js(2,9): error TS2339: Property 'formatters' does not exist on type 'typeof import("tests/cases/conformance/salsa/first")'.
4-
tests/cases/conformance/salsa/second.js(1,1): error TS2539: Cannot assign to '"tests/cases/conformance/salsa/second"' because it is not a variable.
53
tests/cases/conformance/salsa/second.js(1,11): error TS2304: Cannot find name 'require'.
64
tests/cases/conformance/salsa/second.js(2,9): error TS2339: Property 'formatters' does not exist on type 'typeof import("tests/cases/conformance/salsa/second")'.
75

86

97
==== tests/cases/conformance/salsa/mod.js (0 errors) ====
108
// Based on a pattern from adonis
119
exports.formatters = {}
12-
==== tests/cases/conformance/salsa/first.js (3 errors) ====
10+
==== tests/cases/conformance/salsa/first.js (2 errors) ====
1311
exports = require('./mod')
14-
~~~~~~~
15-
!!! error TS2539: Cannot assign to '"tests/cases/conformance/salsa/first"' because it is not a variable.
1612
~~~~~~~
1713
!!! error TS2304: Cannot find name 'require'.
1814
exports.formatters.j = function (v) {
1915
~~~~~~~~~~
2016
!!! error TS2339: Property 'formatters' does not exist on type 'typeof import("tests/cases/conformance/salsa/first")'.
2117
return v
2218
}
23-
==== tests/cases/conformance/salsa/second.js (3 errors) ====
19+
==== tests/cases/conformance/salsa/second.js (2 errors) ====
2420
exports = require('./mod')
25-
~~~~~~~
26-
!!! error TS2539: Cannot assign to '"tests/cases/conformance/salsa/second"' because it is not a variable.
2721
~~~~~~~
2822
!!! error TS2304: Cannot find name 'require'.
2923
exports.formatters.o = function (v) {

tests/baselines/reference/exportNestedNamespaces2.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ exports.formatters = {}
1010
=== tests/cases/conformance/salsa/first.js ===
1111
exports = require('./mod')
1212
>exports = require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
13-
>exports : any
13+
>exports : typeof import("tests/cases/conformance/salsa/first")
1414
>require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
1515
>require : any
1616
>'./mod' : "./mod"
@@ -31,7 +31,7 @@ exports.formatters.j = function (v) {
3131
=== tests/cases/conformance/salsa/second.js ===
3232
exports = require('./mod')
3333
>exports = require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
34-
>exports : any
34+
>exports : typeof import("tests/cases/conformance/salsa/second")
3535
>require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
3636
>require : any
3737
>'./mod' : "./mod"

tests/baselines/reference/moduleExportAlias.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ module.exports.func4 = function () { };
147147
var multipleDeclarationAlias1 = exports = module.exports;
148148
>multipleDeclarationAlias1 : any
149149
>exports = module.exports : any
150-
>exports : any
150+
>exports : typeof import("tests/cases/conformance/salsa/b")
151151
>module.exports : any
152152
>module : any
153153
>exports : any
@@ -212,7 +212,7 @@ var multipleDeclarationAlias5 = module.exports = exports = {};
212212
>module : any
213213
>exports : any
214214
>exports = {} : {}
215-
>exports : any
215+
>exports : typeof import("tests/cases/conformance/salsa/b")
216216
>{} : {}
217217

218218
multipleDeclarationAlias5.func9 = function () { };
@@ -225,7 +225,7 @@ multipleDeclarationAlias5.func9 = function () { };
225225
var multipleDeclarationAlias6 = exports = module.exports = {};
226226
>multipleDeclarationAlias6 : { [x: string]: any; }
227227
>exports = module.exports = {} : { [x: string]: any; }
228-
>exports : any
228+
>exports : typeof import("tests/cases/conformance/salsa/b")
229229
>module.exports = {} : { [x: string]: any; }
230230
>module.exports : any
231231
>module : any
@@ -241,7 +241,7 @@ multipleDeclarationAlias6.func10 = function () { };
241241

242242
exports = module.exports = someOtherVariable = {};
243243
>exports = module.exports = someOtherVariable = {} : {}
244-
>exports : any
244+
>exports : typeof import("tests/cases/conformance/salsa/b")
245245
>module.exports = someOtherVariable = {} : {}
246246
>module.exports : any
247247
>module : any
@@ -268,7 +268,7 @@ module.exports.func12 = function () { };
268268

269269
exports = module.exports = someOtherVariable = {};
270270
>exports = module.exports = someOtherVariable = {} : {}
271-
>exports : any
271+
>exports : typeof import("tests/cases/conformance/salsa/b")
272272
>module.exports = someOtherVariable = {} : {}
273273
>module.exports : any
274274
>module : any
@@ -295,7 +295,7 @@ module.exports.func12 = function () { };
295295

296296
exports = module.exports = {};
297297
>exports = module.exports = {} : { [x: string]: any; }
298-
>exports : any
298+
>exports : typeof import("tests/cases/conformance/salsa/b")
299299
>module.exports = {} : { [x: string]: any; }
300300
>module.exports : any
301301
>module : any
@@ -320,7 +320,7 @@ module.exports.func14 = function () { };
320320

321321
exports = module.exports = {};
322322
>exports = module.exports = {} : { [x: string]: any; }
323-
>exports : any
323+
>exports : typeof import("tests/cases/conformance/salsa/b")
324324
>module.exports = {} : { [x: string]: any; }
325325
>module.exports : any
326326
>module : any
@@ -349,7 +349,7 @@ module.exports = exports = {};
349349
>module : any
350350
>exports : any
351351
>exports = {} : {}
352-
>exports : any
352+
>exports : typeof import("tests/cases/conformance/salsa/b")
353353
>{} : {}
354354

355355
exports.func17 = function () { };
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=== tests/cases/conformance/salsa/index.js ===
2+
/// <reference path='node.d.ts' />
3+
const C = require("./semver")
4+
>C : Symbol(C, Decl(index.js, 1, 5))
5+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
6+
>"./semver" : Symbol("tests/cases/conformance/salsa/semver", Decl(semver.js, 0, 0))
7+
8+
var two = C.f(1)
9+
>two : Symbol(two, Decl(index.js, 2, 3))
10+
>C.f : Symbol(f, Decl(semver.js, 1, 28))
11+
>C : Symbol(C, Decl(index.js, 1, 5))
12+
>f : Symbol(f, Decl(semver.js, 1, 28))
13+
14+
var c = new C
15+
>c : Symbol(c, Decl(index.js, 3, 3))
16+
>C : Symbol(C, Decl(index.js, 1, 5))
17+
18+
=== tests/cases/conformance/salsa/node.d.ts ===
19+
declare function require(name: string): any;
20+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
21+
>name : Symbol(name, Decl(node.d.ts, 0, 25))
22+
23+
declare var exports: any;
24+
>exports : Symbol(exports, Decl(node.d.ts, 1, 11))
25+
26+
declare var module: { exports: any };
27+
>module : Symbol(module, Decl(node.d.ts, 2, 11))
28+
>exports : Symbol(exports, Decl(node.d.ts, 2, 21))
29+
30+
=== tests/cases/conformance/salsa/semver.js ===
31+
/// <reference path='node.d.ts' />
32+
exports = module.exports = C
33+
>exports : Symbol("tests/cases/conformance/salsa/semver", Decl(semver.js, 0, 0))
34+
>module.exports : Symbol(exports, Decl(node.d.ts, 2, 21))
35+
>module : Symbol(export=, Decl(semver.js, 1, 9))
36+
>exports : Symbol(export=, Decl(semver.js, 1, 9))
37+
>C : Symbol(C, Decl(semver.js, 2, 22))
38+
39+
exports.f = n => n + 1
40+
>exports.f : Symbol(f, Decl(semver.js, 1, 28))
41+
>exports : Symbol(f, Decl(semver.js, 1, 28))
42+
>f : Symbol(f, Decl(semver.js, 1, 28))
43+
>n : Symbol(n, Decl(semver.js, 2, 11))
44+
>n : Symbol(n, Decl(semver.js, 2, 11))
45+
46+
function C() {
47+
>C : Symbol(C, Decl(semver.js, 2, 22))
48+
49+
this.p = 1
50+
>p : Symbol(C.p, Decl(semver.js, 3, 14))
51+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/conformance/salsa/index.js ===
2+
/// <reference path='node.d.ts' />
3+
const C = require("./semver")
4+
>C : typeof C
5+
>require("./semver") : typeof C
6+
>require : (name: string) => any
7+
>"./semver" : "./semver"
8+
9+
var two = C.f(1)
10+
>two : any
11+
>C.f(1) : any
12+
>C.f : (n: any) => any
13+
>C : typeof C
14+
>f : (n: any) => any
15+
>1 : 1
16+
17+
var c = new C
18+
>c : C
19+
>new C : C
20+
>C : typeof C
21+
22+
=== tests/cases/conformance/salsa/node.d.ts ===
23+
declare function require(name: string): any;
24+
>require : (name: string) => any
25+
>name : string
26+
27+
declare var exports: any;
28+
>exports : any
29+
30+
declare var module: { exports: any };
31+
>module : { exports: any; }
32+
>exports : any
33+
34+
=== tests/cases/conformance/salsa/semver.js ===
35+
/// <reference path='node.d.ts' />
36+
exports = module.exports = C
37+
>exports = module.exports = C : typeof C
38+
>exports : typeof import("tests/cases/conformance/salsa/semver")
39+
>module.exports = C : typeof C
40+
>module.exports : any
41+
>module : { exports: any; }
42+
>exports : any
43+
>C : typeof C
44+
45+
exports.f = n => n + 1
46+
>exports.f = n => n + 1 : (n: any) => any
47+
>exports.f : (n: any) => any
48+
>exports : typeof import("tests/cases/conformance/salsa/semver")
49+
>f : (n: any) => any
50+
>n => n + 1 : (n: any) => any
51+
>n : any
52+
>n + 1 : any
53+
>n : any
54+
>1 : 1
55+
56+
function C() {
57+
>C : typeof C
58+
59+
this.p = 1
60+
>this.p = 1 : 1
61+
>this.p : any
62+
>this : any
63+
>p : any
64+
>1 : 1
65+
}

tests/baselines/reference/typeFromPropertyAssignment19.errors.txt

Lines changed: 0 additions & 20 deletions
This file was deleted.

tests/baselines/reference/typeFromPropertyAssignment19.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ declare var module: any;
2525
/// <reference path='./types.d.ts'/>
2626
exports = module.exports = C
2727
>exports = module.exports = C : typeof C
28-
>exports : any
28+
>exports : typeof import("tests/cases/conformance/salsa/semver")
2929
>module.exports = C : typeof C
3030
>module.exports : any
3131
>module : any
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @checkJs: true
2+
// @allowJS: true
3+
// @noEmit: true
4+
// @Filename: node.d.ts
5+
declare function require(name: string): any;
6+
declare var exports: any;
7+
declare var module: { exports: any };
8+
// @Filename: semver.js
9+
/// <reference path='node.d.ts' />
10+
exports = module.exports = C
11+
exports.f = n => n + 1
12+
function C() {
13+
this.p = 1
14+
}
15+
// @filename: index.js
16+
/// <reference path='node.d.ts' />
17+
const C = require("./semver")
18+
var two = C.f(1)
19+
var c = new C

0 commit comments

Comments
 (0)