Fix 'as const'-like behavior in JSDoc type cast (#45464)
This commit is contained in:
parent
1dc1efbc4f
commit
107c556fe4
|
@ -8402,12 +8402,12 @@ namespace ts {
|
|||
}
|
||||
|
||||
function isNullOrUndefined(node: Expression) {
|
||||
const expr = skipParentheses(node);
|
||||
const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true);
|
||||
return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol;
|
||||
}
|
||||
|
||||
function isEmptyArrayLiteral(node: Expression) {
|
||||
const expr = skipParentheses(node);
|
||||
const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true);
|
||||
return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0;
|
||||
}
|
||||
|
||||
|
@ -22968,7 +22968,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function isFalseExpression(expr: Expression): boolean {
|
||||
const node = skipParentheses(expr);
|
||||
const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
|
||||
return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && (
|
||||
(node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node as BinaryExpression).left) || isFalseExpression((node as BinaryExpression).right)) ||
|
||||
(node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node as BinaryExpression).left) && isFalseExpression((node as BinaryExpression).right));
|
||||
|
@ -23290,7 +23290,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function narrowTypeByAssertion(type: Type, expr: Expression): Type {
|
||||
const node = skipParentheses(expr);
|
||||
const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
|
||||
if (node.kind === SyntaxKind.FalseKeyword) {
|
||||
return unreachableNeverType;
|
||||
}
|
||||
|
@ -25874,7 +25874,9 @@ namespace ts {
|
|||
case SyntaxKind.ParenthesizedExpression: {
|
||||
// Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast.
|
||||
const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined;
|
||||
return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent as ParenthesizedExpression, contextFlags);
|
||||
return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) :
|
||||
isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? tryFindWhenConstTypeReference(parent as ParenthesizedExpression) :
|
||||
getTypeFromTypeNode(tag.typeExpression.type);
|
||||
}
|
||||
case SyntaxKind.NonNullExpression:
|
||||
return getContextualType(parent as NonNullExpression, contextFlags);
|
||||
|
@ -32857,8 +32859,10 @@ namespace ts {
|
|||
}
|
||||
|
||||
function isTypeAssertion(node: Expression) {
|
||||
node = skipParentheses(node);
|
||||
return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression;
|
||||
node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true);
|
||||
return node.kind === SyntaxKind.TypeAssertionExpression ||
|
||||
node.kind === SyntaxKind.AsExpression ||
|
||||
isJSDocTypeAssertion(node);
|
||||
}
|
||||
|
||||
function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) {
|
||||
|
@ -32933,6 +32937,7 @@ namespace ts {
|
|||
function isConstContext(node: Expression): boolean {
|
||||
const parent = node.parent;
|
||||
return isAssertionExpression(parent) && isConstTypeReference(parent.type) ||
|
||||
isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) ||
|
||||
(isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) ||
|
||||
(isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent);
|
||||
}
|
||||
|
@ -33145,7 +33150,14 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getQuickTypeOfExpression(node: Expression) {
|
||||
const expr = skipParentheses(node);
|
||||
let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true);
|
||||
if (isJSDocTypeAssertion(expr)) {
|
||||
const type = getJSDocTypeAssertionType(expr);
|
||||
if (!isConstTypeReference(type)) {
|
||||
return getTypeFromTypeNode(type);
|
||||
}
|
||||
}
|
||||
expr = skipParentheses(node);
|
||||
// Optimize for the common case of a call to a function with a single non-generic call
|
||||
// signature where we can just fetch the return type without checking the arguments.
|
||||
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
|
||||
|
@ -33232,9 +33244,9 @@ namespace ts {
|
|||
}
|
||||
|
||||
function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type {
|
||||
const tag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined;
|
||||
if (tag) {
|
||||
return checkAssertionWorker(tag.typeExpression.type, tag.typeExpression.type, node.expression, checkMode);
|
||||
if (isJSDocTypeAssertion(node)) {
|
||||
const type = getJSDocTypeAssertionType(node);
|
||||
return checkAssertionWorker(type, type, node.expression, checkMode);
|
||||
}
|
||||
return checkExpression(node.expression, checkMode);
|
||||
}
|
||||
|
@ -36184,7 +36196,7 @@ namespace ts {
|
|||
if (getFalsyFlags(type)) return;
|
||||
|
||||
const location = isBinaryExpression(condExpr) ? condExpr.right : condExpr;
|
||||
if (isPropertyAccessExpression(location) && isAssertionExpression(skipParentheses(location.expression))) {
|
||||
if (isPropertyAccessExpression(location) && isTypeAssertion(location.expression)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -416,9 +416,24 @@ namespace ts {
|
|||
node.kind === SyntaxKind.CommaListExpression;
|
||||
}
|
||||
|
||||
export function isJSDocTypeAssertion(node: Node): node is JSDocTypeAssertion {
|
||||
return isParenthesizedExpression(node)
|
||||
&& isInJSFile(node)
|
||||
&& !!getJSDocTypeTag(node);
|
||||
}
|
||||
|
||||
export function getJSDocTypeAssertionType(node: JSDocTypeAssertion) {
|
||||
const type = getJSDocType(node);
|
||||
Debug.assertIsDefined(type);
|
||||
return type;
|
||||
}
|
||||
|
||||
export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
if (kinds & OuterExpressionKinds.ExcludeJSDocTypeAssertion && isJSDocTypeAssertion(node)) {
|
||||
return false;
|
||||
}
|
||||
return (kinds & OuterExpressionKinds.Parentheses) !== 0;
|
||||
case SyntaxKind.TypeAssertionExpression:
|
||||
case SyntaxKind.AsExpression:
|
||||
|
|
|
@ -2253,6 +2253,11 @@ namespace ts {
|
|||
readonly expression: Expression;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface JSDocTypeAssertion extends ParenthesizedExpression {
|
||||
readonly _jsDocTypeAssertionBrand: never;
|
||||
}
|
||||
|
||||
export interface ArrayLiteralExpression extends PrimaryExpression {
|
||||
readonly kind: SyntaxKind.ArrayLiteralExpression;
|
||||
readonly elements: NodeArray<Expression>;
|
||||
|
@ -6891,7 +6896,9 @@ namespace ts {
|
|||
PartiallyEmittedExpressions = 1 << 3,
|
||||
|
||||
Assertions = TypeAssertions | NonNullAssertions,
|
||||
All = Parentheses | Assertions | PartiallyEmittedExpressions
|
||||
All = Parentheses | Assertions | PartiallyEmittedExpressions,
|
||||
|
||||
ExcludeJSDocTypeAssertion = 1 << 4,
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
|
|
@ -2634,13 +2634,13 @@ namespace ts {
|
|||
let result: (JSDoc | JSDocTag)[] | undefined;
|
||||
// Pull parameter comments from declaring function as well
|
||||
if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) {
|
||||
result = append(result, last((hostNode.initializer as HasJSDoc).jsDoc!));
|
||||
result = addRange(result, filterOwnedJSDocTags(hostNode, last((hostNode.initializer as HasJSDoc).jsDoc!)));
|
||||
}
|
||||
|
||||
let node: Node | undefined = hostNode;
|
||||
while (node && node.parent) {
|
||||
if (hasJSDocNodes(node)) {
|
||||
result = append(result, last(node.jsDoc!));
|
||||
result = addRange(result, filterOwnedJSDocTags(hostNode, last(node.jsDoc!)));
|
||||
}
|
||||
|
||||
if (node.kind === SyntaxKind.Parameter) {
|
||||
|
@ -2656,6 +2656,26 @@ namespace ts {
|
|||
return result || emptyArray;
|
||||
}
|
||||
|
||||
function filterOwnedJSDocTags(hostNode: Node, jsDoc: JSDoc | JSDocTag) {
|
||||
if (isJSDoc(jsDoc)) {
|
||||
const ownedTags = filter(jsDoc.tags, tag => ownsJSDocTag(hostNode, tag));
|
||||
return jsDoc.tags === ownedTags ? [jsDoc] : ownedTags;
|
||||
}
|
||||
return ownsJSDocTag(hostNode, jsDoc) ? [jsDoc] : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a host node owns a jsDoc tag. A `@type` tag attached to a
|
||||
* a ParenthesizedExpression belongs only to the ParenthesizedExpression.
|
||||
*/
|
||||
function ownsJSDocTag(hostNode: Node, tag: JSDocTag) {
|
||||
return !isJSDocTypeTag(tag)
|
||||
|| !tag.parent
|
||||
|| !isJSDoc(tag.parent)
|
||||
|| !isParenthesizedExpression(tag.parent.parent)
|
||||
|| tag.parent.parent === hostNode;
|
||||
}
|
||||
|
||||
export function getNextJSDocCommentLocation(node: Node) {
|
||||
const parent = node.parent;
|
||||
if (parent.kind === SyntaxKind.PropertyAssignment ||
|
||||
|
@ -2899,10 +2919,13 @@ namespace ts {
|
|||
return [child, node];
|
||||
}
|
||||
|
||||
export function skipParentheses(node: Expression): Expression;
|
||||
export function skipParentheses(node: Node): Node;
|
||||
export function skipParentheses(node: Node): Node {
|
||||
return skipOuterExpressions(node, OuterExpressionKinds.Parentheses);
|
||||
export function skipParentheses(node: Expression, excludeJSDocTypeAssertions?: boolean): Expression;
|
||||
export function skipParentheses(node: Node, excludeJSDocTypeAssertions?: boolean): Node;
|
||||
export function skipParentheses(node: Node, excludeJSDocTypeAssertions?: boolean): Node {
|
||||
const flags = excludeJSDocTypeAssertions ?
|
||||
OuterExpressionKinds.Parentheses | OuterExpressionKinds.ExcludeJSDocTypeAssertion :
|
||||
OuterExpressionKinds.Parentheses;
|
||||
return skipOuterExpressions(node, flags);
|
||||
}
|
||||
|
||||
// a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped
|
||||
|
|
|
@ -3220,7 +3220,8 @@ declare namespace ts {
|
|||
NonNullAssertions = 4,
|
||||
PartiallyEmittedExpressions = 8,
|
||||
Assertions = 6,
|
||||
All = 15
|
||||
All = 15,
|
||||
ExcludeJSDocTypeAssertion = 16
|
||||
}
|
||||
export type TypeOfTag = "undefined" | "number" | "bigint" | "boolean" | "string" | "symbol" | "object" | "function";
|
||||
export interface NodeFactory {
|
||||
|
|
|
@ -3220,7 +3220,8 @@ declare namespace ts {
|
|||
NonNullAssertions = 4,
|
||||
PartiallyEmittedExpressions = 8,
|
||||
Assertions = 6,
|
||||
All = 15
|
||||
All = 15,
|
||||
ExcludeJSDocTypeAssertion = 16
|
||||
}
|
||||
export type TypeOfTag = "undefined" | "number" | "bigint" | "boolean" | "string" | "symbol" | "object" | "function";
|
||||
export interface NodeFactory {
|
||||
|
|
|
@ -123,4 +123,7 @@ tests/cases/conformance/jsdoc/b.js(67,8): error TS2454: Variable 'numOrStr' is u
|
|||
}
|
||||
|
||||
|
||||
|
||||
var asConst1 = /** @type {const} */(1);
|
||||
var asConst2 = /** @type {const} */({
|
||||
x: 1
|
||||
});
|
|
@ -74,7 +74,10 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
|
|||
}
|
||||
|
||||
|
||||
|
||||
var asConst1 = /** @type {const} */(1);
|
||||
var asConst2 = /** @type {const} */({
|
||||
x: 1
|
||||
});
|
||||
|
||||
//// [a.js]
|
||||
var W;
|
||||
|
@ -154,3 +157,7 @@ var str;
|
|||
if ( /** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
|
||||
str = numOrStr; // Error, no narrowing occurred
|
||||
}
|
||||
var asConst1 = /** @type {const} */ (1);
|
||||
var asConst2 = /** @type {const} */ ({
|
||||
x: 1
|
||||
});
|
||||
|
|
|
@ -157,4 +157,13 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
|
|||
}
|
||||
|
||||
|
||||
var asConst1 = /** @type {const} */(1);
|
||||
>asConst1 : Symbol(asConst1, Decl(b.js, 70, 3))
|
||||
|
||||
var asConst2 = /** @type {const} */({
|
||||
>asConst2 : Symbol(asConst2, Decl(b.js, 71, 3))
|
||||
|
||||
x: 1
|
||||
>x : Symbol(x, Decl(b.js, 71, 37))
|
||||
|
||||
});
|
||||
|
|
|
@ -209,4 +209,18 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
|
|||
}
|
||||
|
||||
|
||||
var asConst1 = /** @type {const} */(1);
|
||||
>asConst1 : 1
|
||||
>(1) : 1
|
||||
>1 : 1
|
||||
|
||||
var asConst2 = /** @type {const} */({
|
||||
>asConst2 : { readonly x: 1; }
|
||||
>({ x: 1}) : { readonly x: 1; }
|
||||
>{ x: 1} : { readonly x: 1; }
|
||||
|
||||
x: 1
|
||||
>x : 1
|
||||
>1 : 1
|
||||
|
||||
});
|
||||
|
|
|
@ -76,3 +76,7 @@ if(/** @type {numOrStr is string} */(numOrStr === undefined)) { // Error
|
|||
}
|
||||
|
||||
|
||||
var asConst1 = /** @type {const} */(1);
|
||||
var asConst2 = /** @type {const} */({
|
||||
x: 1
|
||||
});
|
Loading…
Reference in a new issue