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