diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a220e79dc44a5..83bbb549a504b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2689,7 +2689,7 @@ namespace ts { let symbolFromVariable: Symbol | undefined; // First check if module was specified with "export=". If so, get the member from the resolved type if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { - symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText); + symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText, /*skipObjectFunctionPropertyAugment*/ true); } else { symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); @@ -11147,7 +11147,7 @@ namespace ts { return getReducedType(getApparentType(getReducedType(type))); } - function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined { + function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { let singleProp: Symbol | undefined; let propSet: ESMap | undefined; let indexTypes: Type[] | undefined; @@ -11159,7 +11159,7 @@ namespace ts { for (const current of containingType.types) { const type = getApparentType(current); if (!(type === errorType || type.flags & TypeFlags.Never)) { - const prop = getPropertyOfType(type, name); + const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment); const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; if (prop) { if (isUnion) { @@ -11276,20 +11276,23 @@ namespace ts { // constituents, in which case the isPartial flag is set when the containing type is union type. We need // these partial properties when identifying discriminant properties, but otherwise they are filtered out // and do not appear to be present in the union type. - function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String): Symbol | undefined { - const properties = type.propertyCache || (type.propertyCache = createSymbolTable()); - let property = properties.get(name); + function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + let property = type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) || + !skipObjectFunctionPropertyAugment ? type.propertyCache?.get(name) : undefined; if (!property) { - property = createUnionOrIntersectionProperty(type, name); + property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); if (property) { + const properties = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : + type.propertyCache ||= createSymbolTable(); properties.set(name, property); } } return property; } - function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String): Symbol | undefined { - const property = getUnionOrIntersectionProperty(type, name); + function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); // We need to filter out partial properties in union types return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; } @@ -11367,7 +11370,7 @@ namespace ts { * @param type a type to look up property from * @param name a name of property to look up in a given type */ - function getPropertyOfType(type: Type, name: __String): Symbol | undefined { + function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { type = getReducedApparentType(type); if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); @@ -11375,6 +11378,7 @@ namespace ts { if (symbol && symbolIsValue(symbol)) { return symbol; } + if (skipObjectFunctionPropertyAugment) return undefined; const functionType = resolved === anyFunctionType ? globalFunctionType : resolved.callSignatures.length ? globalCallableFunctionType : resolved.constructSignatures.length ? globalNewableFunctionType : @@ -11388,7 +11392,7 @@ namespace ts { return getPropertyOfObjectType(globalObjectType, name); } if (type.flags & TypeFlags.UnionOrIntersection) { - return getPropertyOfUnionOrIntersectionType(type, name); + return getPropertyOfUnionOrIntersectionType(type, name, skipObjectFunctionPropertyAugment); } return undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 77af89087de56..625a5d8397537 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5193,7 +5193,9 @@ namespace ts { /* @internal */ objectFlags: ObjectFlags; /* @internal */ - propertyCache: SymbolTable; // Cache of resolved properties + propertyCache?: SymbolTable; // Cache of resolved properties + /* @internal */ + propertyCacheWithoutObjectFunctionPropertyAugment?: SymbolTable; // Cache of resolved properties that does not augment function or object type properties /* @internal */ resolvedProperties: Symbol[]; /* @internal */ diff --git a/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.errors.txt b/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.errors.txt new file mode 100644 index 0000000000000..8e4ef61316609 --- /dev/null +++ b/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.errors.txt @@ -0,0 +1,21 @@ +tests/cases/compiler/bar.d.ts(1,10): error TS2305: Module '"./foo"' has no exported member 'Bar'. + + +==== tests/cases/compiler/foo.d.ts (0 errors) ==== + export = Foo; + export as namespace Foo; + + declare namespace Foo { + function foo(); + } + + declare global { + namespace Bar { } + } + +==== tests/cases/compiler/bar.d.ts (1 errors) ==== + import { Bar } from './foo'; + ~~~ +!!! error TS2305: Module '"./foo"' has no exported member 'Bar'. + export = Bar; + export as namespace Bar; \ No newline at end of file diff --git a/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.symbols b/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.symbols new file mode 100644 index 0000000000000..8e87803396bc6 --- /dev/null +++ b/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.symbols @@ -0,0 +1,31 @@ +=== tests/cases/compiler/foo.d.ts === +export = Foo; +>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24)) + +export as namespace Foo; +>Foo : Symbol(Foo, Decl(foo.d.ts, 0, 13)) + +declare namespace Foo { +>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24)) + + function foo(); +>foo : Symbol(foo, Decl(foo.d.ts, 3, 23)) +} + +declare global { +>global : Symbol(global, Decl(foo.d.ts, 5, 1)) + + namespace Bar { } +>Bar : Symbol(Bar, Decl(foo.d.ts, 7, 16)) +} + +=== tests/cases/compiler/bar.d.ts === +import { Bar } from './foo'; +>Bar : Symbol(Bar, Decl(bar.d.ts, 0, 8)) + +export = Bar; +>Bar : Symbol(Bar, Decl(bar.d.ts, 0, 8)) + +export as namespace Bar; +>Bar : Symbol(Bar, Decl(bar.d.ts, 1, 13)) + diff --git a/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.types b/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.types new file mode 100644 index 0000000000000..99277395c85be --- /dev/null +++ b/tests/baselines/reference/moduleAugmentationWithNonExistentNamedImport.types @@ -0,0 +1,30 @@ +=== tests/cases/compiler/foo.d.ts === +export = Foo; +>Foo : typeof Foo + +export as namespace Foo; +>Foo : typeof Foo + +declare namespace Foo { +>Foo : typeof Foo + + function foo(); +>foo : () => any +} + +declare global { +>global : any + + namespace Bar { } +} + +=== tests/cases/compiler/bar.d.ts === +import { Bar } from './foo'; +>Bar : any + +export = Bar; +>Bar : any + +export as namespace Bar; +>Bar : any + diff --git a/tests/baselines/reference/namedImportNonExistentName.errors.txt b/tests/baselines/reference/namedImportNonExistentName.errors.txt new file mode 100644 index 0000000000000..f26d48b64848e --- /dev/null +++ b/tests/baselines/reference/namedImportNonExistentName.errors.txt @@ -0,0 +1,37 @@ +tests/cases/compiler/bar.ts(1,10): error TS2305: Module '"./foo"' has no exported member 'Bar'. +tests/cases/compiler/bar.ts(1,15): error TS2305: Module '"./foo"' has no exported member 'toString'. +tests/cases/compiler/bar.ts(3,10): error TS2305: Module '"./foo2"' has no exported member 'a'. +tests/cases/compiler/bar.ts(3,13): error TS2305: Module '"./foo2"' has no exported member 'b'. +tests/cases/compiler/bar.ts(3,19): error TS2305: Module '"./foo2"' has no exported member 'd'. +tests/cases/compiler/bar.ts(3,22): error TS2305: Module '"./foo2"' has no exported member 'toString'. + + +==== tests/cases/compiler/foo.d.ts (0 errors) ==== + export = Foo; + export as namespace Foo; + + declare namespace Foo { + function foo(); + } + +==== tests/cases/compiler/foo2.ts (0 errors) ==== + let x: { a: string; c: string; } | { b: number; c: number; }; + export = x + +==== tests/cases/compiler/bar.ts (6 errors) ==== + import { Bar, toString, foo } from './foo'; + ~~~ +!!! error TS2305: Module '"./foo"' has no exported member 'Bar'. + ~~~~~~~~ +!!! error TS2305: Module '"./foo"' has no exported member 'toString'. + foo(); + import { a, b, c, d, toString as foo2String } from './foo2'; + ~ +!!! error TS2305: Module '"./foo2"' has no exported member 'a'. + ~ +!!! error TS2305: Module '"./foo2"' has no exported member 'b'. + ~ +!!! error TS2305: Module '"./foo2"' has no exported member 'd'. + ~~~~~~~~ +!!! error TS2305: Module '"./foo2"' has no exported member 'toString'. + c; \ No newline at end of file diff --git a/tests/baselines/reference/namedImportNonExistentName.js b/tests/baselines/reference/namedImportNonExistentName.js new file mode 100644 index 0000000000000..b017d9b3f093f --- /dev/null +++ b/tests/baselines/reference/namedImportNonExistentName.js @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/namedImportNonExistentName.ts] //// + +//// [foo.d.ts] +export = Foo; +export as namespace Foo; + +declare namespace Foo { + function foo(); +} + +//// [foo2.ts] +let x: { a: string; c: string; } | { b: number; c: number; }; +export = x + +//// [bar.ts] +import { Bar, toString, foo } from './foo'; +foo(); +import { a, b, c, d, toString as foo2String } from './foo2'; +c; + +//// [foo2.js] +"use strict"; +var x; +module.exports = x; +//// [bar.js] +"use strict"; +exports.__esModule = true; +var foo_1 = require("./foo"); +foo_1.foo(); +var foo2_1 = require("./foo2"); +foo2_1.c; diff --git a/tests/baselines/reference/namedImportNonExistentName.symbols b/tests/baselines/reference/namedImportNonExistentName.symbols new file mode 100644 index 0000000000000..b6ec6ebb14021 --- /dev/null +++ b/tests/baselines/reference/namedImportNonExistentName.symbols @@ -0,0 +1,44 @@ +=== tests/cases/compiler/foo.d.ts === +export = Foo; +>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24)) + +export as namespace Foo; +>Foo : Symbol(Foo, Decl(foo.d.ts, 0, 13)) + +declare namespace Foo { +>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24)) + + function foo(); +>foo : Symbol(foo, Decl(foo.d.ts, 3, 23)) +} + +=== tests/cases/compiler/foo2.ts === +let x: { a: string; c: string; } | { b: number; c: number; }; +>x : Symbol(x, Decl(foo2.ts, 0, 3)) +>a : Symbol(a, Decl(foo2.ts, 0, 8)) +>c : Symbol(c, Decl(foo2.ts, 0, 19)) +>b : Symbol(b, Decl(foo2.ts, 0, 36)) +>c : Symbol(c, Decl(foo2.ts, 0, 47)) + +export = x +>x : Symbol(x, Decl(foo2.ts, 0, 3)) + +=== tests/cases/compiler/bar.ts === +import { Bar, toString, foo } from './foo'; +>Bar : Symbol(Bar, Decl(bar.ts, 0, 8)) +>toString : Symbol(toString, Decl(bar.ts, 0, 13)) +>foo : Symbol(foo, Decl(bar.ts, 0, 23)) + +foo(); +>foo : Symbol(foo, Decl(bar.ts, 0, 23)) + +import { a, b, c, d, toString as foo2String } from './foo2'; +>a : Symbol(a, Decl(bar.ts, 2, 8)) +>b : Symbol(b, Decl(bar.ts, 2, 11)) +>c : Symbol(c, Decl(bar.ts, 2, 14)) +>d : Symbol(d, Decl(bar.ts, 2, 17)) +>foo2String : Symbol(foo2String, Decl(bar.ts, 2, 20)) + +c; +>c : Symbol(c, Decl(bar.ts, 2, 14)) + diff --git a/tests/baselines/reference/namedImportNonExistentName.types b/tests/baselines/reference/namedImportNonExistentName.types new file mode 100644 index 0000000000000..02ffb609f5dd8 --- /dev/null +++ b/tests/baselines/reference/namedImportNonExistentName.types @@ -0,0 +1,46 @@ +=== tests/cases/compiler/foo.d.ts === +export = Foo; +>Foo : typeof Foo + +export as namespace Foo; +>Foo : typeof Foo + +declare namespace Foo { +>Foo : typeof Foo + + function foo(); +>foo : () => any +} + +=== tests/cases/compiler/foo2.ts === +let x: { a: string; c: string; } | { b: number; c: number; }; +>x : { a: string; c: string; } | { b: number; c: number; } +>a : string +>c : string +>b : number +>c : number + +export = x +>x : { a: string; c: string; } | { b: number; c: number; } + +=== tests/cases/compiler/bar.ts === +import { Bar, toString, foo } from './foo'; +>Bar : any +>toString : any +>foo : () => any + +foo(); +>foo() : any +>foo : () => any + +import { a, b, c, d, toString as foo2String } from './foo2'; +>a : any +>b : any +>c : string | number +>d : any +>toString : any +>foo2String : any + +c; +>c : string | number + diff --git a/tests/cases/compiler/moduleAugmentationWithNonExistentNamedImport.ts b/tests/cases/compiler/moduleAugmentationWithNonExistentNamedImport.ts new file mode 100644 index 0000000000000..6741e58cc91d6 --- /dev/null +++ b/tests/cases/compiler/moduleAugmentationWithNonExistentNamedImport.ts @@ -0,0 +1,16 @@ +// @filename: foo.d.ts +export = Foo; +export as namespace Foo; + +declare namespace Foo { + function foo(); +} + +declare global { + namespace Bar { } +} + +// @filename: bar.d.ts +import { Bar } from './foo'; +export = Bar; +export as namespace Bar; \ No newline at end of file diff --git a/tests/cases/compiler/namedImportNonExistentName.ts b/tests/cases/compiler/namedImportNonExistentName.ts new file mode 100644 index 0000000000000..3fd3c3011cc96 --- /dev/null +++ b/tests/cases/compiler/namedImportNonExistentName.ts @@ -0,0 +1,17 @@ +// @filename: foo.d.ts +export = Foo; +export as namespace Foo; + +declare namespace Foo { + function foo(); +} + +// @filename: foo2.ts +let x: { a: string; c: string; } | { b: number; c: number; }; +export = x + +// @filename: bar.ts +import { Bar, toString, foo } from './foo'; +foo(); +import { a, b, c, d, toString as foo2String } from './foo2'; +c; \ No newline at end of file