Skip to content

Commit 583bd92

Browse files
authored
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
1 parent 629dd64 commit 583bd92

11 files changed

+216
-45
lines changed

src/compiler/binder.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2980,6 +2980,9 @@ namespace ts {
29802980
}
29812981

29822982
function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) {
2983+
if (namespaceSymbol?.flags! & SymbolFlags.Alias) {
2984+
return namespaceSymbol;
2985+
}
29832986
if (isToplevel && !isPrototypeProperty) {
29842987
// make symbols or add declarations for intermediate containers
29852988
const flags = SymbolFlags.Module | SymbolFlags.Assignment;

src/compiler/checker.ts

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35217,39 +35217,36 @@ namespace ts {
3521735217
const target = resolveAlias(symbol);
3521835218

3521935219
if (target !== unknownSymbol) {
35220-
const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment;
35221-
if (!shouldSkipWithJSExpandoTargets) {
35222-
// For external modules symbol represents local symbol for an alias.
35223-
// This local symbol will merge any other local declarations (excluding other aliases)
35224-
// and symbol.flags will contains combined representation for all merged declaration.
35225-
// Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have,
35226-
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
35227-
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
35228-
symbol = getMergedSymbol(symbol.exportSymbol || symbol);
35229-
const excludedMeanings =
35230-
(symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) |
35231-
(symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) |
35232-
(symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0);
35233-
if (target.flags & excludedMeanings) {
35234-
const message = node.kind === SyntaxKind.ExportSpecifier ?
35235-
Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 :
35236-
Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0;
35237-
error(node, message, symbolToString(symbol));
35238-
}
35239-
35240-
// Don't allow to re-export something with no value side when `--isolatedModules` is set.
35241-
if (compilerOptions.isolatedModules
35242-
&& node.kind === SyntaxKind.ExportSpecifier
35243-
&& !node.parent.parent.isTypeOnly
35244-
&& !(target.flags & SymbolFlags.Value)
35245-
&& !(node.flags & NodeFlags.Ambient)) {
35246-
error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type);
35247-
}
35220+
// For external modules, `symbol` represents the local symbol for an alias.
35221+
// This local symbol will merge any other local declarations (excluding other aliases)
35222+
// and symbol.flags will contains combined representation for all merged declaration.
35223+
// Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have,
35224+
// otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export*
35225+
// in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names).
35226+
symbol = getMergedSymbol(symbol.exportSymbol || symbol);
35227+
const excludedMeanings =
35228+
(symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) |
35229+
(symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) |
35230+
(symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0);
35231+
if (target.flags & excludedMeanings) {
35232+
const message = node.kind === SyntaxKind.ExportSpecifier ?
35233+
Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 :
35234+
Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0;
35235+
error(node, message, symbolToString(symbol));
35236+
}
35237+
35238+
// Don't allow to re-export something with no value side when `--isolatedModules` is set.
35239+
if (compilerOptions.isolatedModules
35240+
&& node.kind === SyntaxKind.ExportSpecifier
35241+
&& !node.parent.parent.isTypeOnly
35242+
&& !(target.flags & SymbolFlags.Value)
35243+
&& !(node.flags & NodeFlags.Ambient)) {
35244+
error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type);
3524835245
}
3524935246

3525035247
if (isImportSpecifier(node) &&
3525135248
(target.valueDeclaration && target.valueDeclaration.flags & NodeFlags.Deprecated
35252-
|| every(target.declarations, d => !!(d.flags & NodeFlags.Deprecated)))) {
35249+
|| every(target.declarations, d => !!(d.flags & NodeFlags.Deprecated)))) {
3525335250
errorOrSuggestion(/* isError */ false, node.name, Diagnostics._0_is_deprecated, symbol.escapedName as string);
3525435251
}
3525535252
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
tests/cases/conformance/salsa/test.js(4,5): error TS2339: Property 'config' does not exist on type 'typeof Vue'.
2+
3+
4+
==== tests/cases/conformance/salsa/vue.js (0 errors) ====
5+
export class Vue {}
6+
export const config = { x: 0 };
7+
8+
==== tests/cases/conformance/salsa/test.js (1 errors) ====
9+
import { Vue, config } from "./vue";
10+
11+
// Expando declarations aren't allowed on aliases.
12+
Vue.config = {};
13+
~~~~~~
14+
!!! error TS2339: Property 'config' does not exist on type 'typeof Vue'.
15+
new Vue();
16+
17+
// This is not an expando declaration; it's just a plain property assignment.
18+
config.x = 1;
19+
20+
// This is not an expando declaration; it works because non-strict JS allows
21+
// loosey goosey assignment on objects.
22+
config.y = {};
23+
config.x;
24+
config.y;
25+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [tests/cases/conformance/salsa/expandoOnAlias.ts] ////
2+
3+
//// [vue.js]
4+
export class Vue {}
5+
export const config = { x: 0 };
6+
7+
//// [test.js]
8+
import { Vue, config } from "./vue";
9+
10+
// Expando declarations aren't allowed on aliases.
11+
Vue.config = {};
12+
new Vue();
13+
14+
// This is not an expando declaration; it's just a plain property assignment.
15+
config.x = 1;
16+
17+
// This is not an expando declaration; it works because non-strict JS allows
18+
// loosey goosey assignment on objects.
19+
config.y = {};
20+
config.x;
21+
config.y;
22+
23+
24+
25+
26+
//// [vue.d.ts]
27+
export class Vue {
28+
}
29+
export namespace config {
30+
const x: number;
31+
}
32+
//// [test.d.ts]
33+
export {};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
=== tests/cases/conformance/salsa/vue.js ===
2+
export class Vue {}
3+
>Vue : Symbol(Vue, Decl(vue.js, 0, 0))
4+
5+
export const config = { x: 0 };
6+
>config : Symbol(config, Decl(vue.js, 1, 12))
7+
>x : Symbol(x, Decl(vue.js, 1, 23))
8+
9+
=== tests/cases/conformance/salsa/test.js ===
10+
import { Vue, config } from "./vue";
11+
>Vue : Symbol(Vue, Decl(test.js, 0, 8))
12+
>config : Symbol(config, Decl(test.js, 0, 13))
13+
14+
// Expando declarations aren't allowed on aliases.
15+
Vue.config = {};
16+
>Vue : Symbol(Vue, Decl(test.js, 0, 8))
17+
18+
new Vue();
19+
>Vue : Symbol(Vue, Decl(test.js, 0, 8))
20+
21+
// This is not an expando declaration; it's just a plain property assignment.
22+
config.x = 1;
23+
>config.x : Symbol(x, Decl(vue.js, 1, 23))
24+
>config : Symbol(config, Decl(test.js, 0, 13))
25+
>x : Symbol(x, Decl(vue.js, 1, 23))
26+
27+
// This is not an expando declaration; it works because non-strict JS allows
28+
// loosey goosey assignment on objects.
29+
config.y = {};
30+
>config : Symbol(config, Decl(test.js, 0, 13))
31+
32+
config.x;
33+
>config.x : Symbol(x, Decl(vue.js, 1, 23))
34+
>config : Symbol(config, Decl(test.js, 0, 13))
35+
>x : Symbol(x, Decl(vue.js, 1, 23))
36+
37+
config.y;
38+
>config : Symbol(config, Decl(test.js, 0, 13))
39+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
=== tests/cases/conformance/salsa/vue.js ===
2+
export class Vue {}
3+
>Vue : Vue
4+
5+
export const config = { x: 0 };
6+
>config : { x: number; }
7+
>{ x: 0 } : { x: number; }
8+
>x : number
9+
>0 : 0
10+
11+
=== tests/cases/conformance/salsa/test.js ===
12+
import { Vue, config } from "./vue";
13+
>Vue : typeof Vue
14+
>config : { x: number; }
15+
16+
// Expando declarations aren't allowed on aliases.
17+
Vue.config = {};
18+
>Vue.config = {} : {}
19+
>Vue.config : any
20+
>Vue : typeof Vue
21+
>config : any
22+
>{} : {}
23+
24+
new Vue();
25+
>new Vue() : Vue
26+
>Vue : typeof Vue
27+
28+
// This is not an expando declaration; it's just a plain property assignment.
29+
config.x = 1;
30+
>config.x = 1 : 1
31+
>config.x : number
32+
>config : { x: number; }
33+
>x : number
34+
>1 : 1
35+
36+
// This is not an expando declaration; it works because non-strict JS allows
37+
// loosey goosey assignment on objects.
38+
config.y = {};
39+
>config.y = {} : {}
40+
>config.y : any
41+
>config : { x: number; }
42+
>y : any
43+
>{} : {}
44+
45+
config.x;
46+
>config.x : number
47+
>config : { x: number; }
48+
>x : number
49+
50+
config.y;
51+
>config.y : any
52+
>config : { x: number; }
53+
>y : any
54+

tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ export default Obj;
77

88
=== tests/cases/compiler/b.js ===
99
import Obj from './a';
10-
>Obj : Symbol(Obj, Decl(b.js, 0, 6), Decl(b.js, 0, 22))
10+
>Obj : Symbol(Obj, Decl(b.js, 0, 6))
1111

1212
Obj.fn = function() {};
13-
>Obj.fn : Symbol(Obj.fn, Decl(b.js, 0, 22))
14-
>Obj : Symbol(Obj, Decl(b.js, 0, 6), Decl(b.js, 0, 22))
15-
>fn : Symbol(Obj.fn, Decl(b.js, 0, 22))
13+
>Obj : Symbol(Obj, Decl(b.js, 0, 6))
1614

tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ export default Obj;
88

99
=== tests/cases/compiler/b.js ===
1010
import Obj from './a';
11-
>Obj : typeof Obj
11+
>Obj : {}
1212

1313
Obj.fn = function() {};
1414
>Obj.fn = function() {} : () => void
15-
>Obj.fn : () => void
16-
>Obj : typeof Obj
17-
>fn : () => void
15+
>Obj.fn : error
16+
>Obj : {}
17+
>fn : any
1818
>function() {} : () => void
1919

tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ export var hurk = {}
44

55
=== tests/cases/conformance/salsa/bug24658.js ===
66
import { hurk } from './mod1'
7-
>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8), Decl(bug24658.js, 0, 29))
7+
>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8))
88

