Skip to content

Commit 5ce34ca

Browse files
authored
Avoid trying to emit anonymous classish/expando functions as assignments (#55472)
1 parent 9d0dc77 commit 5ce34ca

10 files changed

+374
-14
lines changed

src/compiler/checker.ts

+23-6
Original file line numberDiff line numberDiff line change
@@ -8533,11 +8533,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
85338533
...oldcontext,
85348534
usedSymbolNames: new Set(oldcontext.usedSymbolNames),
85358535
remappedSymbolNames: new Map(),
8536+
remappedSymbolReferences: new Map(oldcontext.remappedSymbolReferences?.entries()),
85368537
tracker: undefined!,
85378538
};
85388539
const tracker: SymbolTracker = {
85398540
...oldcontext.tracker.inner,
85408541
trackSymbol: (sym, decl, meaning) => {
8542+
if (context.remappedSymbolNames?.has(getSymbolId(sym))) return false; // If the context has a remapped name for the symbol, it *should* mean it's been made visible
85418543
const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*shouldComputeAliasesToMakeVisible*/ false);
85428544
if (accessibleResult.accessibility === SymbolAccessibility.Accessible) {
85438545
// Lookup the root symbol of the chain of refs we'll use to access it and serialize it
@@ -8790,9 +8792,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
87908792
// If it's a property: emit `export default _default` with a `_default` prop
87918793
// If it's a class/interface/function: emit a class/interface/function with a `default` modifier
87928794
// These forms can merge, eg (`export default 12; export default interface A {}`)
8793-
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void {
8794-
const symbolName = unescapeLeadingUnderscores(symbol.escapedName);
8795-
const isDefault = symbol.escapedName === InternalSymbolName.Default;
8795+
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean, escapedSymbolName = symbol.escapedName): void {
8796+
const symbolName = unescapeLeadingUnderscores(escapedSymbolName);
8797+
const isDefault = escapedSymbolName === InternalSymbolName.Default;
87968798
if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) {
87978799
// Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :(
87988800
context.encounteredError = true;
@@ -8811,7 +8813,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
88118813
const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0);
88128814
const isConstMergedWithNS = symbol.flags & SymbolFlags.Module &&
88138815
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) &&
8814-
symbol.escapedName !== InternalSymbolName.ExportEquals;
8816+
escapedSymbolName !== InternalSymbolName.ExportEquals;
88158817
const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol);
88168818
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) {
88178819
serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
@@ -8823,7 +8825,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
88238825
// symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is.
88248826
if (
88258827
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property | SymbolFlags.Accessor)
8826-
&& symbol.escapedName !== InternalSymbolName.ExportEquals
8828+
&& escapedSymbolName !== InternalSymbolName.ExportEquals
88278829
&& !(symbol.flags & SymbolFlags.Prototype)
88288830
&& !(symbol.flags & SymbolFlags.Class)
88298831
&& !(symbol.flags & SymbolFlags.Method)
@@ -8839,7 +8841,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
88398841
else {
88408842
const type = getTypeOfSymbol(symbol);
88418843
const localName = getInternalSymbolName(symbol, symbolName);
8842-
if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) {
8844+
if (type.symbol && type.symbol !== symbol && type.symbol.flags & SymbolFlags.Function && some(type.symbol.declarations, isFunctionExpressionOrArrowFunction) && (type.symbol.members?.size || type.symbol.exports?.size)) {
8845+
// assignment of a anonymous expando/class-like function, the func/ns/merge branch below won't trigger,
8846+
// and the assignment form has to reference the unreachable anonymous type so will error.
8847+
// Instead, serialize the type's symbol, but with the current symbol's name, rather than the anonymous one.
8848+
if (!context.remappedSymbolReferences) {
8849+
context.remappedSymbolReferences = new Map();
8850+
}
8851+
context.remappedSymbolReferences.set(getSymbolId(type.symbol), symbol); // save name remapping as local name for target symbol
8852+
serializeSymbolWorker(type.symbol, isPrivate, propertyAsAlias, escapedSymbolName);
8853+
context.remappedSymbolReferences.delete(getSymbolId(type.symbol));
8854+
}
8855+
else if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) {
88438856
// If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns
88448857
serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags);
88458858
}
@@ -10159,6 +10172,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1015910172
* It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`.
1016010173
*/
1016110174
function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string {
10175+
if (context?.remappedSymbolReferences?.has(getSymbolId(symbol))) {
10176+
symbol = context.remappedSymbolReferences.get(getSymbolId(symbol))!;
10177+
}
1016210178
if (
1016310179
context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) &&
1016410180
// If it's not the first part of an entity name, it must print as `default`
@@ -49986,6 +50002,7 @@ interface NodeBuilderContext {
4998650002
typeParameterNamesByTextNextNameCount?: Map<string, number>;
4998750003
usedSymbolNames?: Set<string>;
4998850004
remappedSymbolNames?: Map<SymbolId, string>;
50005+
remappedSymbolReferences?: Map<SymbolId, Symbol>;
4998950006
reverseMappedStack?: ReverseMappedSymbol[];
4999050007
}
4999150008

tests/baselines/reference/jsDeclarationsFunctionClassesCjsExportAssignment.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ type Input = {
228228
timer: Timer;
229229
hook: Hook;
230230
};
231+
/**
232+
* Imports
233+
*/
234+
type Timer = import("./timer");
235+
/**
236+
* Imports
237+
*/
238+
type Hook = import("./hook");
231239
/**
232240
* Imports
233241
*/
@@ -239,14 +247,6 @@ type State = {
239247
timer: Timer;
240248
hook: Hook;
241249
};
242-
/**
243-
* Imports
244-
*/
245-
type Timer = import("./timer");
246-
/**
247-
* Imports
248-
*/
249-
type Hook = import("./hook");
250250
//// [hook.d.ts]
251251
export = Hook;
252252
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////
2+
3+
//// [file.js]
4+
const SomeConstructor = function () {
5+
this.x = 1;
6+
};
7+
8+
const SomeConstructor2 = function () {
9+
};
10+
SomeConstructor2.staticMember = "str";
11+
12+
const SomeConstructor3 = function () {
13+
this.x = 1;
14+
};
15+
SomeConstructor3.staticMember = "str";
16+
17+
18+
19+
20+
//// [file.d.ts]
21+
declare function SomeConstructor(): void;
22+
declare class SomeConstructor {
23+
x: number;
24+
}
25+
declare function SomeConstructor2(): void;
26+
declare namespace SomeConstructor2 {
27+
let staticMember: string;
28+
}
29+
declare function SomeConstructor3(): void;
30+
declare namespace SomeConstructor3 {
31+
let staticMember_1: string;
32+
export { staticMember_1 as staticMember };
33+
}
34+
declare class SomeConstructor3 {
35+
x: number;
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////
2+
3+
=== file.js ===
4+
const SomeConstructor = function () {
5+
>SomeConstructor : Symbol(SomeConstructor, Decl(file.js, 0, 5))
6+
7+
this.x = 1;
8+
>this.x : Symbol(SomeConstructor.x, Decl(file.js, 0, 37))
9+
>this : Symbol(SomeConstructor, Decl(file.js, 0, 23))
10+
>x : Symbol(SomeConstructor.x, Decl(file.js, 0, 37))
11+
12+
};
13+
14+
const SomeConstructor2 = function () {
15+
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
16+
17+
};
18+
SomeConstructor2.staticMember = "str";
19+
>SomeConstructor2.staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
20+
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
21+
>staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
22+
23+
const SomeConstructor3 = function () {
24+
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
25+
26+
this.x = 1;
27+
>this.x : Symbol(SomeConstructor3.x, Decl(file.js, 8, 38))
28+
>this : Symbol(SomeConstructor3, Decl(file.js, 8, 24))
29+
>x : Symbol(SomeConstructor3.x, Decl(file.js, 8, 38))
30+
31+
};
32+
SomeConstructor3.staticMember = "str";
33+
>SomeConstructor3.staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
34+
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
35+
>staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
36+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////
2+
3+
=== file.js ===
4+
const SomeConstructor = function () {
5+
>SomeConstructor : typeof SomeConstructor
6+
>function () { this.x = 1;} : typeof SomeConstructor
7+
8+
this.x = 1;
9+
>this.x = 1 : 1
10+
>this.x : any
11+
>this : this
12+
>x : any
13+
>1 : 1
14+
15+
};
16+
17+
const SomeConstructor2 = function () {
18+
>SomeConstructor2 : { (): void; staticMember: string; }
19+
>function () {} : { (): void; staticMember: string; }
20+
21+
};
22+
SomeConstructor2.staticMember = "str";
23+
>SomeConstructor2.staticMember = "str" : "str"
24+
>SomeConstructor2.staticMember : string
25+
>SomeConstructor2 : { (): void; staticMember: string; }
26+
>staticMember : string
27+
>"str" : "str"
28+
29+
const SomeConstructor3 = function () {
30+
>SomeConstructor3 : typeof SomeConstructor3
31+
>function () { this.x = 1;} : typeof SomeConstructor3
32+
33+
this.x = 1;
34+
>this.x = 1 : 1
35+
>this.x : any
36+
>this : this
37+
>x : any
38+
>1 : 1
39+
40+
};
41+
SomeConstructor3.staticMember = "str";
42+
>SomeConstructor3.staticMember = "str" : "str"
43+
>SomeConstructor3.staticMember : string
44+
>SomeConstructor3 : typeof SomeConstructor3
45+
>staticMember : string
46+
>"str" : "str"
47+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunctionNamed.ts] ////
2+
3+
//// [file.js]
4+
const SomeConstructor = function Named() {
5+
this.x = 1;
6+
};
7+
8+
const SomeConstructor2 = function Named() {
9+
};
10+
SomeConstructor2.staticMember = "str";
11+
12+
const SomeConstructor3 = function Named() {
13+
this.x = 1;
14+
};
15+
SomeConstructor3.staticMember = "str";
16+
17+
const SelfReference = function Named() {
18+
if (!(this instanceof Named)) return new Named();
19+
this.x = 1;
20+
}
21+
SelfReference.staticMember = "str";
22+
23+
24+
25+
26+
//// [file.d.ts]
27+
declare function SomeConstructor(): void;
28+
declare class SomeConstructor {
29+
x: number;
30+
}
31+
declare function SomeConstructor2(): void;
32+
declare namespace SomeConstructor2 {
33+
let staticMember: string;
34+
}
35+
declare function SomeConstructor3(): void;
36+
declare namespace SomeConstructor3 {
37+
let staticMember_1: string;
38+
export { staticMember_1 as staticMember };
39+
}
40+
declare class SomeConstructor3 {
41+
x: number;
42+
}
43+
declare function SelfReference(): SelfReference;
44+
declare namespace SelfReference {
45+
let staticMember_2: string;
46+
export { staticMember_2 as staticMember };
47+
}
48+
declare class SelfReference {
49+
x: number;
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunctionNamed.ts] ////
2+
3+
=== file.js ===
4+
const SomeConstructor = function Named() {
5+
>SomeConstructor : Symbol(SomeConstructor, Decl(file.js, 0, 5))
6+
>Named : Symbol(Named, Decl(file.js, 0, 23))
7+
8+
this.x = 1;
9+
>this.x : Symbol(Named.x, Decl(file.js, 0, 42))
10+
>this : Symbol(Named, Decl(file.js, 0, 23))
11+
>x : Symbol(Named.x, Decl(file.js, 0, 42))
12+
13+
};
14+
15+
const SomeConstructor2 = function Named() {
16+
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
17+
>Named : Symbol(Named, Decl(file.js, 4, 24))
18+
19+
};
20+
SomeConstructor2.staticMember = "str";
21+
>SomeConstructor2.staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
22+
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
23+
>staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
24+
25+
const SomeConstructor3 = function Named() {
26+
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
27+
>Named : Symbol(Named, Decl(file.js, 8, 24))
28+
29+
this.x = 1;
30+
>this.x : Symbol(Named.x, Decl(file.js, 8, 43))
31+
>this : Symbol(Named, Decl(file.js, 8, 24))
32+
>x : Symbol(Named.x, Decl(file.js, 8, 43))
33+
34+
};
35+
SomeConstructor3.staticMember = "str";
36+
>SomeConstructor3.staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
37+
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
38+
>staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
39+
40+
const SelfReference = function Named() {
41+
>SelfReference : Symbol(SelfReference, Decl(file.js, 13, 5), Decl(file.js, 16, 1))
42+
>Named : Symbol(Named, Decl(file.js, 13, 21))
43+
44+
if (!(this instanceof Named)) return new Named();
45+
>this : Symbol(Named, Decl(file.js, 13, 21))
46+
>Named : Symbol(Named, Decl(file.js, 13, 21))
47+
>Named : Symbol(Named, Decl(file.js, 13, 21))
48+
49+
this.x = 1;
50+
>this.x : Symbol(Named.x, Decl(file.js, 14, 53))
51+
>this : Symbol(Named, Decl(file.js, 13, 21))
52+
>x : Symbol(Named.x, Decl(file.js, 14, 53))
53+
}
54+
SelfReference.staticMember = "str";
55+
>SelfReference.staticMember : Symbol(SelfReference.staticMember, Decl(file.js, 16, 1))
56+
>SelfReference : Symbol(SelfReference, Decl(file.js, 13, 5), Decl(file.js, 16, 1))
57+
>staticMember : Symbol(SelfReference.staticMember, Decl(file.js, 16, 1))
58+

0 commit comments

Comments
 (0)