Skip to content

Commit 36e212b

Browse files
authored
Report circular JSDoc type references (#27404)
JSDoc types references can often be to values, which can often be circular in ways that types tied to declarations cannot. I decided to create a separate property on SymbolLinks rather than reusing declaredType, although I'm not sure that's strictly required.
1 parent f23845a commit 36e212b

7 files changed

+71
-4
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ namespace ts {
651651
ResolvedReturnType,
652652
ImmediateBaseConstraint,
653653
EnumTagType,
654+
JSDocTypeReference,
654655
}
655656

656657
const enum CheckMode {
@@ -4490,6 +4491,8 @@ namespace ts {
44904491
return !!(<Signature>target).resolvedReturnType;
44914492
case TypeSystemPropertyName.ImmediateBaseConstraint:
44924493
return !!(<Type>target).immediateBaseConstraint;
4494+
case TypeSystemPropertyName.JSDocTypeReference:
4495+
return !!getSymbolLinks(target as Symbol).resolvedJSDocType;
44934496
}
44944497
return Debug.assertNever(propertyName);
44954498
}
@@ -8258,7 +8261,7 @@ namespace ts {
82588261
return type;
82598262
}
82608263

8261-
// JS are 'string' or 'number', not an enum type.
8264+
// JS enums are 'string' or 'number', not an enum type.
82628265
const enumTag = isInJSFile(node) && symbol.valueDeclaration && getJSDocEnumTag(symbol.valueDeclaration);
82638266
if (enumTag) {
82648267
const links = getNodeLinks(enumTag);
@@ -8301,12 +8304,21 @@ namespace ts {
83018304
* the type of this reference is just the type of the value we resolved to.
83028305
*/
83038306
function getJSDocTypeReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined {
8307+
if (!pushTypeResolution(symbol, TypeSystemPropertyName.JSDocTypeReference)) {
8308+
return errorType;
8309+
}
83048310
const assignedType = getAssignedClassType(symbol);
83058311
const valueType = getTypeOfSymbol(symbol);
83068312
const referenceType = valueType.symbol && valueType.symbol !== symbol && !isInferredClassType(valueType) && getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments);
8313+
if (!popTypeResolution()) {
8314+
getSymbolLinks(symbol).resolvedJSDocType = errorType;
8315+
error(node, Diagnostics.JSDoc_type_0_circularly_references_itself, symbolToString(symbol));
8316+
return errorType;
8317+
}
83078318
if (referenceType || assignedType) {
83088319
// TODO: GH#18217 (should the `|| assignedType` be at a lower precedence?)
8309-
return (referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType)!;
8320+
const type = (referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType)!;
8321+
return getSymbolLinks(symbol).resolvedJSDocType = type;
83108322
}
83118323
}
83128324

@@ -8443,7 +8455,7 @@ namespace ts {
84438455
symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning);
84448456
type = getTypeReferenceType(node, symbol);
84458457
}
8446-
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the
8458+
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the
84478459
// type reference in checkTypeReferenceNode.
84488460
links.resolvedSymbol = symbol;
84498461
links.resolvedType = type;

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2116,6 +2116,10 @@
21162116
"category": "Error",
21172117
"code": 2586
21182118
},
2119+
"JSDoc type '{0}' circularly references itself.": {
2120+
"category": "Error",
2121+
"code": 2587
2122+
},
21192123
"JSX element attributes type '{0}' may not be a union type.": {
21202124
"category": "Error",
21212125
"code": 2600
@@ -4680,4 +4684,4 @@
46804684
"category": "Message",
46814685
"code": 95068
46824686
}
4683-
}
4687+
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3535,6 +3535,7 @@ namespace ts {
35353535
type?: Type; // Type of value symbol
35363536
uniqueESSymbolType?: Type; // UniqueESSymbol type for a symbol
35373537
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
3538+
resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference
35383539
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
35393540
outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type
35403541
inferredClassType?: Type; // Type of an inferred ES5 class
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
tests/cases/conformance/jsdoc/bug27346.js(2,4): error TS8030: The type of a function declaration must match the function's signature.
2+
tests/cases/conformance/jsdoc/bug27346.js(2,11): error TS2587: JSDoc type 'MyClass' circularly references itself.
3+
4+
5+
==== tests/cases/conformance/jsdoc/bug27346.js (2 errors) ====
6+
/**
7+
* @type {MyClass}
8+
~~~~~~~~~~~~~~~
9+
!!! error TS8030: The type of a function declaration must match the function's signature.
10+
~~~~~~~
11+
!!! error TS2587: JSDoc type 'MyClass' circularly references itself.
12+
*/
13+
function MyClass() { }
14+
MyClass.prototype = {};
15+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/jsdoc/bug27346.js ===
2+
/**
3+
* @type {MyClass}
4+
*/
5+
function MyClass() { }
6+
>MyClass : Symbol(MyClass, Decl(bug27346.js, 0, 0), Decl(bug27346.js, 3, 22))
7+
8+
MyClass.prototype = {};
9+
>MyClass.prototype : Symbol(MyClass.prototype, Decl(bug27346.js, 3, 22))
10+
>MyClass : Symbol(MyClass, Decl(bug27346.js, 0, 0), Decl(bug27346.js, 3, 22))
11+
>prototype : Symbol(MyClass.prototype, Decl(bug27346.js, 3, 22))
12+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
=== tests/cases/conformance/jsdoc/bug27346.js ===
2+
/**
3+
* @type {MyClass}
4+
*/
5+
function MyClass() { }
6+
>MyClass : typeof MyClass
7+
8+
MyClass.prototype = {};
9+
>MyClass.prototype = {} : {}
10+
>MyClass.prototype : {}
11+
>MyClass : typeof MyClass
12+
>prototype : {}
13+
>{} : {}
14+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @allowJs: true
2+
// @noEmit: true
3+
// @checkJs: true
4+
// @Filename: bug27346.js
5+
/**
6+
* @type {MyClass}
7+
*/
8+
function MyClass() { }
9+
MyClass.prototype = {};

0 commit comments

Comments
 (0)