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

fix: Fix Sniper printer failing to put added import statement on separate line #3702

Conversation

slarse
Copy link
Collaborator

@slarse slarse commented Nov 17, 2020

Fix #3698

This is ready for review, see comments further down :)

Comment on lines -401 to -410
SniperJavaPrettyPrinter printer = new SniperJavaPrettyPrinter(launcher.getEnvironment());
printer.setPreprocessors(Collections.unmodifiableList(Arrays.<Processor<CtElement>>asList(
//remove unused imports first. Do not add new imports at time when conflicts are not resolved
new ImportCleaner().setCanAddImports(false),
//solve conflicts, the current imports are relevant too
new ImportConflictDetector(),
//compute final imports
new ImportCleaner()
)));
return printer;
Copy link
Collaborator Author

@slarse slarse Nov 17, 2020

Choose a reason for hiding this comment

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

Removed as it interfered with my test case (removed unused imports), and no other test cases actually rely on this functionality. Very unclear why it's here.

I'm not satisfied with this solution, there should be a more general solution
@monperrus
Copy link
Collaborator

LGTM, unwip?

@slarse
Copy link
Collaborator Author

slarse commented Nov 17, 2020

LGTM, unwip?

I'm not quite satisfied with the solution, it's a corner case solution for import statements, but the problem is essentially an off-by-one error that might have a more general solution. The ones I've tried have unfortunately yielded unwanted whitespace in other places. It may be that this problem is specific to import statements, but I'm not sure. I'm hoping to find a better solution as side effect of working on #3701, in which I'm poking about the exact same parts of the sniper printer.

If I don't have a better solution in a couple of days, I'll just make a note of it in the source code and unwip and ping you :)

@slarse
Copy link
Collaborator Author

slarse commented Nov 18, 2020

@monperrus I thought this was not good enough a fix, and I was right: it doesn't fix the problem if you add an import statement to a project that has a package statement. The problem is an off-by-one error that seemingly only comes into play when adding stuff (any stuff) at the very top of the file.

Back to the drawing board, I need to actually fix the off-by-one error :)

@slarse
Copy link
Collaborator Author

slarse commented Nov 18, 2020

New test case shows the newline fail here

@slarse
Copy link
Collaborator Author

slarse commented Nov 19, 2020

@monperrus I've finally figured this out. The missing newline for an import following a package statement, and the missing newline between import statements, are really two separate problems. Anyway, here's the rundown.

Two import statements ending up on the same line

This problem is occurs when adding a new import statement, which is printed first in the file, and being immediately followed by a pre-existing import statement. As the pre-existing import statement has index 0 in the fragment child list, any newline events created by the default printer will never be printed.

The fix here is to always print newline events when printing the first pre-existing import statement. I think this might affect other child lists than import statements, but we can't unconditionally print newline events at the start of every child list as the default printer sometimes generates unwanted events (e.g. in if/else statements) that we definitely don't want. I think we will simply have to keep adding exceptions to this particular case as they pop up.

A newly added import statement ending up on the same line as the package declaration

This occurs when adding a new import statement that is printed after the package declaration. Previously, the visitCtPackageDeclaration printed its own newline. This is a big no-no for compatibility with the sniper printer, as if it decides that an element is unaltered, it will print its original source code without the newline. Therefore, in general, no single CtElement's visit method must include a newline for the visited element; it must be a completely standalone printer event. Of course, composite elements may still insert newlines between their constituent elements, but it should not terminate with a newline, or stuff like this happens. I think we will see this problem crop up again.

The fix was simply to not let visitCtPackageDeclaration() print a terminating newline, and instead print it in visitCtCompilationUnit(). Something similar may be the problem in #3697.

Also see some additional comments in the code.

Let me know if you want me to split this PR in two, it's technically two independent fixes with similar symptoms.

Comment on lines +1089 to +1092
CtPackage pkg = compilationUnit.getDeclaredPackage();
if (pkg != null && !pkg.isUnnamedPackage()) {
printer.writeln();
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the only difference here, the rest is just indentation. In combination with the fix in visitCtPackageDeclaration, this fixes newlines after package declarations in the sniper printer.

@@ -1111,7 +1117,7 @@ public void visitCtPackageDeclaration(CtPackageDeclaration packageDeclaration) {
CtPackageReference ctPackage = packageDeclaration.getReference();
elementPrinterHelper.writeComment(ctPackage, CommentOffset.BEFORE);
if (!ctPackage.isUnnamedPackage()) {
elementPrinterHelper.writePackageLine(ctPackage.getQualifiedName());
elementPrinterHelper.writePackageStatement(ctPackage.getQualifiedName());
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the "fix in visitCtPackageDeclaration" I mention.

Comment on lines +105 to +109
} else if (event.getRole() == CtRole.DECLARED_IMPORT && fragmentIndex == 0) {
// this is the first pre-existing import statement, and so we must print all newline
// events unconditionally to avoid placing it on the same line as a newly added import
// statement. See PR #3702 for details
printStandardSpaces();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the special handling of the first import statement. We may need to add more roles here as more problems crop up. Using only the condition fragmentIndex == 0 causes unwanted newlines in if/else statements, and perhaps elsewhere as well.

Comment on lines +351 to +364
/**
* Write a package statement and a newline.
*/
public void writePackageLine(String packageQualifiedName) {
writePackageStatement(packageQualifiedName);
printer.writeln();
}

/**
* Write a package statement.
*/
public void writePackageStatement(String packageQualifiedName) {
printer.writeKeyword("package").writeSpace();
writeQualifiedName(packageQualifiedName).writeSeparator(";").writeln();
writeQualifiedName(packageQualifiedName).writeSeparator(";");
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Here, I've added the method writePackageStatement, as I did not want to break the previous API by modifying writePackageLine.

@slarse slarse changed the title WIP: Fix Sniper printer failing to insert newline after new import statement review: Fix Sniper printer failing to insert newline after new import statement Nov 19, 2020
@slarse slarse changed the title review: Fix Sniper printer failing to insert newline after new import statement review: Fix Sniper printer failing to put added import statement on separate line Nov 19, 2020
@slarse slarse changed the title review: Fix Sniper printer failing to put added import statement on separate line review: fix: Fix Sniper printer failing to put added import statement on separate line Nov 19, 2020
@monperrus monperrus changed the title review: fix: Fix Sniper printer failing to put added import statement on separate line fix: Fix Sniper printer failing to put added import statement on separate line Nov 20, 2020
@monperrus monperrus merged commit f27aa68 into INRIA:master Nov 20, 2020
@monperrus
Copy link
Collaborator

Excellent work and explanations, thanks a lot @slarse

@slarse
Copy link
Collaborator Author

slarse commented Nov 20, 2020

You're welcome :). I learned a ton about the sniper printer doing this, so I think I'm ready to get back to tackling #3697 now.

Ps. it looks like merging my PR broke the build on master, but I'm pretty sure that wasn't my fault.

@monperrus
Copy link
Collaborator

monperrus commented Nov 23, 2020 via email

@slarse slarse deleted the issue/3698-fix-missing-newline-after-new-import-sniper branch May 28, 2021 10:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

bug: Sniper fails to add newline when adding new import statement
2 participants