add refactor of extract type#30562
Conversation
|
|
||
| function visitor(node: Node) { | ||
| if (isTypeReferenceNode(node)) { | ||
| if (isIdentifier(node.typeName)) { |
There was a problem hiding this comment.
why is it not necessary to handle the case where node.typeName is not an identifier but is a qualified name?
There was a problem hiding this comment.
Could TypeParameter be a QualifiedName?
In checkAndCollectionTypeArguments we only iter the TypeParameter whitch defined in the statement
There was a problem hiding this comment.
i think in the common cases where you have to collect the type, the type name is an identifier and not a qualified name. but i'm really not sure, so that's why i asked.
update: came up with an example of a qualified name for the type query check. i think there could be unusual cases where this check can fail.
| hasError = true; | ||
| return; | ||
| } | ||
| else if (isTypeQueryNode(node) && isIdentifier(node.exprName)) { |
There was a problem hiding this comment.
same as above, why is it not necessary to handle the case where node.typeName is not an identifier but is a qualified name?
There was a problem hiding this comment.
usually, I think that QualifiedName is not including in current statement
There was a problem hiding this comment.
And is not a TypeParameter and do not have TypeArguments
There was a problem hiding this comment.
like before, i think common cases are covered by the identifier check, but just came up with the following code:
interface I { a: string, f: (this: O, b: number) => typeof this.a };something bad happens if we extract typeof this.a
There was a problem hiding this comment.
Banned ThisTypeNode, QualityName and most left is ThisIdentifier
| } | ||
| } | ||
| else if (isInferTypeNode(node)) { | ||
| const conditionalTypeNode = findAncestor(node, isConditionalTypeNode); |
There was a problem hiding this comment.
i think this might be wrong. we are assuming that the infer type node is only allowed in the extends clause of a conditional type. so we want to check if selection includes the conditional type node to whose extends clause the infer type node belongs. this conditional type node might not be the first conditional type ancestor we find because there could be nested conditional type nodes. for example:
type Crazy<T> = T extends [infer P, (infer R extends string ? string : never)] ? P & R : string;we can apply the refactoring and extract (infer R extends string ? string : never) to obtain this:
type NewType = (infer R extends string ? string : never); // error: 'infer' declarations are only permitted in the 'extends' clause of a conditional type
type Crazy<T> = T extends [infer P, NewType] ? P & R : string;which gives an error.
There was a problem hiding this comment.
sorry, mis operation😅
There was a problem hiding this comment.
Find type node which is the first condition that contains selected infer type node in extends node
Co-Authored-By: Kingwl <kingwenlu@gmail.com>
Co-Authored-By: Kingwl <kingwenlu@gmail.com>
|
also this one: #29539 |
andrewbranch
left a comment
There was a problem hiding this comment.
Hey @Kingwl, thanks so much for this! It's looking great! 🌟
I thought of two small additional cases I'd like to see, something along these lines:
// typeof other parameters within function signature?
function f(a: string, b: /*a*/typeof a/*b*/): /*a*/typeof b/*b*/ {
return '';
}
// Where do lines get inserted?
// The exact structure here doesn't matter,
// just want to see something within a block body
// to have the behavior defined in tests.
function id<T>(x: T): T {
return (() => {
const s: /*a*/typeof x/*b*/ = x;
return s;
})();
}At a quick glance I think these will already produce the correct behavior, just want to make sure. Thanks!
|
🆙 |
|
⬆️ |
sandersn
left a comment
There was a problem hiding this comment.
Looks really good. The only thing is that this is incorrectly refactored.
I also had a couple of questions and two small suggestions, although they are optional.
| return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); | ||
| } | ||
|
|
||
| function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { |
There was a problem hiding this comment.
probably should be named collectTypeParameters ? I don't think you're really checking them, and it seems like they are intended to be type parameters in addition to arguments, so matching the type name TypeParameterDeclaration feels a little better.
| } | ||
|
|
||
| function isStatementButNotBlock(n: Node): n is Statement { | ||
| return n && isStatement(n) && !isBlock(n); |
There was a problem hiding this comment.
in what cases will you encounter a block before a statement in findAncestor? That is, a block inside a statement?
There was a problem hiding this comment.
Yes,
() => {
{
// here
}
}And i'm cannot remember sure why i do that🤷🏻♂️
Is this unnecessary?
There was a problem hiding this comment.
Weird, I would have that that //here would have its own statement, but I guess not. In that case, it seems necessary.
| } | ||
|
|
||
| function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { | ||
| let hasError = false; |
There was a problem hiding this comment.
seems more elegant to turn this into a return value. It looks like the expected behaviour is to return undefined whenever hasError is true, so stopping the tree walk early should be more efficient.
| } | ||
| } | ||
| else if (isInferTypeNode(node)) { | ||
| const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file)); |
There was a problem hiding this comment.
if the selection contains only a syntactically-correct infer type, then I think this code should (1) not set hasError but also (2) not push any results into result. What happens if you get an empty result list?
Test cases 31-33 show that this works, but I don't understand why.
There was a problem hiding this comment.
That caused:
- find the selections by largest and topmost type node
- Infer type node contains only a
TypeParameterDeclaration
in the following case:
type A = /*a*/Promise/*b*/The selection is the TypeReference which have the same range as the Identifier``Promise.
In cases 31 and 32 were set the hasError flag.
And the case33:
type Item<T> = T extends (infer /*a*/P/*b*/)[] ? P : neverThe selection is the TypeParameter(maybe, or an Identifier), that is a declaration rather a TypeNode. and we are already escaped before collectTypeParameters.
| } | ||
| } | ||
| else { | ||
| if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) { |
There was a problem hiding this comment.
you should skip this elsewhere too:
class C {
m<T>(): /*a*/T | this | number/*b*/ {
return {} as any
}
}currently produces type NewType<T> = T | this | number
|
@DanielRosenwasser you'll probably want to highlight this refactor in the release notes since it is the other half of the named-parameters refactor that @gabritto did. |
Fixes #23869
This pr implement a refactor to extract selected types into another type alias in ts or typedef jsdoc in js
follow things is WIP:
type name suggestion