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

Don't modify package declaration of siblings in ChangeType #4184

Merged
merged 6 commits into from
May 12, 2024

Conversation

kennytv
Copy link
Contributor

@kennytv kennytv commented May 10, 2024

What's changed?

ChangeClassDefinition in ChangeType previously changed package declaration of sibling types, this PR restricts these to only the classes that should actually be changed

What's your motivation?

Closes #4182

Anything in particular you'd like reviewers to focus on?

I'm not exactly sure as to whether this is the proper way to do it (tracking required modification in JavaSourceFile visits), but at the very least this should show what the root cause is. And I suppose this breaks the immutability rule should be addressed now

Checklist

  • I've added unit tests to cover both positive and negative cases
  • I've read and applied the recipe conventions and best practices
  • I've used the IntelliJ IDEA auto-formatter on affected files

@timtebeek timtebeek marked this pull request as draft May 10, 2024 09:29
@timtebeek timtebeek added bug Something isn't working test provided Already replicated with a unit test, using JUnit pioneer's ExpectedToFail labels May 10, 2024
@timtebeek timtebeek self-requested a review May 10, 2024 09:30
Copy link
Contributor

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

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

Thanks a lot for getting this started. Helpful to know we might now inadvertently make changes, and a good start at getting that resolved. I've added a note about the use of cursor messaging over mutable fields; I hope that's clear enough. If not do let us know!

Copy link
Contributor

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

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

Looking good ; quick suggestion to rename the message key and variable, as path can mean the source file path too, which might be confusing.

Copy link
Contributor

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

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

Approved already; let me know how you feel about those suggestions and then we can get this merged.

Co-authored-by: Tim te Beek <timtebeek@gmail.com>
@kennytv
Copy link
Contributor Author

kennytv commented May 10, 2024

Should be all good 👍

@timtebeek
Copy link
Contributor

Thanks a lot for the quick turn around on this; really helpful to get this sorted. I'll merge as soon as the tests pass and from there it'll become available in our snapshot versions until the next release, typically in a week or two.

@timtebeek timtebeek marked this pull request as ready for review May 10, 2024 10:48
@timtebeek
Copy link
Contributor

Hmm; seems two other tests have started failing; This might need some work still. 🤔

@Issue("https://github.com/openrewrite/rewrite/issues/1904")
@Test
void onlyChangeTypeWithoutMatchedFilePath() {
rewriteRun(
spec -> spec.recipe(new ChangeType("a.b.Original", "x.y.Target", false)),
java(
"""
package a.b;
public class Original {
}
""",
"""
package x.y;
public class Target {
}
""",
spec -> spec.path("a/b/NoMatch.java").afterRecipe(cu -> {
assertThat(PathUtils.separatorsToUnix(cu.getSourcePath().toString())).isEqualTo("a/b/NoMatch.java");
assertThat(TypeUtils.isOfClassType(cu.getClasses().get(0).getType(), "x.y.Target")).isTrue();
})
)
);
}

@Issue("https://github.com/openrewrite/rewrite/issues/2528")
@Test
void changePathOfNonPublicClass() {
rewriteRun(
spec -> spec.recipe(new ChangeType("a.b.C", "x.y.Z", false)),
java(
"""
package a.b;
class C {
}
class D {
}
""",
"""
package x.y;
class Z {
}
class D {
}
""",
spec -> spec.path("x/y/Z.java")
)
);
}

@kennytv
Copy link
Contributor Author

kennytv commented May 10, 2024

Hmm that's strange, they run and pass locally, both individually through IntelliJ and the full module via gradlew :rewrite-java-test:test. I'm running Windows, if that matters

@timtebeek
Copy link
Contributor

I did see them fail for me locally; I've triggered the tests to run again to see if that shows any difference.

@kennytv
Copy link
Contributor Author

kennytv commented May 10, 2024

After switching machines I got the same lol, a fresh clone might have done it as well. In any case, I'm not sure I understand the spec -> spec.path("x/y/Z.java") lines. Docs say this marks the path after running the recipe, but I'm already getting x/y/Z.java as the source path in the initial ChangeClassDefinition visit - is that intended behavior? Changing the spec.path call to the origin path makes the test pass again

