Skip to content

Commit 2532806

Browse files
committed
Emit as class with private ctor
1 parent 4a8526b commit 2532806

File tree

2 files changed

+19
-23
lines changed

2 files changed

+19
-23
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5949,24 +5949,6 @@ namespace ts {
59495949
}
59505950
}
59515951

5952-
function isEffectiveClassSymbol(symbol: Symbol) {
5953-
if (!(symbol.flags & SymbolFlags.Class)) {
5954-
return false;
5955-
}
5956-
if (isInJSFile(symbol.valueDeclaration) && !isClassLike(symbol.valueDeclaration)) {
5957-
// For a symbol that isn't syntactically a `class` in a JS file we have heuristics
5958-
// that detect prototype assignments that indicate the symbol is *probably* a class.
5959-
// Filter out any prototype assignments for non-class symbols, i.e.
5960-
//
5961-
// let A;
5962-
// A = {};
5963-
// A.prototype.b = {};
5964-
const type = getTypeOfSymbol(symbol);
5965-
return some(getSignaturesOfType(type, SignatureKind.Construct))
5966-
|| some(getSignaturesOfType(type, SignatureKind.Call));
5967-
}
5968-
return true;
5969-
}
59705952

59715953
// Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias
59725954
// or a merge of some number of those.
@@ -6009,14 +5991,14 @@ namespace ts {
60095991
if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property)
60105992
&& symbol.escapedName !== InternalSymbolName.ExportEquals
60115993
&& !(symbol.flags & SymbolFlags.Prototype)
6012-
&& !isEffectiveClassSymbol(symbol)
5994+
&& !(symbol.flags & SymbolFlags.Class)
60135995
&& !isConstMergedWithNSPrintableAsSignatureMerge) {
60145996
serializeVariableOrProperty(symbol, symbolName, isPrivate, needsPostExportDefault, propertyAsAlias, modifierFlags);
60155997
}
60165998
if (symbol.flags & SymbolFlags.Enum) {
60175999
serializeEnum(symbol, symbolName, modifierFlags);
60186000
}
6019-
if (isEffectiveClassSymbol(symbol)) {
6001+
if (symbol.flags & SymbolFlags.Class) {
60206002
if (symbol.flags & SymbolFlags.Property && isBinaryExpression(symbol.valueDeclaration.parent) && isClassExpression(symbol.valueDeclaration.parent.right)) {
60216003
// Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members,
60226004
// since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property
@@ -6336,7 +6318,8 @@ namespace ts {
63366318
const baseTypes = getBaseTypes(classType);
63376319
const implementsTypes = getImplementsTypes(classType);
63386320
const staticType = getTypeOfSymbol(symbol);
6339-
const staticBaseType = staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration)
6321+
const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration);
6322+
const staticBaseType = isClass
63406323
? getBaseConstructorTypeOfClass(staticType as InterfaceType)
63416324
: anyType;
63426325
const heritageClauses = [
@@ -6374,7 +6357,17 @@ namespace ts {
63746357
const staticMembers = flatMap(
63756358
filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" && !isNamespaceMember(p)),
63766359
p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType));
6377-
const constructors = serializeSignatures(SignatureKind.Construct, staticType, baseTypes[0], SyntaxKind.Constructor) as ConstructorDeclaration[];
6360+
// When we encounter an `X.prototype.y` assignment in a JS file, we bind `X` as a class regardless as to whether
6361+
// the value is ever initialized with a class or function-like value. For cases where `X` could never be
6362+
// created via `new`, we will inject a `private constructor()` declaration to indicate it is not createable.
6363+
const isNonConstructableClassLikeInJsFile =
6364+
!isClass &&
6365+
!!symbol.valueDeclaration &&
6366+
isInJSFile(symbol.valueDeclaration) &&
6367+
!some(getSignaturesOfType(staticType, SignatureKind.Construct));
6368+
const constructors = isNonConstructableClassLikeInJsFile ?
6369+
[createConstructor(/*decorators*/ undefined, createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] :
6370+
serializeSignatures(SignatureKind.Construct, staticType, baseTypes[0], SyntaxKind.Constructor) as ConstructorDeclaration[];
63786371
for (const c of constructors) {
63796372
// A constructor's return type and type parameters are supposed to be controlled by the enclosing class declaration
63806373
// `signatureToSignatureDeclarationHelper` appends them regardless, so for now we delete them here

tests/baselines/reference/jsDeclarationsClassLikeHeuristic.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ A.prototype.b = {};
1212

1313

1414
//// [index.d.ts]
15-
declare let A: any;
15+
declare class A {
16+
private constructor();
17+
b: {};
18+
}

0 commit comments

Comments
 (0)