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.
This commit is contained in:
Nathan Shively-Sanders 2018-10-05 15:41:09 -07:00
parent 6afa880aa3
commit a4a5b3806e
7 changed files with 71 additions and 4 deletions

View File

@ -666,6 +666,7 @@ namespace ts {
ResolvedReturnType,
ImmediateBaseConstraint,
EnumTagType,
JSDocTypeReference,
}
const enum CheckMode {
@ -4503,6 +4504,8 @@ namespace ts {
return !!(<Signature>target).resolvedReturnType;
case TypeSystemPropertyName.ImmediateBaseConstraint:
return !!(<Type>target).immediateBaseConstraint;
case TypeSystemPropertyName.JSDocTypeReference:
return !!getSymbolLinks(target as Symbol).resolvedJSDocType;
}
return Debug.assertNever(propertyName);
}
@ -8275,7 +8278,7 @@ namespace ts {
return type;
}
// JS are 'string' or 'number', not an enum type.
// JS enums are 'string' or 'number', not an enum type.
const enumTag = isInJSFile(node) && symbol.valueDeclaration && getJSDocEnumTag(symbol.valueDeclaration);
if (enumTag) {
const links = getNodeLinks(enumTag);
@ -8318,12 +8321,21 @@ namespace ts {
* the type of this reference is just the type of the value we resolved to.
*/
function getJSDocTypeReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.JSDocTypeReference)) {
return errorType;
}
const assignedType = getAssignedClassType(symbol);
const valueType = getTypeOfSymbol(symbol);
const referenceType = valueType.symbol && valueType.symbol !== symbol && !isInferredClassType(valueType) && getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments);
if (!popTypeResolution()) {
getSymbolLinks(symbol).resolvedJSDocType = errorType;
error(node, Diagnostics.JSDoc_type_0_circularly_references_itself, symbolToString(symbol));
return errorType;
}
if (referenceType || assignedType) {
// TODO: GH#18217 (should the `|| assignedType` be at a lower precedence?)
return (referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType)!;
const type = (referenceType && assignedType ? getIntersectionType([assignedType, referenceType]) : referenceType || assignedType)!;
return getSymbolLinks(symbol).resolvedJSDocType = type;
}
}
@ -8460,7 +8472,7 @@ namespace ts {
symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning);
type = getTypeReferenceType(node, symbol);
}
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the
// type reference in checkTypeReferenceNode.
links.resolvedSymbol = symbol;
links.resolvedType = type;

View File

@ -2116,6 +2116,10 @@
"category": "Error",
"code": 2586
},
"JSDoc type '{0}' circularly references itself.": {
"category": "Error",
"code": 2587
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
@ -4684,4 +4688,4 @@
"category": "Message",
"code": 95068
}
}
}

View File

@ -3536,6 +3536,7 @@ namespace ts {
type?: Type; // Type of value symbol
uniqueESSymbolType?: Type; // UniqueESSymbol type for a symbol
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type
inferredClassType?: Type; // Type of an inferred ES5 class

View File

@ -0,0 +1,15 @@
tests/cases/conformance/jsdoc/bug27346.js(2,4): error TS8030: The type of a function declaration must match the function's signature.
tests/cases/conformance/jsdoc/bug27346.js(2,11): error TS2587: JSDoc type 'MyClass' circularly references itself.
==== tests/cases/conformance/jsdoc/bug27346.js (2 errors) ====
/**
* @type {MyClass}
~~~~~~~~~~~~~~~
!!! error TS8030: The type of a function declaration must match the function's signature.
~~~~~~~
!!! error TS2587: JSDoc type 'MyClass' circularly references itself.
*/
function MyClass() { }
MyClass.prototype = {};

View File

@ -0,0 +1,12 @@
=== tests/cases/conformance/jsdoc/bug27346.js ===
/**
* @type {MyClass}
*/
function MyClass() { }
>MyClass : Symbol(MyClass, Decl(bug27346.js, 0, 0), Decl(bug27346.js, 3, 22))
MyClass.prototype = {};
>MyClass.prototype : Symbol(MyClass.prototype, Decl(bug27346.js, 3, 22))
>MyClass : Symbol(MyClass, Decl(bug27346.js, 0, 0), Decl(bug27346.js, 3, 22))
>prototype : Symbol(MyClass.prototype, Decl(bug27346.js, 3, 22))

View File

@ -0,0 +1,14 @@
=== tests/cases/conformance/jsdoc/bug27346.js ===
/**
* @type {MyClass}
*/
function MyClass() { }
>MyClass : typeof MyClass
MyClass.prototype = {};
>MyClass.prototype = {} : {}
>MyClass.prototype : {}
>MyClass : typeof MyClass
>prototype : {}
>{} : {}

View File

@ -0,0 +1,9 @@
// @allowJs: true
// @noEmit: true
// @checkJs: true
// @Filename: bug27346.js
/**
* @type {MyClass}
*/
function MyClass() { }
MyClass.prototype = {};