From 05dc981ebfc7c86ff6c409d51639b2156bbc5f45 Mon Sep 17 00:00:00 2001
From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
Date: Tue, 10 Apr 2018 14:26:38 -0700
Subject: [PATCH] Allow exports assignments
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.
---
src/compiler/checker.ts | 8 ++-
.../exportNestedNamespaces2.errors.txt | 10 +--
.../reference/exportNestedNamespaces2.types | 4 +-
.../reference/moduleExportAlias.types | 16 ++---
.../reference/moduleExportAlias2.symbols | 51 +++++++++++++++
.../reference/moduleExportAlias2.types | 65 +++++++++++++++++++
.../typeFromPropertyAssignment19.errors.txt | 20 ------
.../typeFromPropertyAssignment19.types | 2 +-
.../conformance/salsa/moduleExportAlias2.ts | 19 ++++++
9 files changed, 153 insertions(+), 42 deletions(-)
create mode 100644 tests/baselines/reference/moduleExportAlias2.symbols
create mode 100644 tests/baselines/reference/moduleExportAlias2.types
delete mode 100644 tests/baselines/reference/typeFromPropertyAssignment19.errors.txt
create mode 100644 tests/cases/conformance/salsa/moduleExportAlias2.ts
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 42e7e8f7b16f8..f2a5c1f913077 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -13879,7 +13879,8 @@ namespace ts {
const assignmentKind = getAssignmentTargetKind(node);
if (assignmentKind) {
- if (!(localOrExportSymbol.flags & SymbolFlags.Variable)) {
+ if (!(localOrExportSymbol.flags & SymbolFlags.Variable) &&
+ !(isInJavaScriptFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) {
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable, symbolToString(symbol));
return unknownType;
}
@@ -19852,8 +19853,9 @@ namespace ts {
// VarExpr = ValueExpr
// requires VarExpr to be classified as a reference
// A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1)
- // and the type of the non - compound operation to be assignable to the type of VarExpr.
- if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)) {
+ // and the type of the non-compound operation to be assignable to the type of VarExpr.
+ if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)
+ && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
checkTypeAssignableTo(valueType, leftType, left, /*headMessage*/ undefined);
}
diff --git a/tests/baselines/reference/exportNestedNamespaces2.errors.txt b/tests/baselines/reference/exportNestedNamespaces2.errors.txt
index 7b4b3284f7223..38dc2fc2c608d 100644
--- a/tests/baselines/reference/exportNestedNamespaces2.errors.txt
+++ b/tests/baselines/reference/exportNestedNamespaces2.errors.txt
@@ -1,7 +1,5 @@
-tests/cases/conformance/salsa/first.js(1,1): error TS2539: Cannot assign to '"tests/cases/conformance/salsa/first"' because it is not a variable.
tests/cases/conformance/salsa/first.js(1,11): error TS2304: Cannot find name 'require'.
tests/cases/conformance/salsa/first.js(2,9): error TS2339: Property 'formatters' does not exist on type 'typeof import("tests/cases/conformance/salsa/first")'.
-tests/cases/conformance/salsa/second.js(1,1): error TS2539: Cannot assign to '"tests/cases/conformance/salsa/second"' because it is not a variable.
tests/cases/conformance/salsa/second.js(1,11): error TS2304: Cannot find name 'require'.
tests/cases/conformance/salsa/second.js(2,9): error TS2339: Property 'formatters' does not exist on type 'typeof import("tests/cases/conformance/salsa/second")'.
@@ -9,10 +7,8 @@ tests/cases/conformance/salsa/second.js(2,9): error TS2339: Property 'formatters
==== tests/cases/conformance/salsa/mod.js (0 errors) ====
// Based on a pattern from adonis
exports.formatters = {}
-==== tests/cases/conformance/salsa/first.js (3 errors) ====
+==== tests/cases/conformance/salsa/first.js (2 errors) ====
exports = require('./mod')
- ~~~~~~~
-!!! error TS2539: Cannot assign to '"tests/cases/conformance/salsa/first"' because it is not a variable.
~~~~~~~
!!! error TS2304: Cannot find name 'require'.
exports.formatters.j = function (v) {
@@ -20,10 +16,8 @@ tests/cases/conformance/salsa/second.js(2,9): error TS2339: Property 'formatters
!!! error TS2339: Property 'formatters' does not exist on type 'typeof import("tests/cases/conformance/salsa/first")'.
return v
}
-==== tests/cases/conformance/salsa/second.js (3 errors) ====
+==== tests/cases/conformance/salsa/second.js (2 errors) ====
exports = require('./mod')
- ~~~~~~~
-!!! error TS2539: Cannot assign to '"tests/cases/conformance/salsa/second"' because it is not a variable.
~~~~~~~
!!! error TS2304: Cannot find name 'require'.
exports.formatters.o = function (v) {
diff --git a/tests/baselines/reference/exportNestedNamespaces2.types b/tests/baselines/reference/exportNestedNamespaces2.types
index e15395866506b..5d32eb29215e4 100644
--- a/tests/baselines/reference/exportNestedNamespaces2.types
+++ b/tests/baselines/reference/exportNestedNamespaces2.types
@@ -10,7 +10,7 @@ exports.formatters = {}
=== tests/cases/conformance/salsa/first.js ===
exports = require('./mod')
>exports = require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/first")
>require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
>require : any
>'./mod' : "./mod"
@@ -31,7 +31,7 @@ exports.formatters.j = function (v) {
=== tests/cases/conformance/salsa/second.js ===
exports = require('./mod')
>exports = require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/second")
>require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
>require : any
>'./mod' : "./mod"
diff --git a/tests/baselines/reference/moduleExportAlias.types b/tests/baselines/reference/moduleExportAlias.types
index ce4e1a8fe7324..924f02d7fa866 100644
--- a/tests/baselines/reference/moduleExportAlias.types
+++ b/tests/baselines/reference/moduleExportAlias.types
@@ -147,7 +147,7 @@ module.exports.func4 = function () { };
var multipleDeclarationAlias1 = exports = module.exports;
>multipleDeclarationAlias1 : any
>exports = module.exports : any
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports : any
>module : any
>exports : any
@@ -212,7 +212,7 @@ var multipleDeclarationAlias5 = module.exports = exports = {};
>module : any
>exports : any
>exports = {} : {}
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>{} : {}
multipleDeclarationAlias5.func9 = function () { };
@@ -225,7 +225,7 @@ multipleDeclarationAlias5.func9 = function () { };
var multipleDeclarationAlias6 = exports = module.exports = {};
>multipleDeclarationAlias6 : { [x: string]: any; }
>exports = module.exports = {} : { [x: string]: any; }
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = {} : { [x: string]: any; }
>module.exports : any
>module : any
@@ -241,7 +241,7 @@ multipleDeclarationAlias6.func10 = function () { };
exports = module.exports = someOtherVariable = {};
>exports = module.exports = someOtherVariable = {} : {}
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = someOtherVariable = {} : {}
>module.exports : any
>module : any
@@ -268,7 +268,7 @@ module.exports.func12 = function () { };
exports = module.exports = someOtherVariable = {};
>exports = module.exports = someOtherVariable = {} : {}
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = someOtherVariable = {} : {}
>module.exports : any
>module : any
@@ -295,7 +295,7 @@ module.exports.func12 = function () { };
exports = module.exports = {};
>exports = module.exports = {} : { [x: string]: any; }
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = {} : { [x: string]: any; }
>module.exports : any
>module : any
@@ -320,7 +320,7 @@ module.exports.func14 = function () { };
exports = module.exports = {};
>exports = module.exports = {} : { [x: string]: any; }
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = {} : { [x: string]: any; }
>module.exports : any
>module : any
@@ -349,7 +349,7 @@ module.exports = exports = {};
>module : any
>exports : any
>exports = {} : {}
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/b")
>{} : {}
exports.func17 = function () { };
diff --git a/tests/baselines/reference/moduleExportAlias2.symbols b/tests/baselines/reference/moduleExportAlias2.symbols
new file mode 100644
index 0000000000000..52e089050b171
--- /dev/null
+++ b/tests/baselines/reference/moduleExportAlias2.symbols
@@ -0,0 +1,51 @@
+=== tests/cases/conformance/salsa/index.js ===
+///
+const C = require("./semver")
+>C : Symbol(C, Decl(index.js, 1, 5))
+>require : Symbol(require, Decl(node.d.ts, 0, 0))
+>"./semver" : Symbol("tests/cases/conformance/salsa/semver", Decl(semver.js, 0, 0))
+
+var two = C.f(1)
+>two : Symbol(two, Decl(index.js, 2, 3))
+>C.f : Symbol(f, Decl(semver.js, 1, 28))
+>C : Symbol(C, Decl(index.js, 1, 5))
+>f : Symbol(f, Decl(semver.js, 1, 28))
+
+var c = new C
+>c : Symbol(c, Decl(index.js, 3, 3))
+>C : Symbol(C, Decl(index.js, 1, 5))
+
+=== tests/cases/conformance/salsa/node.d.ts ===
+declare function require(name: string): any;
+>require : Symbol(require, Decl(node.d.ts, 0, 0))
+>name : Symbol(name, Decl(node.d.ts, 0, 25))
+
+declare var exports: any;
+>exports : Symbol(exports, Decl(node.d.ts, 1, 11))
+
+declare var module: { exports: any };
+>module : Symbol(module, Decl(node.d.ts, 2, 11))
+>exports : Symbol(exports, Decl(node.d.ts, 2, 21))
+
+=== tests/cases/conformance/salsa/semver.js ===
+///
+exports = module.exports = C
+>exports : Symbol("tests/cases/conformance/salsa/semver", Decl(semver.js, 0, 0))
+>module.exports : Symbol(exports, Decl(node.d.ts, 2, 21))
+>module : Symbol(export=, Decl(semver.js, 1, 9))
+>exports : Symbol(export=, Decl(semver.js, 1, 9))
+>C : Symbol(C, Decl(semver.js, 2, 22))
+
+exports.f = n => n + 1
+>exports.f : Symbol(f, Decl(semver.js, 1, 28))
+>exports : Symbol(f, Decl(semver.js, 1, 28))
+>f : Symbol(f, Decl(semver.js, 1, 28))
+>n : Symbol(n, Decl(semver.js, 2, 11))
+>n : Symbol(n, Decl(semver.js, 2, 11))
+
+function C() {
+>C : Symbol(C, Decl(semver.js, 2, 22))
+
+ this.p = 1
+>p : Symbol(C.p, Decl(semver.js, 3, 14))
+}
diff --git a/tests/baselines/reference/moduleExportAlias2.types b/tests/baselines/reference/moduleExportAlias2.types
new file mode 100644
index 0000000000000..c76c2c33c4553
--- /dev/null
+++ b/tests/baselines/reference/moduleExportAlias2.types
@@ -0,0 +1,65 @@
+=== tests/cases/conformance/salsa/index.js ===
+///
+const C = require("./semver")
+>C : typeof C
+>require("./semver") : typeof C
+>require : (name: string) => any
+>"./semver" : "./semver"
+
+var two = C.f(1)
+>two : any
+>C.f(1) : any
+>C.f : (n: any) => any
+>C : typeof C
+>f : (n: any) => any
+>1 : 1
+
+var c = new C
+>c : C
+>new C : C
+>C : typeof C
+
+=== tests/cases/conformance/salsa/node.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/salsa/semver.js ===
+///
+exports = module.exports = C
+>exports = module.exports = C : typeof C
+>exports : typeof import("tests/cases/conformance/salsa/semver")
+>module.exports = C : typeof C
+>module.exports : any
+>module : { exports: any; }
+>exports : any
+>C : typeof C
+
+exports.f = n => n + 1
+>exports.f = n => n + 1 : (n: any) => any
+>exports.f : (n: any) => any
+>exports : typeof import("tests/cases/conformance/salsa/semver")
+>f : (n: any) => any
+>n => n + 1 : (n: any) => any
+>n : any
+>n + 1 : any
+>n : any
+>1 : 1
+
+function C() {
+>C : typeof C
+
+ this.p = 1
+>this.p = 1 : 1
+>this.p : any
+>this : any
+>p : any
+>1 : 1
+}
diff --git a/tests/baselines/reference/typeFromPropertyAssignment19.errors.txt b/tests/baselines/reference/typeFromPropertyAssignment19.errors.txt
deleted file mode 100644
index d43728d360b13..0000000000000
--- a/tests/baselines/reference/typeFromPropertyAssignment19.errors.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-tests/cases/conformance/salsa/semver.js(2,1): error TS2539: Cannot assign to '"tests/cases/conformance/salsa/semver"' because it is not a variable.
-
-
-==== tests/cases/conformance/salsa/index.js (0 errors) ====
- ///
- const C = require("./semver")
- var two = C.f(1)
-
-==== tests/cases/conformance/salsa/types.d.ts (0 errors) ====
- declare var require: any;
- declare var module: any;
-==== tests/cases/conformance/salsa/semver.js (1 errors) ====
- ///
- exports = module.exports = C
- ~~~~~~~
-!!! error TS2539: Cannot assign to '"tests/cases/conformance/salsa/semver"' because it is not a variable.
- C.f = n => n + 1
- function C() {
- this.p = 1
- }
\ No newline at end of file
diff --git a/tests/baselines/reference/typeFromPropertyAssignment19.types b/tests/baselines/reference/typeFromPropertyAssignment19.types
index ee219e7164ae2..b0200fc231109 100644
--- a/tests/baselines/reference/typeFromPropertyAssignment19.types
+++ b/tests/baselines/reference/typeFromPropertyAssignment19.types
@@ -25,7 +25,7 @@ declare var module: any;
///
exports = module.exports = C
>exports = module.exports = C : typeof C
->exports : any
+>exports : typeof import("tests/cases/conformance/salsa/semver")
>module.exports = C : typeof C
>module.exports : any
>module : any
diff --git a/tests/cases/conformance/salsa/moduleExportAlias2.ts b/tests/cases/conformance/salsa/moduleExportAlias2.ts
new file mode 100644
index 0000000000000..027cc83e6f1e2
--- /dev/null
+++ b/tests/cases/conformance/salsa/moduleExportAlias2.ts
@@ -0,0 +1,19 @@
+// @checkJs: true
+// @allowJS: true
+// @noEmit: true
+// @Filename: node.d.ts
+declare function require(name: string): any;
+declare var exports: any;
+declare var module: { exports: any };
+// @Filename: semver.js
+///
+exports = module.exports = C
+exports.f = n => n + 1
+function C() {
+ this.p = 1
+}
+// @filename: index.js
+///
+const C = require("./semver")
+var two = C.f(1)
+var c = new C