From 72c8b804da98ea64376ad3981f0ad167d4d19934 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 22 Sep 2017 10:42:02 -0700 Subject: [PATCH] Use JSDoc `@type` tag type cast as a contextual type (#18690) * Use JSDoc `@type` tag type cast as a contextual type * Suggested changes --- src/compiler/checker.ts | 17 ++++++++--------- src/compiler/utilities.ts | 9 +++++++-- .../fourslash/completionsJsdocTypeTagCast.ts | 7 +++++++ 3 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 tests/cases/fourslash/completionsJsdocTypeTagCast.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9f326aec7d..5c835582da 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13305,8 +13305,11 @@ namespace ts { case SyntaxKind.TemplateSpan: Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); return getContextualTypeForSubstitutionExpression(parent.parent, node); - case SyntaxKind.ParenthesizedExpression: - return getContextualType(parent); + case SyntaxKind.ParenthesizedExpression: { + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const tag = isInJavaScriptFile(parent) ? getJSDocTypeTag(parent) : undefined; + return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent); + } case SyntaxKind.JsxExpression: return getContextualTypeForJsxExpression(parent); case SyntaxKind.JsxAttribute: @@ -18127,13 +18130,9 @@ namespace ts { } function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - if (isInJavaScriptFile(node) && node.jsDoc) { - const typecasts = flatMap(node.jsDoc, doc => filter(doc.tags, tag => tag.kind === SyntaxKind.JSDocTypeTag && !!(tag as JSDocTypeTag).typeExpression && !!(tag as JSDocTypeTag).typeExpression.type)); - if (typecasts && typecasts.length) { - // We should have already issued an error if there were multiple type jsdocs - const cast = typecasts[0] as JSDocTypeTag; - return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode); - } + const tag = isInJavaScriptFile(node) ? getJSDocTypeTag(node) : undefined; + if (tag) { + return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode); } return checkExpression(node.expression, checkMode); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ede4c5a45e..674215b058 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4091,9 +4091,14 @@ namespace ts { return getFirstJSDocTag(node, SyntaxKind.JSDocTemplateTag) as JSDocTemplateTag; } - /** Gets the JSDoc type tag for the node if present */ + /** Gets the JSDoc type tag for the node if present and valid */ export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined { - return getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag; + // We should have already issued an error if there were multiple type jsdocs, so just use the first one. + const tag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag; + if (tag && tag.typeExpression && tag.typeExpression.type) { + return tag; + } + return undefined; } /** diff --git a/tests/cases/fourslash/completionsJsdocTypeTagCast.ts b/tests/cases/fourslash/completionsJsdocTypeTagCast.ts new file mode 100644 index 0000000000..822069feb5 --- /dev/null +++ b/tests/cases/fourslash/completionsJsdocTypeTagCast.ts @@ -0,0 +1,7 @@ +/// + +// @allowJs: true +// @Filename: /a.js +////const x = /** @type {{ s: string }} */ ({ /**/ }); + +verify.completionsAt("", ["s", "x"]);