-
Notifications
You must be signed in to change notification settings - Fork 13k
fix54035, extractType now allows parts of union and intersection types to be extracted #56131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
if (isArray(selection)) { | ||
const newTypeValue = isUnionTypeNode(selection[0].parent) ? factory.createUnionTypeNode(selection) : factory.createIntersectionTypeNode(selection); | ||
const newTypeDeclaration = factory.createTypeAliasDeclaration( | ||
/*modifiers*/ undefined, | ||
name, | ||
typeParameters.map(id => factory.updateTypeParameterDeclaration(id, id.modifiers, id.name, id.constraint, /*defaultType*/ undefined)), | ||
newTypeValue, | ||
); | ||
changes.insertNodeBefore(file, enclosingNode, ignoreSourceNewlines(newTypeDeclaration), /*blankLineBetween*/ true); | ||
changes.replaceNodeRange(file, selection[0], selection[selection.length - 1], factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); | ||
} | ||
else { | ||
const newTypeNode = factory.createTypeAliasDeclaration( | ||
/*modifiers*/ undefined, | ||
name, | ||
typeParameters.map(id => factory.updateTypeParameterDeclaration(id, id.modifiers, id.name, id.constraint, /*defaultType*/ undefined)), | ||
selection, | ||
); | ||
changes.insertNodeBefore(file, enclosingNode, ignoreSourceNewlines(newTypeNode), /*blankLineBetween*/ true); | ||
changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: it feels like like a lot of this can probably be deduplicated between the array and non-array cases. Maybe instead of an if/else the newTypeValue
/newTypeNode
variable could incorporate an isArray(selection)
into its assignment. Then, you should be able to use changes.replaceNodeRange
in both cases (instead of changes.replaceNode
in the else
, specifying selection
as both the start and end of the range)
@typescript-bot pack this |
Hey @gabritto, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
What happens if the selection doesn't fully enclose the types in a union? type a = { a: string /*1*/} | { b: string }/*2*/ | { c: string }; and it becomes this: type NewType = {
a: string;
};
type a = NewType | { b: string } | { c: string }; and type a = { a: st/*1*/ring } | { b: string }/*2*/ | { c: string }; which becomes this: type NewType = string;
type a = { a: NewType } | { b: string } | { c: string }; Those surprised me a bit, but tbh I don't know how much we care about this or how much people are annoyed or not by this behavior. |
When @navya9singh implemented partial selection ranges for move to file, I think the logic was basically that the selection shrinks over whitespace and otherwise grows to encompass a valid set of nodes to move. It would be nice to have similar behavior between all selection-based refactors. So for example type a = { a: string }/*1*/ | { b: string } | /*2*/{ c: string }; // extract b
type a = { a: string /*1*/} | { b: string }/*2*/ | { c: string }; // extract a and b
type a = { a: st/*1*/ring } | { b: string }/*2*/ | { c: string }; // extract a and b
type a = { a: st/*1*/ring }/*2*/ | { b: string } | { c: string }; // extract a
type a = { a: st/*1*/ring /*2*/} | { b: string } | { c: string }; // extract string is probably what I’d expect |
The original behavior didn't check for a lot of these imperfect selection cases, and as a result there is some weird behavior. For example
are all things that happen with the original behavior.
I as well was surprised by this behavior, but I was also surprised that this partial type extraction bug wasn't noticed sooner. I think it would be really nice to have the behavior do be less finicky for the user and be more consistent across refactors, but again, not sure how much it's been noticed. |
We might want to check with Matt, since he opened the original issue, to get a sense of how much this partial selection behavior matters. |
After discussion with Matt today, and taking another look at the code that picks the nodes from the selection, adding small improvements such as
becoming
will require adjustments to the way I did the bug fix here. The existing code assumes the start of the selection is well placed, and my fix uses that assumption. |
The most recent change only addresses the behavior discussed above, and doesn't require such a big change like I had originally expected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left one small question, but otherwise looks good to me
const selectionRange = isArray(selection) ? { pos: selection[0].pos, end: selection[selection.length - 1].end } : selection; | ||
if (isArray(selection)) { | ||
selection.forEach(t => { | ||
if (!visitor(t)) return undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what's going on here: if visitor
returns true, that means we want to skip collection of type parameters and so we want collectTypeParameters
to return undefined
, right?
It seems to me we're not doing that anymore here when selection
is an array, since inside this if
we're always returning the result
array.
Fixes #54035