diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b9126ca464..98d9c195b5 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2980,6 +2980,9 @@ namespace ts { } function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { + if (namespaceSymbol?.flags! & SymbolFlags.Alias) { + return namespaceSymbol; + } if (isToplevel && !isPrototypeProperty) { // make symbols or add declarations for intermediate containers const flags = SymbolFlags.Module | SymbolFlags.Assignment; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 082c18f348..eeba72a26d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -35217,39 +35217,36 @@ namespace ts { const target = resolveAlias(symbol); if (target !== unknownSymbol) { - const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment; - if (!shouldSkipWithJSExpandoTargets) { - // For external modules symbol represents local symbol for an alias. - // This local symbol will merge any other local declarations (excluding other aliases) - // and symbol.flags will contains combined representation for all merged declaration. - // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, - // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* - // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). - symbol = getMergedSymbol(symbol.exportSymbol || symbol); - const excludedMeanings = - (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | - (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | - (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); - if (target.flags & excludedMeanings) { - const message = node.kind === SyntaxKind.ExportSpecifier ? - Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : - Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; - error(node, message, symbolToString(symbol)); - } + // For external modules, `symbol` represents the local symbol for an alias. + // This local symbol will merge any other local declarations (excluding other aliases) + // and symbol.flags will contains combined representation for all merged declaration. + // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, + // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* + // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). + symbol = getMergedSymbol(symbol.exportSymbol || symbol); + const excludedMeanings = + (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | + (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | + (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); + if (target.flags & excludedMeanings) { + const message = node.kind === SyntaxKind.ExportSpecifier ? + Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : + Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; + error(node, message, symbolToString(symbol)); + } - // Don't allow to re-export something with no value side when `--isolatedModules` is set. - if (compilerOptions.isolatedModules - && node.kind === SyntaxKind.ExportSpecifier - && !node.parent.parent.isTypeOnly - && !(target.flags & SymbolFlags.Value) - && !(node.flags & NodeFlags.Ambient)) { - error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type); - } + // Don't allow to re-export something with no value side when `--isolatedModules` is set. + if (compilerOptions.isolatedModules + && node.kind === SyntaxKind.ExportSpecifier + && !node.parent.parent.isTypeOnly + && !(target.flags & SymbolFlags.Value) + && !(node.flags & NodeFlags.Ambient)) { + error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type); } if (isImportSpecifier(node) && (target.valueDeclaration && target.valueDeclaration.flags & NodeFlags.Deprecated - || every(target.declarations, d => !!(d.flags & NodeFlags.Deprecated)))) { + || every(target.declarations, d => !!(d.flags & NodeFlags.Deprecated)))) { errorOrSuggestion(/* isError */ false, node.name, Diagnostics._0_is_deprecated, symbol.escapedName as string); } } diff --git a/tests/baselines/reference/expandoOnAlias.errors.txt b/tests/baselines/reference/expandoOnAlias.errors.txt new file mode 100644 index 0000000000..d1b29d0aa3 --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.errors.txt @@ -0,0 +1,25 @@ +tests/cases/conformance/salsa/test.js(4,5): error TS2339: Property 'config' does not exist on type 'typeof Vue'. + + +==== tests/cases/conformance/salsa/vue.js (0 errors) ==== + export class Vue {} + export const config = { x: 0 }; + +==== tests/cases/conformance/salsa/test.js (1 errors) ==== + import { Vue, config } from "./vue"; + + // Expando declarations aren't allowed on aliases. + Vue.config = {}; + ~~~~~~ +!!! error TS2339: Property 'config' does not exist on type 'typeof Vue'. + new Vue(); + + // This is not an expando declaration; it's just a plain property assignment. + config.x = 1; + + // This is not an expando declaration; it works because non-strict JS allows + // loosey goosey assignment on objects. + config.y = {}; + config.x; + config.y; + \ No newline at end of file diff --git a/tests/baselines/reference/expandoOnAlias.js b/tests/baselines/reference/expandoOnAlias.js new file mode 100644 index 0000000000..39e20fb38d --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.js @@ -0,0 +1,33 @@ +//// [tests/cases/conformance/salsa/expandoOnAlias.ts] //// + +//// [vue.js] +export class Vue {} +export const config = { x: 0 }; + +//// [test.js] +import { Vue, config } from "./vue"; + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +new Vue(); + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +config.x; +config.y; + + + + +//// [vue.d.ts] +export class Vue { +} +export namespace config { + const x: number; +} +//// [test.d.ts] +export {}; diff --git a/tests/baselines/reference/expandoOnAlias.symbols b/tests/baselines/reference/expandoOnAlias.symbols new file mode 100644 index 0000000000..e4aa1350dd --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.symbols @@ -0,0 +1,39 @@ +=== tests/cases/conformance/salsa/vue.js === +export class Vue {} +>Vue : Symbol(Vue, Decl(vue.js, 0, 0)) + +export const config = { x: 0 }; +>config : Symbol(config, Decl(vue.js, 1, 12)) +>x : Symbol(x, Decl(vue.js, 1, 23)) + +=== tests/cases/conformance/salsa/test.js === +import { Vue, config } from "./vue"; +>Vue : Symbol(Vue, Decl(test.js, 0, 8)) +>config : Symbol(config, Decl(test.js, 0, 13)) + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +>Vue : Symbol(Vue, Decl(test.js, 0, 8)) + +new Vue(); +>Vue : Symbol(Vue, Decl(test.js, 0, 8)) + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; +>config.x : Symbol(x, Decl(vue.js, 1, 23)) +>config : Symbol(config, Decl(test.js, 0, 13)) +>x : Symbol(x, Decl(vue.js, 1, 23)) + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +>config : Symbol(config, Decl(test.js, 0, 13)) + +config.x; +>config.x : Symbol(x, Decl(vue.js, 1, 23)) +>config : Symbol(config, Decl(test.js, 0, 13)) +>x : Symbol(x, Decl(vue.js, 1, 23)) + +config.y; +>config : Symbol(config, Decl(test.js, 0, 13)) + diff --git a/tests/baselines/reference/expandoOnAlias.types b/tests/baselines/reference/expandoOnAlias.types new file mode 100644 index 0000000000..a0dd0872de --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.types @@ -0,0 +1,54 @@ +=== tests/cases/conformance/salsa/vue.js === +export class Vue {} +>Vue : Vue + +export const config = { x: 0 }; +>config : { x: number; } +>{ x: 0 } : { x: number; } +>x : number +>0 : 0 + +=== tests/cases/conformance/salsa/test.js === +import { Vue, config } from "./vue"; +>Vue : typeof Vue +>config : { x: number; } + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +>Vue.config = {} : {} +>Vue.config : any +>Vue : typeof Vue +>config : any +>{} : {} + +new Vue(); +>new Vue() : Vue +>Vue : typeof Vue + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; +>config.x = 1 : 1 +>config.x : number +>config : { x: number; } +>x : number +>1 : 1 + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +>config.y = {} : {} +>config.y : any +>config : { x: number; } +>y : any +>{} : {} + +config.x; +>config.x : number +>config : { x: number; } +>x : number + +config.y; +>config.y : any +>config : { x: number; } +>y : any + diff --git a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols index ffb9643c6b..9dbdf4e44d 100644 --- a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols +++ b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols @@ -7,10 +7,8 @@ export default Obj; === tests/cases/compiler/b.js === import Obj from './a'; ->Obj : Symbol(Obj, Decl(b.js, 0, 6), Decl(b.js, 0, 22)) +>Obj : Symbol(Obj, Decl(b.js, 0, 6)) Obj.fn = function() {}; ->Obj.fn : Symbol(Obj.fn, Decl(b.js, 0, 22)) ->Obj : Symbol(Obj, Decl(b.js, 0, 6), Decl(b.js, 0, 22)) ->fn : Symbol(Obj.fn, Decl(b.js, 0, 22)) +>Obj : Symbol(Obj, Decl(b.js, 0, 6)) diff --git a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types index f2c20778de..68ec7924aa 100644 --- a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types +++ b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types @@ -8,12 +8,12 @@ export default Obj; === tests/cases/compiler/b.js === import Obj from './a'; ->Obj : typeof Obj +>Obj : {} Obj.fn = function() {}; >Obj.fn = function() {} : () => void ->Obj.fn : () => void ->Obj : typeof Obj ->fn : () => void +>Obj.fn : error +>Obj : {} +>fn : any >function() {} : () => void diff --git a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols index 453816e38c..f8d01ae698 100644 --- a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols +++ b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols @@ -4,10 +4,8 @@ export var hurk = {} === tests/cases/conformance/salsa/bug24658.js === import { hurk } from './mod1' ->hurk : Symbol(hurk, Decl(bug24658.js, 0, 8), Decl(bug24658.js, 0, 29)) +>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8)) hurk.expando = 4 ->hurk.expando : Symbol(hurk.expando, Decl(bug24658.js, 0, 29)) ->hurk : Symbol(hurk, Decl(bug24658.js, 0, 8), Decl(bug24658.js, 0, 29)) ->expando : Symbol(hurk.expando, Decl(bug24658.js, 0, 29)) +>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8)) diff --git a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types index 53930f09a6..8df4f98457 100644 --- a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types +++ b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types @@ -5,12 +5,12 @@ export var hurk = {} === tests/cases/conformance/salsa/bug24658.js === import { hurk } from './mod1' ->hurk : typeof hurk +>hurk : {} hurk.expando = 4 >hurk.expando = 4 : 4 ->hurk.expando : number ->hurk : typeof hurk ->expando : number +>hurk.expando : any +>hurk : {} +>expando : any >4 : 4 diff --git a/tests/cases/conformance/salsa/expandoOnAlias.ts b/tests/cases/conformance/salsa/expandoOnAlias.ts new file mode 100644 index 0000000000..1a2ecb09f8 --- /dev/null +++ b/tests/cases/conformance/salsa/expandoOnAlias.ts @@ -0,0 +1,24 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true + +// @Filename: vue.js +export class Vue {} +export const config = { x: 0 }; + +// @Filename: test.js +import { Vue, config } from "./vue"; + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +new Vue(); + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +config.x; +config.y;