Skip to content

Commit 72c8b80

Browse files
author
Andy
authored
Use JSDoc @type tag type cast as a contextual type (#18690)
* Use JSDoc `@type` tag type cast as a contextual type * Suggested changes
1 parent a4b5870 commit 72c8b80

File tree

3 files changed

+22
-11
lines changed

3 files changed

+22
-11
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13305,8 +13305,11 @@ namespace ts {
1330513305
case SyntaxKind.TemplateSpan:
1330613306
Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression);
1330713307
return getContextualTypeForSubstitutionExpression(<TemplateExpression>parent.parent, node);
13308-
case SyntaxKind.ParenthesizedExpression:
13309-
return getContextualType(<ParenthesizedExpression>parent);
13308+
case SyntaxKind.ParenthesizedExpression: {
13309+
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
13310+
const tag = isInJavaScriptFile(parent) ? getJSDocTypeTag(parent) : undefined;
13311+
return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(<ParenthesizedExpression>parent);
13312+
}
1331013313
case SyntaxKind.JsxExpression:
1331113314
return getContextualTypeForJsxExpression(<JsxExpression>parent);
1331213315
case SyntaxKind.JsxAttribute:
@@ -18127,13 +18130,9 @@ namespace ts {
1812718130
}
1812818131

1812918132
function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
18130-
if (isInJavaScriptFile(node) && node.jsDoc) {
18131-
const typecasts = flatMap(node.jsDoc, doc => filter(doc.tags, tag => tag.kind === SyntaxKind.JSDocTypeTag && !!(tag as JSDocTypeTag).typeExpression && !!(tag as JSDocTypeTag).typeExpression.type));
18132-
if (typecasts && typecasts.length) {
18133-
// We should have already issued an error if there were multiple type jsdocs
18134-
const cast = typecasts[0] as JSDocTypeTag;
18135-
return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode);
18136-
}
18133+
const tag = isInJavaScriptFile(node) ? getJSDocTypeTag(node) : undefined;
18134+
if (tag) {
18135+
return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode);
1813718136
}
1813818137
return checkExpression(node.expression, checkMode);
1813918138
}

src/compiler/utilities.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4091,9 +4091,14 @@ namespace ts {
40914091
return getFirstJSDocTag(node, SyntaxKind.JSDocTemplateTag) as JSDocTemplateTag;
40924092
}
40934093

4094-
/** Gets the JSDoc type tag for the node if present */
4094+
/** Gets the JSDoc type tag for the node if present and valid */
40954095
export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined {
4096-
return getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
4096+
// We should have already issued an error if there were multiple type jsdocs, so just use the first one.
4097+
const tag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
4098+
if (tag && tag.typeExpression && tag.typeExpression.type) {
4099+
return tag;
4100+
}
4101+
return undefined;
40974102
}
40984103

40994104
/**
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @allowJs: true
4+
// @Filename: /a.js
5+
////const x = /** @type {{ s: string }} */ ({ /**/ });
6+
7+
verify.completionsAt("", ["s", "x"]);

0 commit comments

Comments
 (0)