Fix ChangeType recipe's path resolution logic for substring and directory matches#6743
Fix ChangeType recipe's path resolution logic for substring and directory matches#6743KaushikV1017 wants to merge 4 commits intoopenrewrite:mainfrom
Conversation
|
Thanks for pointing out a limitation; not sure if I agree with making this recipe Java specific though, and we see a related failure: |
|
@timtebeek Thanks for the comment! I've revised the logic to make this apply for all file types, not just |
|
@timtebeek Let me know if there's any other changes you'd like me to make here. Thanks again. |
| spec -> spec.path(pathWithOriginalDir).afterRecipe(cu -> { | ||
| String path = PathUtils.separatorsToUnix(cu.getSourcePath().toString()); | ||
| assertThat(path).isEqualTo(pathWithOriginalDir); | ||
| assertThat(path).doesNotContain("NewName/Original.java"); | ||
| assertThat(TypeUtils.isOfClassType(cu.getClasses().getFirst().getType(), "com.example.NewName")).isTrue(); |
There was a problem hiding this comment.
I wonder if these are indeed the paths we want to be using here; the problem is mostly that the before path is not consistent with the package name; I wonder if we should unconditionally make it consistent, so not using replaceFirst, but just swapping into place the new target path.
| spec -> spec.path(pathWithOriginalDir).afterRecipe(cu -> { | |
| String path = PathUtils.separatorsToUnix(cu.getSourcePath().toString()); | |
| assertThat(path).isEqualTo(pathWithOriginalDir); | |
| assertThat(path).doesNotContain("NewName/Original.java"); | |
| assertThat(TypeUtils.isOfClassType(cu.getClasses().getFirst().getType(), "com.example.NewName")).isTrue(); | |
| spec -> spec.path("src/main/java/com/example/Original/Original.java").afterRecipe(cu -> { | |
| assertThat(PathUtils.separatorsToUnix(cu.getSourcePath().toString())).isEqualTo("src/main/java/com/example/NewName.java"); | |
| assertThat(TypeUtils.isOfClassType(cu.getClasses().getFirst().getType(), "com.example.NewName")).isTrue(); |
| int lastDot = oldPath.lastIndexOf('.'); | ||
| String extension = lastDot >= 0 ? oldPath.substring(lastDot) : ""; | ||
| String newPathStr = oldPath; | ||
| if (!extension.isEmpty() && oldPath.endsWith(oldFqn + extension)) { | ||
| newPathStr = oldPath.substring(0, oldPath.length() - (oldFqn + extension).length()) + newFqn + extension; | ||
| } | ||
| Path newPath = Paths.get(newPathStr); |
There was a problem hiding this comment.
Here's roughly what I meant with make the paths consistent: match liberally and replace.
| int lastDot = oldPath.lastIndexOf('.'); | |
| String extension = lastDot >= 0 ? oldPath.substring(lastDot) : ""; | |
| String newPathStr = oldPath; | |
| if (!extension.isEmpty() && oldPath.endsWith(oldFqn + extension)) { | |
| newPathStr = oldPath.substring(0, oldPath.length() - (oldFqn + extension).length()) + newFqn + extension; | |
| } | |
| Path newPath = Paths.get(newPathStr); | |
| String replaced = oldPath.replaceFirst( | |
| oldFqn + ".*?(\\..+)?$", | |
| newFqn + "$1"); | |
| Path newPath = Paths.get(replaced); |
What's changed?
rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java): Source path is updated only when it ends witholdFqn + file extension (.java, .groovy, etc). That suffix is replaced withnewFqn + file extension. In all other cases the path is left unchanged.ChangeTypeTest.java:doNotRenameFileWhenPathAlreadyHasNewTypeName()– path already has the new type name in the filename; path must not be altered again.doNotCorruptPathWhenDirectoryMatchesOldFqn()– path contains a directory that matchesoldFqn; only the filename suffix is considered for renaming, so the path is not corrupted.What's your motivation?
The
ChangeTyperecipe's logic for updating the source path has faults due to the use of thereplaceFirst()string method. Examples of where this comes up are highlighted below:A→ANewwith file patha/ANew.java(the file still has classAinside) was turning the path intoa/ANewNew.javabecausereplaceFirst("A", "ANew")matched the"A"in"ANew". See thedoNotRenameFileWhenPathAlreadyHasNewTypeName()test for an example.src/main/java/com/example/Original/Original.javaand changecom.example.Original→com.example.NewNamewas producing a path like.../NewName/Original.javaby replacing the directory segment; the type was correct but the file path was wrong. The path should stay as-is when the match is not the filename suffix. See thedoNotCorruptPathWhenDirectoryMatchesOldFqn()test for an example.Anything in particular you'd like reviewers to focus on?
sourcePathends witholdFqn + extension, replace that suffix withnewFqn + "extension.@Issue(I saw this being used in other tests, so wanted to see if it was needed for these new ones)Any additional context
oldFqn + extension(e.g.a/b/Original.java→x/y/Target.java); that case is unchanged and still covered by existing tests (e.g.renameClassAndFilePath).