Allow type predicates in JSDoc#26343
Conversation
1. Parse type predicates. Note that they are parsed everywhere, and get the appropriate error when used places besides a return type. 2. When creating a type predicate, correctly find the function's parameters starting from the jsdoc return type.
| function createTypePredicateFromTypePredicateNode(node: TypePredicateNode): IdentifierTypePredicate | ThisTypePredicate { | ||
| const { parameterName } = node; | ||
| const type = getTypeFromTypeNode(node.type); | ||
| const func = isJSDocTypeExpression(node.parent) ? getJSDocHost(node.parent) as FunctionLikeDeclaration : node.parent; |
There was a problem hiding this comment.
node.parent.kind === SyntaxKind.JSDocTypeExpression; is a compile error. (#25316)
There was a problem hiding this comment.
I think the correct fix is to change the type of TypePredicateNode.parent. Do you agree?
There was a problem hiding this comment.
👍
Also, the only reason the cast is valid is because the only place we call this function is getEffectiveReturnTypeNode(signature.declaration); -- could just pass signature.declaration in as a parameter func: SignatureDeclaration | JSDocSignature to avoid computing this.
Then getTypePredicateParameterIndex needs to be updated to function getTypePredicateParameterIndex(parameterList: ReadonlyArray<ParameterDeclaration | JSDocParameterTag>, parameter: Identifier) (and remove the if since parameters are non-optional).
There was a problem hiding this comment.
Previous comment may have been a little rambling so I'll rewrite. It is basically 3 comments.
In createTypePredicateFromTypePredicate you compute const func = ...etc....
createTypePredicateFromTypePredicate is only called in exactly one place, getTypePredicateOfSignature. In getTypePredicateOfSignature you already have access to signature.declaration. So you could add a second parameter to createTypeFromTypePredicateNode, func: SignatureDeclaration | JSDocSignature, rather than computing it inside the function.
I also had a somewhat unrelated comment to make: getTypePredicateParameterIndex can take defined inputs, so parameterList: NodeArray<ParameterDeclaration>, parameter: Identifier instead of parameterList: NodeArray<ParameterDeclaration> | undefined, parameter: Identifier | undefined. Then you can remove the if at the start of that function.
But, you'll note that once we are passing in func as a parameter without a cast, we've needed to allow JSDocSignature. That means that parameterList in getTypePredicateParameterIndex needs to be of type ReadonlyArray<ParameterDeclaration | JSDocParameterTag>, which just happens to work without changing any of the actual code in that function.
| const func = isJSDocTypeExpression(node.parent) ? getJSDocHost(node.parent) as FunctionLikeDeclaration : node.parent; | ||
| if (parameterName.kind === SyntaxKind.Identifier) { | ||
| return createIdentifierTypePredicate( | ||
| parameterName && parameterName.escapedText as string, // TODO: GH#18217 |
There was a problem hiding this comment.
We just accessed a property on parameterName, so it must be defined.
|
Failing test case: /**
* @callback Cb
* @param {unknown} x
* @return {x is number}
*/
/** @type {Cb} */
function isNumber(x) { return typeof x === "number" }
/** @param {unknown} x */
function g(x) {
if (isNumber(x)) {
x * 2;
}
} |
Also move createTypePredicateFromTypePredicateNode closer to its use
Previously, non-this type predicates almost worked. They just needed a couple of lines of code.
Fixes #26297