diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b8c134b44a8..98bc54e88c3 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -279,6 +279,7 @@ namespace ts { Debug.assert(!hasDynamicName(node)); const isDefaultExport = node.flags & NodeFlags.Default; + // The exported symbol for an export default function/class node is always named "default" const name = isDefaultExport && parent ? "default" : getDeclarationName(node); @@ -1429,7 +1430,7 @@ namespace ts { function bindModuleExportsAssignment(node: BinaryExpression) { // 'module.exports = expr' assignment setCommonJsModuleIndicator(node); - bindExportAssignment(node); + declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.Export | SymbolFlags.ValueModule, SymbolFlags.None); } function bindThisPropertyAssignment(node: BinaryExpression) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 44713b2c1db..baa57ddd014 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -888,8 +888,12 @@ namespace ts { function getTargetOfImportClause(node: ImportClause): Symbol { const moduleSymbol = resolveExternalModuleName(node, (node.parent).moduleSpecifier); + if (moduleSymbol) { - const exportDefaultSymbol = resolveSymbol(moduleSymbol.exports["default"]); + const exportDefaultSymbol = moduleSymbol.exports["export="] ? + getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") : + resolveSymbol(moduleSymbol.exports["default"]); + if (!exportDefaultSymbol && !allowSyntheticDefaultImports) { error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); } @@ -960,8 +964,15 @@ namespace ts { if (targetSymbol) { const name = specifier.propertyName || specifier.name; if (name.text) { + let symbolFromVariable: Symbol; + // First check if module was specified with "export=". If so, get the member from the resolved type + if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports["export="]) { + symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.text); + } + else { + symbolFromVariable = getPropertyOfVariable(targetSymbol, name.text); + } const symbolFromModule = getExportOfModule(targetSymbol, name.text); - const symbolFromVariable = getPropertyOfVariable(targetSymbol, name.text); const symbol = symbolFromModule && symbolFromVariable ? combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : symbolFromModule || symbolFromVariable; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fa9305ae722..42f439e0ded 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1107,6 +1107,9 @@ namespace ts { /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property /// assignments we treat as special in the binder export function getSpecialPropertyAssignmentKind(expression: Node): SpecialPropertyAssignmentKind { + if (!isInJavaScriptFile(expression)) { + return SpecialPropertyAssignmentKind.None; + } if (expression.kind !== SyntaxKind.BinaryExpression) { return SpecialPropertyAssignmentKind.None; } diff --git a/tests/baselines/reference/exportEqualsDefaultProperty.js b/tests/baselines/reference/exportEqualsDefaultProperty.js new file mode 100644 index 00000000000..6d75ef5d95d --- /dev/null +++ b/tests/baselines/reference/exportEqualsDefaultProperty.js @@ -0,0 +1,27 @@ +//// [tests/cases/compiler/exportEqualsDefaultProperty.ts] //// + +//// [exp.ts] + +var x = { + "greeting": "hello, world", + "default": 42 +}; + +export = x + +//// [imp.ts] +import foo from "./exp"; +foo.toExponential(2); + + +//// [exp.js] +"use strict"; +var x = { + "greeting": "hello, world", + "default": 42 +}; +module.exports = x; +//// [imp.js] +"use strict"; +var exp_1 = require("./exp"); +exp_1["default"].toExponential(2); diff --git a/tests/baselines/reference/exportEqualsDefaultProperty.symbols b/tests/baselines/reference/exportEqualsDefaultProperty.symbols new file mode 100644 index 00000000000..54bbbde6956 --- /dev/null +++ b/tests/baselines/reference/exportEqualsDefaultProperty.symbols @@ -0,0 +1,21 @@ +=== tests/cases/compiler/exp.ts === + +var x = { +>x : Symbol(x, Decl(exp.ts, 1, 3)) + + "greeting": "hello, world", + "default": 42 +}; + +export = x +>x : Symbol(x, Decl(exp.ts, 1, 3)) + +=== tests/cases/compiler/imp.ts === +import foo from "./exp"; +>foo : Symbol(foo, Decl(imp.ts, 0, 6)) + +foo.toExponential(2); +>foo.toExponential : Symbol(Number.toExponential, Decl(lib.d.ts, --, --)) +>foo : Symbol(foo, Decl(imp.ts, 0, 6)) +>toExponential : Symbol(Number.toExponential, Decl(lib.d.ts, --, --)) + diff --git a/tests/baselines/reference/exportEqualsDefaultProperty.types b/tests/baselines/reference/exportEqualsDefaultProperty.types new file mode 100644 index 00000000000..31a6c8052f9 --- /dev/null +++ b/tests/baselines/reference/exportEqualsDefaultProperty.types @@ -0,0 +1,28 @@ +=== tests/cases/compiler/exp.ts === + +var x = { +>x : { "greeting": string; "default": number; } +>{ "greeting": "hello, world", "default": 42} : { "greeting": string; "default": number; } + + "greeting": "hello, world", +>"hello, world" : string + + "default": 42 +>42 : number + +}; + +export = x +>x : { "greeting": string; "default": number; } + +=== tests/cases/compiler/imp.ts === +import foo from "./exp"; +>foo : number + +foo.toExponential(2); +>foo.toExponential(2) : string +>foo.toExponential : (fractionDigits?: number) => string +>foo : number +>toExponential : (fractionDigits?: number) => string +>2 : number + diff --git a/tests/cases/compiler/exportEqualsDefaultProperty.ts b/tests/cases/compiler/exportEqualsDefaultProperty.ts new file mode 100644 index 00000000000..1adce3cabe1 --- /dev/null +++ b/tests/cases/compiler/exportEqualsDefaultProperty.ts @@ -0,0 +1,12 @@ + +// @Filename: exp.ts +var x = { + "greeting": "hello, world", + "default": 42 +}; + +export = x + +// @Filename: imp.ts +import foo from "./exp"; +foo.toExponential(2); diff --git a/tests/cases/fourslash/javascriptModules20.ts b/tests/cases/fourslash/javascriptModules20.ts new file mode 100644 index 00000000000..7ef5c73e1c3 --- /dev/null +++ b/tests/cases/fourslash/javascriptModules20.ts @@ -0,0 +1,13 @@ +/// +// @allowJs: true + +// @Filename: mod.js +//// function foo() { return {a: true}; } +//// module.exports = foo(); + +// @Filename: app.js +//// import * as mod from "./mod" +//// mod./**/ + +goTo.marker(); +verify.completionListContains('a'); diff --git a/tests/cases/fourslash/javascriptModules21.ts b/tests/cases/fourslash/javascriptModules21.ts new file mode 100644 index 00000000000..3a046515924 --- /dev/null +++ b/tests/cases/fourslash/javascriptModules21.ts @@ -0,0 +1,14 @@ +/// +// @allowJs: true +// @module: system + +// @Filename: mod.js +//// function foo() { return {a: true}; } +//// module.exports = foo(); + +// @Filename: app.js +//// import mod from "./mod" +//// mod./**/ + +goTo.marker(); +verify.completionListContains('a'); diff --git a/tests/cases/fourslash/javascriptModules22.ts b/tests/cases/fourslash/javascriptModules22.ts new file mode 100644 index 00000000000..89fa99b5ea2 --- /dev/null +++ b/tests/cases/fourslash/javascriptModules22.ts @@ -0,0 +1,32 @@ +/// +// @allowJs: true + +// @Filename: mod.js +//// function foo() { return {a: "hello, world"}; } +//// module.exports = foo(); + +// @Filename: mod2.js +//// var x = {name: 'test'}; +//// (function createExport(obj){ +//// module.exports = { +//// "default": x, +//// "sausages": {eggs: 2} +//// }; +//// })(); + +// @Filename: app.js +//// import {a} from "./mod" +//// import def, {sausages} from "./mod2" +//// a./**/ + +goTo.marker(); +verify.completionListContains('toString'); + +edit.backspace(2); +edit.insert("def."); +verify.completionListContains("name"); + +edit.insert("name;\nsausages."); +verify.completionListContains("eggs"); +edit.insert("eggs;"); +verify.numberOfErrorsInCurrentFile(0); diff --git a/tests/cases/fourslash/javascriptModules23.ts b/tests/cases/fourslash/javascriptModules23.ts new file mode 100644 index 00000000000..eafbea87baa --- /dev/null +++ b/tests/cases/fourslash/javascriptModules23.ts @@ -0,0 +1,12 @@ +/// + +// @Filename: mod.ts +//// var foo = {a: "test"}; +//// export = foo; + +// @Filename: app.ts +//// import {a} from "./mod" +//// a./**/ + +goTo.marker(); +verify.completionListContains('toString'); diff --git a/tests/cases/fourslash/javascriptModules24.ts b/tests/cases/fourslash/javascriptModules24.ts new file mode 100644 index 00000000000..5a0dd892db7 --- /dev/null +++ b/tests/cases/fourslash/javascriptModules24.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: mod.ts +//// function foo() { return 42; } +//// namespace foo { +//// export function bar (a: string) { return a; } +//// } +//// export = foo; + +// @Filename: app.ts +//// import * as foo from "./mod" +//// foo/*1*/(); +//// foo.bar(/*2*/"test"); + +goTo.marker('1'); + +/**** BUG: Should be an error to invoke a call signature on a namespace import ****/ +//verify.errorExistsBeforeMarker('1'); +verify.quickInfoIs("(alias) foo(): number\nimport foo"); +goTo.marker('2'); +verify.signatureHelpArgumentCountIs(1);