From a4a5b3806e92ddced9f82b2c341e78bd91c041e4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 5 Oct 2018 15:41:09 -0700 Subject: [PATCH] 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. --- src/compiler/checker.ts | 18 +++++++++++++++--- src/compiler/diagnosticMessages.json | 6 +++++- src/compiler/types.ts | 1 + ...arReferenceOnConstructorFunction.errors.txt | 15 +++++++++++++++ ...cularReferenceOnConstructorFunction.symbols | 12 ++++++++++++ ...ircularReferenceOnConstructorFunction.types | 14 ++++++++++++++ ...agCircularReferenceOnConstructorFunction.ts | 9 +++++++++ 7 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt create mode 100644 tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.symbols create mode 100644 tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.types create mode 100644 tests/cases/conformance/jsdoc/typeTagCircularReferenceOnConstructorFunction.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ecd3312fa30..852bf39c16d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -666,6 +666,7 @@ namespace ts { ResolvedReturnType, ImmediateBaseConstraint, EnumTagType, + JSDocTypeReference, } const enum CheckMode { @@ -4503,6 +4504,8 @@ namespace ts { return !!(target).resolvedReturnType; case TypeSystemPropertyName.ImmediateBaseConstraint: return !!(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; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index ce1ccf17145..7baeeb7b65d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 } -} \ No newline at end of file +} diff --git a/src/compiler/types.ts b/src/compiler/types.ts index eb85d8c966a..c7a5979d20b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 diff --git a/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt new file mode 100644 index 00000000000..2469e647685 --- /dev/null +++ b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt @@ -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 = {}; + \ No newline at end of file diff --git a/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.symbols b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.symbols new file mode 100644 index 00000000000..bdbd4ee1b05 --- /dev/null +++ b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.symbols @@ -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)) + diff --git a/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.types b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.types new file mode 100644 index 00000000000..901fca39955 --- /dev/null +++ b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.types @@ -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 : {} +>{} : {} + diff --git a/tests/cases/conformance/jsdoc/typeTagCircularReferenceOnConstructorFunction.ts b/tests/cases/conformance/jsdoc/typeTagCircularReferenceOnConstructorFunction.ts new file mode 100644 index 00000000000..9d511ed4375 --- /dev/null +++ b/tests/cases/conformance/jsdoc/typeTagCircularReferenceOnConstructorFunction.ts @@ -0,0 +1,9 @@ +// @allowJs: true +// @noEmit: true +// @checkJs: true +// @Filename: bug27346.js +/** + * @type {MyClass} + */ +function MyClass() { } +MyClass.prototype = {};