Avoid trying to emit anonymous classish/expando functions as assignments (#55472)

This commit is contained in:
Wesley Wigham
2023-08-28 12:15:53 -07:00
committed by GitHub
parent 9d0dc77712
commit 5ce34cafad
10 changed files with 374 additions and 14 deletions
+23 -6
View File
@@ -8533,11 +8533,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
...oldcontext,
usedSymbolNames: new Set(oldcontext.usedSymbolNames),
remappedSymbolNames: new Map(),
remappedSymbolReferences: new Map(oldcontext.remappedSymbolReferences?.entries()),
tracker: undefined!,
};
const tracker: SymbolTracker = {
...oldcontext.tracker.inner,
trackSymbol: (sym, decl, meaning) => {
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
const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*shouldComputeAliasesToMakeVisible*/ false);
if (accessibleResult.accessibility === SymbolAccessibility.Accessible) {
// 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 {
// If it's a property: emit `export default _default` with a `_default` prop
// If it's a class/interface/function: emit a class/interface/function with a `default` modifier
// These forms can merge, eg (`export default 12; export default interface A {}`)
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void {
const symbolName = unescapeLeadingUnderscores(symbol.escapedName);
const isDefault = symbol.escapedName === InternalSymbolName.Default;
function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean, escapedSymbolName = symbol.escapedName): void {
const symbolName = unescapeLeadingUnderscores(escapedSymbolName);
const isDefault = escapedSymbolName === InternalSymbolName.Default;
if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) {
// 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` :(
context.encounteredError = true;
@@ -8811,7 +8813,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0);
const isConstMergedWithNS = symbol.flags & SymbolFlags.Module &&
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) &&
symbol.escapedName !== InternalSymbolName.ExportEquals;
escapedSymbolName !== InternalSymbolName.ExportEquals;
const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol);
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) {
serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags);
@@ -8823,7 +8825,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is.
if (
symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property | SymbolFlags.Accessor)
&& symbol.escapedName !== InternalSymbolName.ExportEquals
&& escapedSymbolName !== InternalSymbolName.ExportEquals
&& !(symbol.flags & SymbolFlags.Prototype)
&& !(symbol.flags & SymbolFlags.Class)
&& !(symbol.flags & SymbolFlags.Method)
@@ -8839,7 +8841,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
else {
const type = getTypeOfSymbol(symbol);
const localName = getInternalSymbolName(symbol, symbolName);
if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) {
if (type.symbol && type.symbol !== symbol && type.symbol.flags & SymbolFlags.Function && some(type.symbol.declarations, isFunctionExpressionOrArrowFunction) && (type.symbol.members?.size || type.symbol.exports?.size)) {
// assignment of a anonymous expando/class-like function, the func/ns/merge branch below won't trigger,
// and the assignment form has to reference the unreachable anonymous type so will error.
// Instead, serialize the type's symbol, but with the current symbol's name, rather than the anonymous one.
if (!context.remappedSymbolReferences) {
context.remappedSymbolReferences = new Map();
}
context.remappedSymbolReferences.set(getSymbolId(type.symbol), symbol); // save name remapping as local name for target symbol
serializeSymbolWorker(type.symbol, isPrivate, propertyAsAlias, escapedSymbolName);
context.remappedSymbolReferences.delete(getSymbolId(type.symbol));
}
else if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) {
// 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
serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags);
}
@@ -10159,6 +10172,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`.
*/
function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string {
if (context?.remappedSymbolReferences?.has(getSymbolId(symbol))) {
symbol = context.remappedSymbolReferences.get(getSymbolId(symbol))!;
}
if (
context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) &&
// If it's not the first part of an entity name, it must print as `default`
@@ -49986,6 +50002,7 @@ interface NodeBuilderContext {
typeParameterNamesByTextNextNameCount?: Map<string, number>;
usedSymbolNames?: Set<string>;
remappedSymbolNames?: Map<SymbolId, string>;
remappedSymbolReferences?: Map<SymbolId, Symbol>;
reverseMappedStack?: ReverseMappedSymbol[];
}
@@ -228,6 +228,14 @@ type Input = {
timer: Timer;
hook: Hook;
};
/**
* Imports
*/
type Timer = import("./timer");
/**
* Imports
*/
type Hook = import("./hook");
/**
* Imports
*/
@@ -239,14 +247,6 @@ type State = {
timer: Timer;
hook: Hook;
};
/**
* Imports
*/
type Timer = import("./timer");
/**
* Imports
*/
type Hook = import("./hook");
//// [hook.d.ts]
export = Hook;
/**
@@ -0,0 +1,36 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////
//// [file.js]
const SomeConstructor = function () {
this.x = 1;
};
const SomeConstructor2 = function () {
};
SomeConstructor2.staticMember = "str";
const SomeConstructor3 = function () {
this.x = 1;
};
SomeConstructor3.staticMember = "str";
//// [file.d.ts]
declare function SomeConstructor(): void;
declare class SomeConstructor {
x: number;
}
declare function SomeConstructor2(): void;
declare namespace SomeConstructor2 {
let staticMember: string;
}
declare function SomeConstructor3(): void;
declare namespace SomeConstructor3 {
let staticMember_1: string;
export { staticMember_1 as staticMember };
}
declare class SomeConstructor3 {
x: number;
}
@@ -0,0 +1,36 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////
=== file.js ===
const SomeConstructor = function () {
>SomeConstructor : Symbol(SomeConstructor, Decl(file.js, 0, 5))
this.x = 1;
>this.x : Symbol(SomeConstructor.x, Decl(file.js, 0, 37))
>this : Symbol(SomeConstructor, Decl(file.js, 0, 23))
>x : Symbol(SomeConstructor.x, Decl(file.js, 0, 37))
};
const SomeConstructor2 = function () {
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
};
SomeConstructor2.staticMember = "str";
>SomeConstructor2.staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
>staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
const SomeConstructor3 = function () {
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
this.x = 1;
>this.x : Symbol(SomeConstructor3.x, Decl(file.js, 8, 38))
>this : Symbol(SomeConstructor3, Decl(file.js, 8, 24))
>x : Symbol(SomeConstructor3.x, Decl(file.js, 8, 38))
};
SomeConstructor3.staticMember = "str";
>SomeConstructor3.staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
>staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
@@ -0,0 +1,47 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunction.ts] ////
=== file.js ===
const SomeConstructor = function () {
>SomeConstructor : typeof SomeConstructor
>function () { this.x = 1;} : typeof SomeConstructor
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : this
>x : any
>1 : 1
};
const SomeConstructor2 = function () {
>SomeConstructor2 : { (): void; staticMember: string; }
>function () {} : { (): void; staticMember: string; }
};
SomeConstructor2.staticMember = "str";
>SomeConstructor2.staticMember = "str" : "str"
>SomeConstructor2.staticMember : string
>SomeConstructor2 : { (): void; staticMember: string; }
>staticMember : string
>"str" : "str"
const SomeConstructor3 = function () {
>SomeConstructor3 : typeof SomeConstructor3
>function () { this.x = 1;} : typeof SomeConstructor3
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : this
>x : any
>1 : 1
};
SomeConstructor3.staticMember = "str";
>SomeConstructor3.staticMember = "str" : "str"
>SomeConstructor3.staticMember : string
>SomeConstructor3 : typeof SomeConstructor3
>staticMember : string
>"str" : "str"
@@ -0,0 +1,50 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunctionNamed.ts] ////
//// [file.js]
const SomeConstructor = function Named() {
this.x = 1;
};
const SomeConstructor2 = function Named() {
};
SomeConstructor2.staticMember = "str";
const SomeConstructor3 = function Named() {
this.x = 1;
};
SomeConstructor3.staticMember = "str";
const SelfReference = function Named() {
if (!(this instanceof Named)) return new Named();
this.x = 1;
}
SelfReference.staticMember = "str";
//// [file.d.ts]
declare function SomeConstructor(): void;
declare class SomeConstructor {
x: number;
}
declare function SomeConstructor2(): void;
declare namespace SomeConstructor2 {
let staticMember: string;
}
declare function SomeConstructor3(): void;
declare namespace SomeConstructor3 {
let staticMember_1: string;
export { staticMember_1 as staticMember };
}
declare class SomeConstructor3 {
x: number;
}
declare function SelfReference(): SelfReference;
declare namespace SelfReference {
let staticMember_2: string;
export { staticMember_2 as staticMember };
}
declare class SelfReference {
x: number;
}
@@ -0,0 +1,58 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunctionNamed.ts] ////
=== file.js ===
const SomeConstructor = function Named() {
>SomeConstructor : Symbol(SomeConstructor, Decl(file.js, 0, 5))
>Named : Symbol(Named, Decl(file.js, 0, 23))
this.x = 1;
>this.x : Symbol(Named.x, Decl(file.js, 0, 42))
>this : Symbol(Named, Decl(file.js, 0, 23))
>x : Symbol(Named.x, Decl(file.js, 0, 42))
};
const SomeConstructor2 = function Named() {
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
>Named : Symbol(Named, Decl(file.js, 4, 24))
};
SomeConstructor2.staticMember = "str";
>SomeConstructor2.staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
>SomeConstructor2 : Symbol(SomeConstructor2, Decl(file.js, 4, 5), Decl(file.js, 5, 2))
>staticMember : Symbol(SomeConstructor2.staticMember, Decl(file.js, 5, 2))
const SomeConstructor3 = function Named() {
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
>Named : Symbol(Named, Decl(file.js, 8, 24))
this.x = 1;
>this.x : Symbol(Named.x, Decl(file.js, 8, 43))
>this : Symbol(Named, Decl(file.js, 8, 24))
>x : Symbol(Named.x, Decl(file.js, 8, 43))
};
SomeConstructor3.staticMember = "str";
>SomeConstructor3.staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
>SomeConstructor3 : Symbol(SomeConstructor3, Decl(file.js, 8, 5), Decl(file.js, 10, 2))
>staticMember : Symbol(SomeConstructor3.staticMember, Decl(file.js, 10, 2))
const SelfReference = function Named() {
>SelfReference : Symbol(SelfReference, Decl(file.js, 13, 5), Decl(file.js, 16, 1))
>Named : Symbol(Named, Decl(file.js, 13, 21))
if (!(this instanceof Named)) return new Named();
>this : Symbol(Named, Decl(file.js, 13, 21))
>Named : Symbol(Named, Decl(file.js, 13, 21))
>Named : Symbol(Named, Decl(file.js, 13, 21))
this.x = 1;
>this.x : Symbol(Named.x, Decl(file.js, 14, 53))
>this : Symbol(Named, Decl(file.js, 13, 21))
>x : Symbol(Named.x, Decl(file.js, 14, 53))
}
SelfReference.staticMember = "str";
>SelfReference.staticMember : Symbol(SelfReference.staticMember, Decl(file.js, 16, 1))
>SelfReference : Symbol(SelfReference, Decl(file.js, 13, 5), Decl(file.js, 16, 1))
>staticMember : Symbol(SelfReference.staticMember, Decl(file.js, 16, 1))
@@ -0,0 +1,78 @@
//// [tests/cases/compiler/jsDeclarationsGlobalFileConstFunctionNamed.ts] ////
=== file.js ===
const SomeConstructor = function Named() {
>SomeConstructor : typeof Named
>function Named() { this.x = 1;} : typeof Named
>Named : typeof Named
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : this
>x : any
>1 : 1
};
const SomeConstructor2 = function Named() {
>SomeConstructor2 : { (): void; staticMember: string; }
>function Named() {} : { (): void; staticMember: string; }
>Named : { (): void; staticMember: string; }
};
SomeConstructor2.staticMember = "str";
>SomeConstructor2.staticMember = "str" : "str"
>SomeConstructor2.staticMember : string
>SomeConstructor2 : { (): void; staticMember: string; }
>staticMember : string
>"str" : "str"
const SomeConstructor3 = function Named() {
>SomeConstructor3 : typeof Named
>function Named() { this.x = 1;} : typeof Named
>Named : typeof Named
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : this
>x : any
>1 : 1
};
SomeConstructor3.staticMember = "str";
>SomeConstructor3.staticMember = "str" : "str"
>SomeConstructor3.staticMember : string
>SomeConstructor3 : typeof Named
>staticMember : string
>"str" : "str"
const SelfReference = function Named() {
>SelfReference : typeof Named
>function Named() { if (!(this instanceof Named)) return new Named(); this.x = 1;} : typeof Named
>Named : typeof Named
if (!(this instanceof Named)) return new Named();
>!(this instanceof Named) : boolean
>(this instanceof Named) : boolean
>this instanceof Named : boolean
>this : this
>Named : typeof Named
>new Named() : Named
>Named : typeof Named
this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : this
>x : any
>1 : 1
}
SelfReference.staticMember = "str";
>SelfReference.staticMember = "str" : "str"
>SelfReference.staticMember : string
>SelfReference : typeof Named
>staticMember : string
>"str" : "str"
@@ -0,0 +1,16 @@
// @checkJs: true
// @declaration: true
// @emitDeclarationOnly: true
// @filename: file.js
const SomeConstructor = function () {
this.x = 1;
};
const SomeConstructor2 = function () {
};
SomeConstructor2.staticMember = "str";
const SomeConstructor3 = function () {
this.x = 1;
};
SomeConstructor3.staticMember = "str";
@@ -0,0 +1,22 @@
// @checkJs: true
// @declaration: true
// @emitDeclarationOnly: true
// @filename: file.js
const SomeConstructor = function Named() {
this.x = 1;
};
const SomeConstructor2 = function Named() {
};
SomeConstructor2.staticMember = "str";
const SomeConstructor3 = function Named() {
this.x = 1;
};
SomeConstructor3.staticMember = "str";
const SelfReference = function Named() {
if (!(this instanceof Named)) return new Named();
this.x = 1;
}
SelfReference.staticMember = "str";