From e1aef1e4035702df2d386894aac09f0764eca9cd Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 7 Aug 2018 16:09:03 -0700 Subject: [PATCH 1/3] Don't store @template constraint in a TypeParameterDeclaration node --- src/compiler/checker.ts | 34 +++++++++++++++---- src/compiler/parser.ts | 7 ++-- src/compiler/types.ts | 2 ++ src/compiler/utilities.ts | 2 ++ .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../reference/jsdocTemplateTag3.errors.txt | 5 ++- .../cases/fourslash/editTemplateConstraint.ts | 10 ++++++ tests/cases/fourslash/quickInfoTemplateTag.ts | 21 ++++++++++++ 9 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 tests/cases/fourslash/editTemplateConstraint.ts create mode 100644 tests/cases/fourslash/quickInfoTemplateTag.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 71fd9ca42e774..721cd1770ae43 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5948,7 +5948,8 @@ namespace ts { /** A type parameter is thisless if its contraint is thisless, or if it has no constraint. */ function isThislessTypeParameter(node: TypeParameterDeclaration) { - return !node.constraint || isThislessType(node.constraint); + const constraint = getEffectiveConstraintOfTypeParameter(node); + return !constraint || isThislessType(constraint); } /** @@ -6735,7 +6736,7 @@ namespace ts { } function getConstraintDeclarationForMappedType(type: MappedType) { - return type.declaration.typeParameter.constraint; + return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); } function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { @@ -7872,7 +7873,7 @@ namespace ts { function getConstraintDeclaration(type: TypeParameter) { const decl = type.symbol && getDeclarationOfKind(type.symbol, SyntaxKind.TypeParameter); - return decl && decl.constraint; + return decl && getEffectiveConstraintOfTypeParameter(decl); } function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { @@ -7936,7 +7937,9 @@ namespace ts { } function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { - return getSymbolOfNode(getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!.parent); + const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; + const host = isJSDocTemplateTag(tp.parent) ? getHostSignatureFromJSDoc(tp.parent) : tp.parent; + return host && getSymbolOfNode(host); } function getTypeListId(types: ReadonlyArray | undefined) { @@ -22016,7 +22019,7 @@ namespace ts { checkSourceElement(node.default); const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); if (!hasNonCircularBaseConstraint(typeParameter)) { - error(node.constraint, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter)); + error(getEffectiveConstraintOfTypeParameter(node), Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter)); } if (!hasNonCircularTypeParameterDefault(typeParameter)) { error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); @@ -22749,7 +22752,7 @@ namespace ts { const type = getTypeFromMappedTypeNode(node); const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, keyofConstraintType, node.typeParameter.constraint); + checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); } function checkThisType(node: ThisTypeNode) { @@ -23640,6 +23643,13 @@ namespace ts { checkSourceElement(node.typeExpression); } + function checkJSDocTemplateTag(node: JSDocTemplateTag): void { + checkSourceElement(node.constraint); + for (const tp of node.typeParameters) { + checkSourceElement(tp); + } + } + function checkJSDocTypeTag(node: JSDocTypeTag) { checkSourceElement(node.typeExpression); } @@ -25427,7 +25437,8 @@ namespace ts { // If the type parameter node does not have an identical constraint as the resolved // type parameter at this position, we report an error. - const sourceConstraint = source.constraint && getTypeFromTypeNode(source.constraint); + const constraint = getEffectiveConstraintOfTypeParameter(source); + const sourceConstraint = constraint && getTypeFromTypeNode(constraint); const targetConstraint = getConstraintOfTypeParameter(target); if (sourceConstraint) { // relax check if later interface augmentation has no constraint @@ -26647,6 +26658,8 @@ namespace ts { case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: return checkJSDocTypeAliasTag(node as JSDocTypedefTag); + case SyntaxKind.JSDocTemplateTag: + return checkJSDocTemplateTag(node as JSDocTemplateTag); case SyntaxKind.JSDocTypeTag: return checkJSDocTypeTag(node as JSDocTypeTag); case SyntaxKind.JSDocParameterTag: @@ -29763,6 +29776,13 @@ namespace ts { } return false; } + + function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined { + return node.constraint ? node.constraint + : isJSDocTemplateTag(node.parent) && node === node.parent.typeParameters[0] + ? node.parent.constraint + : undefined; + } } /** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 570d50f848532..0f2c43e75ed06 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -475,7 +475,7 @@ namespace ts { case SyntaxKind.JSDocAugmentsTag: return visitNode(cbNode, (node).class); case SyntaxKind.JSDocTemplateTag: - return visitNodes(cbNode, cbNodes, (node).typeParameters); + return visitNode(cbNode, (node).constraint) || visitNodes(cbNode, cbNodes, (node).typeParameters); case SyntaxKind.JSDocTypedefTag: if ((node as JSDocTypedefTag).typeExpression && (node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression) { @@ -7049,13 +7049,10 @@ namespace ts { typeParameters.push(typeParameter); } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); - if (constraint) { - first(typeParameters).constraint = constraint.type; - } - const result = createNode(SyntaxKind.JSDocTemplateTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; + result.constraint = constraint; result.typeParameters = createNodeArray(typeParameters, typeParametersPos); finishNode(result); return result; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 06867a94c17cb..6f2d3fe8569ba 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -759,6 +759,7 @@ namespace ts { kind: SyntaxKind.TypeParameter; parent: DeclarationWithTypeParameterChildren | InferTypeNode; name: Identifier; + // Note: Consider calling `getEffectiveConstraintOfTypeParameter` constraint?: TypeNode; default?: TypeNode; @@ -2363,6 +2364,7 @@ namespace ts { export interface JSDocTemplateTag extends JSDocTag { kind: SyntaxKind.JSDocTemplateTag; + constraint: TypeNode | undefined; typeParameters: NodeArray; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b285a9878486e..daf585fcbcafa 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1016,6 +1016,8 @@ namespace ts { return !isExpressionWithTypeArgumentsInClassExtendsClause(parent); case SyntaxKind.TypeParameter: return node === (parent).constraint; + case SyntaxKind.JSDocTemplateTag: + return node === (parent).constraint; case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.Parameter: diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 339e2394ce440..879c0e0ad27ce 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1568,6 +1568,7 @@ declare namespace ts { } interface JSDocTemplateTag extends JSDocTag { kind: SyntaxKind.JSDocTemplateTag; + constraint: TypeNode | undefined; typeParameters: NodeArray; } interface JSDocReturnTag extends JSDocTag { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 92152c7ca87b3..d9e91cee5d96e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1568,6 +1568,7 @@ declare namespace ts { } interface JSDocTemplateTag extends JSDocTag { kind: SyntaxKind.JSDocTemplateTag; + constraint: TypeNode | undefined; typeParameters: NodeArray; } interface JSDocReturnTag extends JSDocTag { diff --git a/tests/baselines/reference/jsdocTemplateTag3.errors.txt b/tests/baselines/reference/jsdocTemplateTag3.errors.txt index a85024e1499d1..19b8231158ddf 100644 --- a/tests/baselines/reference/jsdocTemplateTag3.errors.txt +++ b/tests/baselines/reference/jsdocTemplateTag3.errors.txt @@ -2,10 +2,11 @@ tests/cases/conformance/jsdoc/a.js(14,29): error TS2339: Property 'a' does not e tests/cases/conformance/jsdoc/a.js(14,35): error TS2339: Property 'b' does not exist on type 'U'. tests/cases/conformance/jsdoc/a.js(21,3): error TS2345: Argument of type '{ a: number; }' is not assignable to parameter of type '{ a: number; b: string; }'. Property 'b' is missing in type '{ a: number; }'. +tests/cases/conformance/jsdoc/a.js(24,15): error TS2304: Cannot find name 'NoLongerAllowed'. tests/cases/conformance/jsdoc/a.js(25,2): error TS1069: Unexpected token. A type parameter name was expected without curly braces. -==== tests/cases/conformance/jsdoc/a.js (4 errors) ==== +==== tests/cases/conformance/jsdoc/a.js (5 errors) ==== /** * @template {{ a: number, b: string }} T,U A Comment * @template {{ c: boolean }} V uh ... are comments even supported?? @@ -37,6 +38,8 @@ tests/cases/conformance/jsdoc/a.js(25,2): error TS1069: Unexpected token. A type /** * @template {NoLongerAllowed} + ~~~~~~~~~~~~~~~ +!!! error TS2304: Cannot find name 'NoLongerAllowed'. * @template T preceding line's syntax is no longer allowed ~ !!! error TS1069: Unexpected token. A type parameter name was expected without curly braces. diff --git a/tests/cases/fourslash/editTemplateConstraint.ts b/tests/cases/fourslash/editTemplateConstraint.ts new file mode 100644 index 0000000000000..2aa4fb079b58f --- /dev/null +++ b/tests/cases/fourslash/editTemplateConstraint.ts @@ -0,0 +1,10 @@ +/// + +/////** +//// * @template {/**/ +//// */ +////function f() {} + +goTo.marker(""); +edit.insert("n"); +edit.insert("u"); diff --git a/tests/cases/fourslash/quickInfoTemplateTag.ts b/tests/cases/fourslash/quickInfoTemplateTag.ts new file mode 100644 index 0000000000000..9d611823fadfa --- /dev/null +++ b/tests/cases/fourslash/quickInfoTemplateTag.ts @@ -0,0 +1,21 @@ +/// + +// @allowJs: true +// @checkJs: true +// @Filename: /foo.js + +/////** +//// * Doc +//// * @template {new (...args: any[]) => any} T +//// * @param {T} cls +//// */ +////function /**/myMixin(cls) { +//// return class extends cls {} +////} + +verify.quickInfoAt("", +`function myMixin any>(cls: T): { + new (...args: any[]): (Anonymous class); + prototype: myMixin.(Anonymous class); +} & T`, +"Doc"); From dfbc181d024cbab27660b43d0a2248d73f49fcae Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 9 Aug 2018 12:13:36 -0700 Subject: [PATCH 2/3] Code review --- src/compiler/checker.ts | 7 ------- src/compiler/types.ts | 2 +- src/compiler/utilities.ts | 7 +++++++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 721cd1770ae43..17402776c6bd7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29776,13 +29776,6 @@ namespace ts { } return false; } - - function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined { - return node.constraint ? node.constraint - : isJSDocTemplateTag(node.parent) && node === node.parent.typeParameters[0] - ? node.parent.constraint - : undefined; - } } /** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6f2d3fe8569ba..1be243c947b14 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -759,7 +759,7 @@ namespace ts { kind: SyntaxKind.TypeParameter; parent: DeclarationWithTypeParameterChildren | InferTypeNode; name: Identifier; - // Note: Consider calling `getEffectiveConstraintOfTypeParameter` + /** Note: Consider calling `getEffectiveConstraintOfTypeParameter` */ constraint?: TypeNode; default?: TypeNode; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index daf585fcbcafa..32bf33e2b578b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5106,6 +5106,13 @@ namespace ts { } return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : emptyArray); } + + export function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined { + return node.constraint ? node.constraint + : isJSDocTemplateTag(node.parent) && node === node.parent.typeParameters[0] + ? node.parent.constraint + : undefined; + } } // Simple node tests of the form `node.kind === SyntaxKind.Foo`. From e6e3705193b0df7f4589e67a79d0ba7bfcb45702 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 9 Aug 2018 15:37:19 -0700 Subject: [PATCH 3/3] Update API --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 ++ tests/baselines/reference/api/typescript.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 879c0e0ad27ce..c08e8bf9b5cd9 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -548,6 +548,7 @@ declare namespace ts { kind: SyntaxKind.TypeParameter; parent: DeclarationWithTypeParameterChildren | InferTypeNode; name: Identifier; + /** Note: Consider calling `getEffectiveConstraintOfTypeParameter` */ constraint?: TypeNode; default?: TypeNode; expression?: Expression; @@ -3268,6 +3269,7 @@ declare namespace ts { * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray; + function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined; } declare namespace ts { function isNumericLiteral(node: Node): node is NumericLiteral; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d9e91cee5d96e..7d00fa8aff849 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -548,6 +548,7 @@ declare namespace ts { kind: SyntaxKind.TypeParameter; parent: DeclarationWithTypeParameterChildren | InferTypeNode; name: Identifier; + /** Note: Consider calling `getEffectiveConstraintOfTypeParameter` */ constraint?: TypeNode; default?: TypeNode; expression?: Expression; @@ -3268,6 +3269,7 @@ declare namespace ts { * JavaScript file, gets the type parameters from the `@template` tag from JSDoc. */ function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray; + function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined; } declare namespace ts { function isNumericLiteral(node: Node): node is NumericLiteral;