Skip to content

Commit 4a8526b

Browse files
committed
Fix crash in JS decl emit
1 parent 7b03835 commit 4a8526b

File tree

6 files changed

+91
-3
lines changed

6 files changed

+91
-3
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5949,6 +5949,25 @@ 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+
}
5970+
59525971
// Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias
59535972
// or a merge of some number of those.
59545973
// An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping
@@ -5990,14 +6009,14 @@ namespace ts {
59906009
if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property)
59916010
&& symbol.escapedName !== InternalSymbolName.ExportEquals
59926011
&& !(symbol.flags & SymbolFlags.Prototype)
5993-
&& !(symbol.flags & SymbolFlags.Class)
6012+
&& !isEffectiveClassSymbol(symbol)
59946013
&& !isConstMergedWithNSPrintableAsSignatureMerge) {
59956014
serializeVariableOrProperty(symbol, symbolName, isPrivate, needsPostExportDefault, propertyAsAlias, modifierFlags);
59966015
}
59976016
if (symbol.flags & SymbolFlags.Enum) {
59986017
serializeEnum(symbol, symbolName, modifierFlags);
59996018
}
6000-
if (symbol.flags & SymbolFlags.Class) {
6019+
if (isEffectiveClassSymbol(symbol)) {
60016020
if (symbol.flags & SymbolFlags.Property && isBinaryExpression(symbol.valueDeclaration.parent) && isClassExpression(symbol.valueDeclaration.parent.right)) {
60026021
// Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members,
60036022
// since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property
@@ -6317,7 +6336,9 @@ namespace ts {
63176336
const baseTypes = getBaseTypes(classType);
63186337
const implementsTypes = getImplementsTypes(classType);
63196338
const staticType = getTypeOfSymbol(symbol);
6320-
const staticBaseType = getBaseConstructorTypeOfClass(staticType as InterfaceType);
6339+
const staticBaseType = staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration)
6340+
? getBaseConstructorTypeOfClass(staticType as InterfaceType)
6341+
: anyType;
63216342
const heritageClauses = [
63226343
...!length(baseTypes) ? [] : [createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))],
63236344
...!length(implementsTypes) ? [] : [createHeritageClause(SyntaxKind.ImplementsKeyword, map(implementsTypes, b => serializeBaseType(b, staticBaseType, localName)))]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tests/cases/conformance/jsdoc/declarations/index.js(4,3): error TS2339: Property 'prototype' does not exist on type '{}'.
2+
3+
4+
==== tests/cases/conformance/jsdoc/declarations/index.js (1 errors) ====
5+
// https://github.com/microsoft/TypeScript/issues/35801
6+
let A;
7+
A = {};
8+
A.prototype.b = {};
9+
~~~~~~~~~
10+
!!! error TS2339: Property 'prototype' does not exist on type '{}'.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [index.js]
2+
// https://github.com/microsoft/TypeScript/issues/35801
3+
let A;
4+
A = {};
5+
A.prototype.b = {};
6+
7+
//// [index.js]
8+
// https://github.com/microsoft/TypeScript/issues/35801
9+
var A;
10+
A = {};
11+
A.prototype.b = {};
12+
13+
14+
//// [index.d.ts]
15+
declare let A: any;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
// https://github.com/microsoft/TypeScript/issues/35801
3+
let A;
4+
>A : Symbol(A, Decl(index.js, 1, 3))
5+
6+
A = {};
7+
>A : Symbol(A, Decl(index.js, 1, 3))
8+
9+
A.prototype.b = {};
10+
>A.prototype : Symbol(A.b, Decl(index.js, 2, 7))
11+
>A : Symbol(A, Decl(index.js, 1, 3))
12+
>b : Symbol(A.b, Decl(index.js, 2, 7))
13+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
// https://github.com/microsoft/TypeScript/issues/35801
3+
let A;
4+
>A : any
5+
6+
A = {};
7+
>A = {} : {}
8+
>A : any
9+
>{} : {}
10+
11+
A.prototype.b = {};
12+
>A.prototype.b = {} : {}
13+
>A.prototype.b : any
14+
>A.prototype : any
15+
>A : {}
16+
>prototype : any
17+
>b : any
18+
>{} : {}
19+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @target: es5
4+
// @outDir: ./out
5+
// @declaration: true
6+
// @filename: index.js
7+
// https://github.com/microsoft/TypeScript/issues/35801
8+
let A;
9+
A = {};
10+
A.prototype.b = {};

0 commit comments

Comments
 (0)