Don’t create expando declarations on alias symbols (#39558)

* Don’t create expando declarations on alias symbols

* Update other baseline

* Fix brace nesting refactor mistake
This commit is contained in:
Andrew Branch 2020-07-13 10:05:48 -07:00 committed by GitHub
parent 629dd6487b
commit 583bd92bc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 215 additions and 44 deletions

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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 {};

View file

@ -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))

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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;