review: fix: Ensure CU-level elements are included when renaming a top-level type #6188
+19
−14
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
fix #5086
This PR modifies the
Refactoring.changeTypeName
logic to prevent loss of information from the original class.The current logic effectively creates a new compilation unit to house the renamed type. However, the type by itself only contains the source code elements for the class. All other
SourceFragment
s outside of the class block, such as imports, comments, and whitespace, are lost in this conversion. The most apparent issue is thatSniperJavaPrettyPrinter
does not have the source context to properly print the full class file after a rename.This can easily be illustrated with the existing
TestSniperPrinter#testClassRename1
test (which currently does not have assertions for these external elements):Original input:
Current result of
TestSniperPrinter#testClassRename1
:The change proposed here simply reuses the original compilation unit object to ensure top-level elements (and their source context) are retained. This comes with two consequences of note:
Query
for type references is not sufficient since the reference in question is a child of the compilation unit (not the package) and therefore isn't captured with the default scope.CtCompilationUnit#getMainType
to be more lenient with the top-level type. I believe it's safe to assume that a single declared type should always be considered the main type, especially given the rarity (and discouraged practice) of having multiple top-level types in a single file. We could further refine this logic for robustness (possibly adding a check on the type visibility) but this may be getting into some remote edge cases that even the existing logic doesn't cover.Thanks in advance for the review! Don't hesitate to @ me if there are other considerations to discuss.
Other solutions explored
Copying elements into the new compilation unit
This method involved manually copying the imports and comments into the compilation unit created by
CompilationUnitFactory#addType
in the current logic. It's also important to set the parent of these elements to avoid consistency errors downstream.However, this devolved into directly manipulating the
SourceFragment
s of the compilation unit in an attempt to recreate whitespace around the new elements. This proved futile as (naturally) we can only add instances ofElementSourceFragment
as children ofCtCompilationUnit#getOriginalSourceFragment
, and wrapping whitespace as an element felt hacky at best.Setting the file to the same source as the original compilation unit helped to regenerate the source context, but at that point we've seemingly defeated the purpose of copying the compilation unit in the first place.
Copying the source file along with the compilation unit
Another possibility is to copy the source file along with the compilation unit. The big question here is where to copy the source file to on the filesystem. We cannot copy the file to
Environment#getSourceOutputDirectory
prematurely because the contents will be cleared onJDTBasedSpoonCompiler#generateProcessedSourceFilesUsingCUs
(unless this logic is refined, perhaps by creating the file if it doesn't exist to satisfy the comments herespoon/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java
Lines 567 to 569 in 028a123
Environment#getBinaryOutputDirectory
might be a better candidate, but it's unclear whether a Mavengenerated-sources
-type approach is appropriate for its intended use case.A more creative solution could involve a virtual/in-memory file, although we are limited to instances of
java.io.File
whichVirtualFile
and the like does not implement. Something like Apache Commons VFS may prove useful, but this would require a new dependency as well as extensive refactoring without a provider that adheres tojava.io.File
.Exposing new capabilities from
CompilationUnitFactory
If the issue is that
CompilationUnitFactory
only acceptsCtType
for building a new compilation unit, perhaps another solution would be to add methods that include CU-level context. Unfortunately, this doesn't solve many of the other challenges discussed here but does provide the benefit of modularizing the solution for use cases outside of renaming a top-level type. This also gives us access to thecachedCompilationUnits
internals to ensure consistency if needed.