diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d6a317b3203..3e5c85149d1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2303,7 +2303,17 @@ namespace ts { // When we create a property via 'exports.foo = bar', the 'exports.foo' property access // expression is the declaration setCommonJsModuleIndicator(node); - declareSymbol(file.symbol.exports, file.symbol, node.left, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); + const lhs = node.left as PropertyAccessEntityNameExpression; + const symbol = forEachIdentifierInEntityName(lhs.expression, (id, original, e) => { + if (isExportsOrModuleExportsOrAlias(file, e) || (isIdentifier(e) && e.escapedText === "module" && original === undefined)) { + return file.symbol; + } + Debug.assert(!!original); + const s = getJSInitializerSymbol(original); + addDeclarationToSymbol(s, id, SymbolFlags.Module | SymbolFlags.JSContainer); + return s; + }); + declareSymbol(symbol.exports, symbol, lhs, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); } function bindModuleExportsAssignment(node: BinaryExpression) { @@ -2460,14 +2470,14 @@ namespace ts { } } - function forEachIdentifierInEntityName(e: EntityNameExpression, action: (e: Identifier, symbol: Symbol) => Symbol): Symbol { + function forEachIdentifierInEntityName(e: EntityNameExpression, action: (e: Identifier, symbol: Symbol, k: EntityNameExpression) => Symbol): Symbol { if (isIdentifier(e)) { - return action(e, lookupSymbolForPropertyAccess(e)); + return action(e, lookupSymbolForPropertyAccess(e), e); } else { const s = getJSInitializerSymbol(forEachIdentifierInEntityName(e.expression, action)); Debug.assert(!!s && !!s.exports); - return action(e.name, s.exports.get(e.name.escapedText)); + return action(e.name, s.exports.get(e.name.escapedText), e); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c81b2dd791a..c00e6eb3b80 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1585,8 +1585,9 @@ namespace ts { } function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning === SymbolFlags.Namespace) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Namespace, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + const namespaceMeaning = SymbolFlags.Namespace | (isInJavaScriptFile(errorLocation) ? SymbolFlags.Value : 0); + if (meaning === namespaceMeaning) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); const parent = errorLocation.parent; if (symbol) { if (isQualifiedName(parent)) { @@ -1992,9 +1993,10 @@ namespace ts { return undefined; } + const namespaceMeaning = SymbolFlags.Namespace | (isInJavaScriptFile(name) ? meaning & SymbolFlags.Value : 0); let symbol: Symbol; if (name.kind === SyntaxKind.Identifier) { - const message = meaning === SymbolFlags.Namespace ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0; + const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0; symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors ? undefined : message, name, /*isUse*/ true); if (!symbol) { @@ -2004,7 +2006,7 @@ namespace ts { else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; - let namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors, /*dontResolveAlias*/ false, location); + let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); if (!namespace || nodeIsMissing(right)) { return undefined; } @@ -2012,7 +2014,7 @@ namespace ts { return namespace; } if (isInJavaScriptFile(name)) { - const initializer = getDeclaredJavascriptInitializer(namespace.valueDeclaration); + const initializer = getDeclaredJavascriptInitializer(namespace.valueDeclaration) || getAssignedJavascriptInitializer(namespace.valueDeclaration); if (initializer) { namespace = getSymbolOfNode(initializer); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2f4c80ecbe1..8947f8910d1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1488,7 +1488,8 @@ namespace ts { } export function getAssignedJavascriptInitializer(node: Node) { - return (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.BarBarToken || isPropertyAccessExpression(node)) && + return node && + (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.BarBarToken || isPropertyAccessExpression(node)) && node.parent && isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && (getJavascriptInitializer(node.parent.right) || getDefaultedJavascriptInitializer(node.parent.left as EntityNameExpression, node.parent.right)); @@ -1570,33 +1571,30 @@ namespace ts { if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return SpecialPropertyAssignmentKind.ThisProperty; } - if (isIdentifier(lhs.expression)) { - if (lhs.expression.escapedText === "exports") { - // exports.name = expr - return SpecialPropertyAssignmentKind.ExportsProperty; - } - else if (lhs.expression.escapedText === "module" && lhs.name.escapedText === "exports") { - // module.exports = expr - return SpecialPropertyAssignmentKind.ModuleExports; - } + else if (isIdentifier(lhs.expression) && lhs.expression.escapedText === "module" && lhs.name.escapedText === "exports") { + // module.exports = expr + return SpecialPropertyAssignmentKind.ModuleExports; } - if (isEntityNameExpression(lhs.expression)) { + else if (isEntityNameExpression(lhs.expression)) { if (lhs.name.escapedText === "prototype" && isObjectLiteralExpression(expr.right)) { // F.prototype = { ... } return SpecialPropertyAssignmentKind.Prototype; } - else if (isPropertyAccessExpression(lhs.expression)) { - // chained dot, e.g. x.y.z = expr; this var is the 'x.y' part - if (isIdentifier(lhs.expression.expression) && - lhs.expression.expression.escapedText === "module" && - lhs.expression.name.escapedText === "exports") { - // module.exports.name = expr - return SpecialPropertyAssignmentKind.ExportsProperty; - } - if (lhs.expression.name.escapedText === "prototype") { - // F.G....prototype.x = expr - return SpecialPropertyAssignmentKind.PrototypeProperty; - } + else if (isPropertyAccessExpression(lhs.expression) && lhs.expression.name.escapedText === "prototype") { + // F.G....prototype.x = expr + return SpecialPropertyAssignmentKind.PrototypeProperty; + } + + let nextToLast = lhs; + while (isPropertyAccessExpression(nextToLast.expression)) { + nextToLast = nextToLast.expression; + } + Debug.assert(isIdentifier(nextToLast.expression)); + const id = nextToLast.expression as Identifier; + if (id.escapedText === "exports" || + id.escapedText === "module" && nextToLast.name.escapedText === "exports") { + // exports.name = expr OR module.exports.name = expr + return SpecialPropertyAssignmentKind.ExportsProperty; } // F.G...x = expr return SpecialPropertyAssignmentKind.Property; diff --git a/tests/baselines/reference/exportNestedNamespaces.symbols b/tests/baselines/reference/exportNestedNamespaces.symbols new file mode 100644 index 00000000000..072b802f351 --- /dev/null +++ b/tests/baselines/reference/exportNestedNamespaces.symbols @@ -0,0 +1,74 @@ +=== tests/cases/conformance/salsa/mod.js === +exports.n = {}; +>exports.n : Symbol(n, Decl(mod.js, 0, 0)) +>exports : Symbol(n, Decl(mod.js, 0, 0)) +>n : Symbol(n, Decl(mod.js, 0, 0)) + +exports.n.K = function () { +>exports.n.K : Symbol(K, Decl(mod.js, 0, 15)) +>exports.n : Symbol(K, Decl(mod.js, 0, 15)) +>exports : Symbol("tests/cases/conformance/salsa/mod", Decl(mod.js, 0, 0)) +>n : Symbol(n, Decl(mod.js, 0, 0)) +>K : Symbol(K, Decl(mod.js, 0, 15)) + + this.x = 10; +>this : Symbol(__object, Decl(mod.js, 0, 11), Decl(mod.js, 1, 8)) +>x : Symbol((Anonymous function).x, Decl(mod.js, 1, 27)) +} +exports.Classic = class { +>exports.Classic : Symbol(Classic, Decl(mod.js, 3, 1)) +>exports : Symbol(Classic, Decl(mod.js, 3, 1)) +>Classic : Symbol(Classic, Decl(mod.js, 3, 1)) + + constructor() { + this.p = 1 +>this.p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) +>this : Symbol((Anonymous class), Decl(mod.js, 4, 17)) +>p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) + } +} + +=== tests/cases/conformance/salsa/use.js === +import * as s from './mod' +>s : Symbol(s, Decl(use.js, 0, 6)) + +var k = new s.n.K() +>k : Symbol(k, Decl(use.js, 2, 3)) +>s.n.K : Symbol(K, Decl(mod.js, 0, 15)) +>s.n : Symbol(s.n, Decl(mod.js, 0, 0)) +>s : Symbol(s, Decl(use.js, 0, 6)) +>n : Symbol(s.n, Decl(mod.js, 0, 0)) +>K : Symbol(K, Decl(mod.js, 0, 15)) + +k.x +>k.x : Symbol((Anonymous function).x, Decl(mod.js, 1, 27)) +>k : Symbol(k, Decl(use.js, 2, 3)) +>x : Symbol((Anonymous function).x, Decl(mod.js, 1, 27)) + +var classic = new s.Classic() +>classic : Symbol(classic, Decl(use.js, 4, 3)) +>s.Classic : Symbol(s.Classic, Decl(mod.js, 3, 1)) +>s : Symbol(s, Decl(use.js, 0, 6)) +>Classic : Symbol(s.Classic, Decl(mod.js, 3, 1)) + + +/** @param {s.n.K} c + @param {s.Classic} classic */ +function f(c, classic) { +>f : Symbol(f, Decl(use.js, 4, 29)) +>c : Symbol(c, Decl(use.js, 9, 11)) +>classic : Symbol(classic, Decl(use.js, 9, 13)) + + c.x +>c.x : Symbol((Anonymous function).x, Decl(mod.js, 1, 27)) +>c : Symbol(c, Decl(use.js, 9, 11)) +>x : Symbol((Anonymous function).x, Decl(mod.js, 1, 27)) + + classic.p +>classic.p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) +>classic : Symbol(classic, Decl(use.js, 9, 13)) +>p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) +} + + + diff --git a/tests/baselines/reference/exportNestedNamespaces.types b/tests/baselines/reference/exportNestedNamespaces.types new file mode 100644 index 00000000000..893ed56ef70 --- /dev/null +++ b/tests/baselines/reference/exportNestedNamespaces.types @@ -0,0 +1,87 @@ +=== tests/cases/conformance/salsa/mod.js === +exports.n = {}; +>exports.n = {} : typeof __object +>exports.n : typeof __object +>exports : typeof "tests/cases/conformance/salsa/mod" +>n : typeof __object +>{} : typeof __object + +exports.n.K = function () { +>exports.n.K = function () { this.x = 10;} : () => void +>exports.n.K : () => void +>exports.n : typeof __object +>exports : typeof "tests/cases/conformance/salsa/mod" +>n : typeof __object +>K : () => void +>function () { this.x = 10;} : () => void + + this.x = 10; +>this.x = 10 : 10 +>this.x : any +>this : typeof __object +>x : any +>10 : 10 +} +exports.Classic = class { +>exports.Classic = class { constructor() { this.p = 1 }} : typeof (Anonymous class) +>exports.Classic : typeof (Anonymous class) +>exports : typeof "tests/cases/conformance/salsa/mod" +>Classic : typeof (Anonymous class) +>class { constructor() { this.p = 1 }} : typeof (Anonymous class) + + constructor() { + this.p = 1 +>this.p = 1 : 1 +>this.p : number +>this : this +>p : number +>1 : 1 + } +} + +=== tests/cases/conformance/salsa/use.js === +import * as s from './mod' +>s : typeof s + +var k = new s.n.K() +>k : { x: number; } +>new s.n.K() : { x: number; } +>s.n.K : () => void +>s.n : typeof __object +>s : typeof s +>n : typeof __object +>K : () => void + +k.x +>k.x : number +>k : { x: number; } +>x : number + +var classic = new s.Classic() +>classic : (Anonymous class) +>new s.Classic() : (Anonymous class) +>s.Classic : typeof (Anonymous class) +>s : typeof s +>Classic : typeof (Anonymous class) + + +/** @param {s.n.K} c + @param {s.Classic} classic */ +function f(c, classic) { +>f : (c: { x: number; }, classic: (Anonymous class)) => void +>c : { x: number; } +>classic : (Anonymous class) + + c.x +>c.x : number +>c : { x: number; } +>x : number + + classic.p +>classic.p : number +>classic : (Anonymous class) +>p : number +} + + + diff --git a/tests/baselines/reference/moduleExportNestedNamespaces.errors.txt b/tests/baselines/reference/moduleExportNestedNamespaces.errors.txt new file mode 100644 index 00000000000..bdf81a55228 --- /dev/null +++ b/tests/baselines/reference/moduleExportNestedNamespaces.errors.txt @@ -0,0 +1,37 @@ +tests/cases/conformance/salsa/mod.js(1,1): error TS2304: Cannot find name 'module'. +tests/cases/conformance/salsa/mod.js(2,1): error TS2304: Cannot find name 'module'. +tests/cases/conformance/salsa/mod.js(5,1): error TS2304: Cannot find name 'module'. + + +==== tests/cases/conformance/salsa/mod.js (3 errors) ==== + module.exports.n = {}; + ~~~~~~ +!!! error TS2304: Cannot find name 'module'. + module.exports.n.K = function C() { + ~~~~~~ +!!! error TS2304: Cannot find name 'module'. + this.x = 10; + } + module.exports.Classic = class { + ~~~~~~ +!!! error TS2304: Cannot find name 'module'. + constructor() { + this.p = 1 + } + } + +==== tests/cases/conformance/salsa/use.js (0 errors) ==== + import * as s from './mod' + + var k = new s.n.K() + k.x + var classic = new s.Classic() + + + /** @param {s.n.K} c + @param {s.Classic} classic */ + function f(c, classic) { + c.x + classic.p + } + \ No newline at end of file diff --git a/tests/baselines/reference/moduleExportNestedNamespaces.symbols b/tests/baselines/reference/moduleExportNestedNamespaces.symbols new file mode 100644 index 00000000000..72215bf1089 --- /dev/null +++ b/tests/baselines/reference/moduleExportNestedNamespaces.symbols @@ -0,0 +1,67 @@ +=== tests/cases/conformance/salsa/mod.js === +module.exports.n = {}; +>module.exports : Symbol(n, Decl(mod.js, 0, 0)) +>n : Symbol(n, Decl(mod.js, 0, 0)) + +module.exports.n.K = function C() { +>module.exports.n : Symbol(K, Decl(mod.js, 0, 22)) +>K : Symbol(K, Decl(mod.js, 0, 22)) +>C : Symbol(C, Decl(mod.js, 1, 20)) + + this.x = 10; +>x : Symbol(C.x, Decl(mod.js, 1, 35)) +} +module.exports.Classic = class { +>module.exports : Symbol(Classic, Decl(mod.js, 3, 1)) +>Classic : Symbol(Classic, Decl(mod.js, 3, 1)) + + constructor() { + this.p = 1 +>this.p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) +>this : Symbol((Anonymous class), Decl(mod.js, 4, 24)) +>p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) + } +} + +=== tests/cases/conformance/salsa/use.js === +import * as s from './mod' +>s : Symbol(s, Decl(use.js, 0, 6)) + +var k = new s.n.K() +>k : Symbol(k, Decl(use.js, 2, 3)) +>s.n.K : Symbol(K, Decl(mod.js, 0, 22)) +>s.n : Symbol(s.n, Decl(mod.js, 0, 0)) +>s : Symbol(s, Decl(use.js, 0, 6)) +>n : Symbol(s.n, Decl(mod.js, 0, 0)) +>K : Symbol(K, Decl(mod.js, 0, 22)) + +k.x +>k.x : Symbol(C.x, Decl(mod.js, 1, 35)) +>k : Symbol(k, Decl(use.js, 2, 3)) +>x : Symbol(C.x, Decl(mod.js, 1, 35)) + +var classic = new s.Classic() +>classic : Symbol(classic, Decl(use.js, 4, 3)) +>s.Classic : Symbol(s.Classic, Decl(mod.js, 3, 1)) +>s : Symbol(s, Decl(use.js, 0, 6)) +>Classic : Symbol(s.Classic, Decl(mod.js, 3, 1)) + + +/** @param {s.n.K} c + @param {s.Classic} classic */ +function f(c, classic) { +>f : Symbol(f, Decl(use.js, 4, 29)) +>c : Symbol(c, Decl(use.js, 9, 11)) +>classic : Symbol(classic, Decl(use.js, 9, 13)) + + c.x +>c.x : Symbol(C.x, Decl(mod.js, 1, 35)) +>c : Symbol(c, Decl(use.js, 9, 11)) +>x : Symbol(C.x, Decl(mod.js, 1, 35)) + + classic.p +>classic.p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) +>classic : Symbol(classic, Decl(use.js, 9, 13)) +>p : Symbol((Anonymous class).p, Decl(mod.js, 5, 19)) +} + diff --git a/tests/baselines/reference/moduleExportNestedNamespaces.types b/tests/baselines/reference/moduleExportNestedNamespaces.types new file mode 100644 index 00000000000..7c776e362f6 --- /dev/null +++ b/tests/baselines/reference/moduleExportNestedNamespaces.types @@ -0,0 +1,92 @@ +=== tests/cases/conformance/salsa/mod.js === +module.exports.n = {}; +>module.exports.n = {} : typeof __object +>module.exports.n : any +>module.exports : any +>module : any +>exports : any +>n : any +>{} : typeof __object + +module.exports.n.K = function C() { +>module.exports.n.K = function C() { this.x = 10;} : () => void +>module.exports.n.K : any +>module.exports.n : any +>module.exports : any +>module : any +>exports : any +>n : any +>K : any +>function C() { this.x = 10;} : () => void +>C : () => void + + this.x = 10; +>this.x = 10 : 10 +>this.x : any +>this : any +>x : any +>10 : 10 +} +module.exports.Classic = class { +>module.exports.Classic = class { constructor() { this.p = 1 }} : typeof (Anonymous class) +>module.exports.Classic : any +>module.exports : any +>module : any +>exports : any +>Classic : any +>class { constructor() { this.p = 1 }} : typeof (Anonymous class) + + constructor() { + this.p = 1 +>this.p = 1 : 1 +>this.p : number +>this : this +>p : number +>1 : 1 + } +} + +=== tests/cases/conformance/salsa/use.js === +import * as s from './mod' +>s : typeof s + +var k = new s.n.K() +>k : { x: number; } +>new s.n.K() : { x: number; } +>s.n.K : () => void +>s.n : typeof __object +>s : typeof s +>n : typeof __object +>K : () => void + +k.x +>k.x : number +>k : { x: number; } +>x : number + +var classic = new s.Classic() +>classic : (Anonymous class) +>new s.Classic() : (Anonymous class) +>s.Classic : typeof (Anonymous class) +>s : typeof s +>Classic : typeof (Anonymous class) + + +/** @param {s.n.K} c + @param {s.Classic} classic */ +function f(c, classic) { +>f : (c: { x: number; }, classic: (Anonymous class)) => void +>c : { x: number; } +>classic : (Anonymous class) + + c.x +>c.x : number +>c : { x: number; } +>x : number + + classic.p +>classic.p : number +>classic : (Anonymous class) +>p : number +} + diff --git a/tests/cases/conformance/salsa/exportNestedNamespaces.ts b/tests/cases/conformance/salsa/exportNestedNamespaces.ts new file mode 100644 index 00000000000..41f4a942d02 --- /dev/null +++ b/tests/cases/conformance/salsa/exportNestedNamespaces.ts @@ -0,0 +1,31 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: mod.js + +exports.n = {}; +exports.n.K = function () { + this.x = 10; +} +exports.Classic = class { + constructor() { + this.p = 1 + } +} + +// @Filename: use.js +import * as s from './mod' + +var k = new s.n.K() +k.x +var classic = new s.Classic() + + +/** @param {s.n.K} c + @param {s.Classic} classic */ +function f(c, classic) { + c.x + classic.p +} + + diff --git a/tests/cases/conformance/salsa/moduleExportNestedNamespaces.ts b/tests/cases/conformance/salsa/moduleExportNestedNamespaces.ts new file mode 100644 index 00000000000..37a6aaf58ec --- /dev/null +++ b/tests/cases/conformance/salsa/moduleExportNestedNamespaces.ts @@ -0,0 +1,29 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: mod.js + +module.exports.n = {}; +module.exports.n.K = function C() { + this.x = 10; +} +module.exports.Classic = class { + constructor() { + this.p = 1 + } +} + +// @Filename: use.js +import * as s from './mod' + +var k = new s.n.K() +k.x +var classic = new s.Classic() + + +/** @param {s.n.K} c + @param {s.Classic} classic */ +function f(c, classic) { + c.x + classic.p +}