99
hurk.expando = 4
10-
>hurk.expando : Symbol(hurk.expando, Decl(bug24658.js, 0, 29))
11-
>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8), Decl(bug24658.js, 0, 29))
12-
>expando : Symbol(hurk.expando, Decl(bug24658.js, 0, 29))
10+
>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8))
1311

tests/baselines/reference/propertyAssignmentOnImportedSymbol.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ export var hurk = {}
55

66
=== tests/cases/conformance/salsa/bug24658.js ===
77
import { hurk } from './mod1'
8-
>hurk : typeof hurk
8+
>hurk : {}
99

1010
hurk.expando = 4
1111
>hurk.expando = 4 : 4
12-
>hurk.expando : number
13-
>hurk : typeof hurk
14-
>expando : number
12+
>hurk.expando : any
13+
>hurk : {}
14+
>expando : any
1515
>4 : 4
1616

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @declaration: true
4+
// @emitDeclarationOnly: true
5+
6+
// @Filename: vue.js
7+
export class Vue {}
8+
export const config = { x: 0 };
9+
10+
// @Filename: test.js
11+
import { Vue, config } from "./vue";
12+
13+
// Expando declarations aren't allowed on aliases.
14+
Vue.config = {};
15+
new Vue();
16+
17+
// This is not an expando declaration; it's just a plain property assignment.
18+
config.x = 1;
19+
20+
// This is not an expando declaration; it works because non-strict JS allows
21+
// loosey goosey assignment on objects.
22+
config.y = {};
23+
config.x;
24+
config.y;

0 commit comments

Comments
 (0)