Skip to content
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

allow more cases to not block convert params to destructured object refactor #59140

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

iisaduan
Copy link
Member

@iisaduan iisaduan commented Jul 4, 2024

fixes #58930

^ was purposefully disabled in #30089 (comment):

This refactor changes a function's signature, so it affects the way a function should be called either directly or indirectly. The refactor also changes assignability to and from a function's type, since its type will change. Because of that, there are several situations where performing the refactor could cause a breaking change...

The current solution to this problem is to adopt a conservative approach: the refactor will perform changes only if those changes are guaranteed not to break the code...

Therefore, the refactor can fail to perform changes even if it was listed as available and the user tried to applied it. Currently there is no way to inform a user why a refactor has failed.

TODO: update description more.

@iisaduan iisaduan requested a review from gabritto July 4, 2024 02:51
@typescript-bot typescript-bot added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels Jul 4, 2024
@iisaduan iisaduan changed the title allow more cases to be restructured allow more cases to not block convert params to destructured object refactor Jul 4, 2024
Copy link
Member

@gabritto gabritto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left minor comments, but in general I think this looks ok to me. One thing I'm wondering, though, is that if we are willing to allow the refactor to be applied even when we know it will introduce errors, we shouldn't simply allow all cases (i.e. more than the ones in this PR), and rely on the fact that there will be type errors to alert the user to the places where the code needs to be manually updated.

if (entry.node.parent) {
const functionReference = entry.node;
const parent = functionReference.parent;
let callOrNewExpression: CallExpression | NewExpression | undefined;
let matchReference = functionReference;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we assign functionReference to matchReference inside the first case of the switch, to match the other two cases?

}

if (callOrNewExpression === undefined) return undefined;
if (callOrNewExpression.expression === matchReference) return callOrNewExpression;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move the comments on the switch cases to here? Something saying that if callOrNewExpression.expression === matchReference, then it means we have one of the cases of e.g. x.foo(...) or x["foo"](...) or foo(...) or super(...) or new Foo(...).


if (callOrNewExpression === undefined) return undefined;
if (callOrNewExpression.expression === matchReference) return callOrNewExpression;
if (isCallExpression(callOrNewExpression) && callOrNewExpression.arguments.some(arg => arg === matchReference)) return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comment above, could you add comments here saying that if this is true, we have a reference like map(x, foo) or x.map(foo), etc.?

@@ -355,47 +366,63 @@ function entryToImportOrExport(entry: FindAllReferences.NodeEntry): Node | undef
return undefined;
}

function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined {
function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | true | undefined {
// returns a callOrNewExpression if the reference needs to be updated during the refactor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think technically this returns a Node whose parent is a declaration.

Copy link
Member

@gabritto gabritto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought of a test case that's not currently covered immediately after approving 😅

function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | true | undefined {
// returns a callOrNewExpression if the reference needs to be updated during the refactor
// returns 'true' if the reference will not be updated by the refactor, but should not block the refactor
// returns 'undefined' if the reference should block the refactor
if (isDeclaration(entry.node.parent)) {
return entry.node;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this also needs to be modified to return true in some cases. For instance, the following test case that is similar to the method assignment one, results in the refactor failing to be applied:

function /*a*/foo/*b*/(actual: number) {
    return actual;
}
const x = foo;

That's because const x = foo is a declaration, so that foo reference will be added to the grouped reference's declarations, but it will later be considered invalid here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what I did, but I thought I had tested that case out and it wasn't blocked! I'll add it.

@iisaduan
Copy link
Member Author

iisaduan commented Jul 4, 2024

One thing I'm wondering, though, is that if we are willing to allow the refactor to be applied even when we know it will introduce errors, we shouldn't simply allow all cases (i.e. more than the ones in this PR), and rely on the fact that there will be type errors to alert the user to the places where the code needs to be manually updated.

I was thinking the same (hence why the draft)-- the original bug report provided a case where there was just one broken reference in a test case, but I can see how it could get bad fast if there are a lot of broken cases. And if we allow this refactor to break this case, why not others?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Milestone Bug PRs that fix a bug with a specific milestone
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Convert params to destructed object: Cannot apply refactoring
3 participants