@timtebeek
Copy link
Contributor

Oh wow, I'd never noticed the Java docs on those methods; they seem to have been added deliberately, although I'd always taken those to mean the source paths before the recipe is run.

/**
* @param sourcePath The source path after the recipe is run.
* @return This source spec.
*/
public SourceSpec<T> path(Path sourcePath) {
this.sourcePath = sourcePath;
return this;
}
/**
* @param sourcePath The source path after the recipe is run.
* @return This source spec.
*/
public SourceSpec<T> path(String sourcePath) {
this.sourcePath = Paths.get(sourcePath);
return this;
}

We have other mechanisms to verify the source path is changed after a recipe run, which hints at the JavaDoc above being wrong.

spec
.path("a/b/hello.txt")
.afterRecipe(pt -> assertThat(pt.getSourcePath()).isEqualTo(Paths.get("a/b/goodbye.txt")));

So with that, the spec -> spec.path("a/b/NoMatch.java") and spec -> spec.path("x/y/Z.java") should set the paths on the input source files, not their moved equivalents.

The onlyChangeTypeWithoutMatchedFilePath test then verifies that if the filename does not match any of the contained classes, that we do not change the filename. I think that's valid and probably best to retain to avoid surprises. What do you think? The linked issue is this one

The changePathOfNonPublicClass test verifies that we do actually change the path, even when the class is not public, as defined in this linked issue:

With that I think it would be best if we don't change the behavior of any of the existing cases, and maybe even fix the confusing JavaDocs. What are your thoughts on the same?

@kennytv
Copy link
Contributor Author

kennytv commented May 11, 2024

That makes sense, thanks for the help! I pushed a commit here with how I'd go about fixing them, let me know if that should be a separate PR. The fix for the jd and the changePathOfNonPublicClass test are pretty straight forward, just for onlyChangeTypeWithoutMatchedFilePath I split the test for the non-matching class into its own call, so that the source path of the a.b.Original case stays correct

Though for the jd, I don't know the other uses, so let me know if "before" isn't correct either

@timtebeek
Copy link
Contributor

Thanks for the quick work; I had a slightly different idea come to mind after I'd already replied above; I should have updated my comment but was in the middle of other work.

Before any of your changes, we handled the cases where there's multiple classes in a single compilation unit (file) and changed their package and source path if the recipe indicates that's necessary. The NoMatch.java test highlighted a separate case where neither non-public class matched the filename, and where the filename should thus not change for that test (at least up to now).

The issue you've reported, and are tackling here is a different variation: there's again two classes in the same package, but in different compilation units (files). That's where the recipe up to now appears to have been making incorrect changes, based on the new test you added where the sibling in a different file was changed as well.

I think ideally we don't modify the tests we had before, and keep their behavior the same (unless we feel that's incorrect), and instead work out how to get the new test to pass by handling the above cases separately: sibling classes in the same compilation unit versus in different compilation units.

And note that I'm certainly not trying to complicate the work you're doing! It's much appreciated; we're both learning what the recipe did before, and how that relates to what we'd like it to do going forward, and hope we agree on the approach.

Source path and package declaration technically do not have to match, so this instead only looks for fully qualified class names
@kennytv
Copy link
Contributor Author

kennytv commented May 11, 2024

Ah I see now. Technically the provided and expected results aren't invalid Java code, but in practice you get misplaced class files that don't match their package declaration before compilation, which doesn't seem to be part of the linked issues (the provided class in changePathOfNonPublicClass, and the result in onlyChangeTypeWithoutMatchedFilePath are misplaced). So at least the path spec in changePathOfNonPublicClass looks wrong to me

An alternative to checking the source path for the sibling fix (as pushed now) would be to go through the classes in JavaSourceFile to check their fully qualified names, ignoring the actual source path. That should work and makes all tests pass, but I'm not sure of other implications

Copy link
Contributor

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

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

Thanks a lot for working through all these iterations. I think this now works as we'd like it to.

@timtebeek timtebeek merged commit 249c0a5 into openrewrite:main May 12, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working test provided Already replicated with a unit test, using JUnit pioneer's ExpectedToFail
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

ChangeType erroneously changes package of unrelated type
2 participants