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-printing of method imports #3744

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ protected void exit(CtElement e) {
*/
private ElementSourceFragment addChild(CtRole roleInParent, SourcePositionHolder otherElement) {
SourcePosition otherSourcePosition = otherElement.getPosition();
if (otherSourcePosition instanceof SourcePositionImpl && !(otherSourcePosition.getCompilationUnit() instanceof NoSourcePosition.NullCompilationUnit)) {
if (otherSourcePosition instanceof SourcePositionImpl && !(otherSourcePosition.getCompilationUnit() instanceof NoSourcePosition.NullCompilationUnit)
// method imports have child type references from other files, see https://github.com/INRIA/spoon/issues/3743
&& fromSameFile(element, otherElement)) {
ElementSourceFragment otherFragment = new ElementSourceFragment(otherElement, this.getRoleHandler(roleInParent, otherElement));
this.addChild(otherFragment);
return otherFragment;
Expand All @@ -245,6 +247,16 @@ private ElementSourceFragment addChild(CtRole roleInParent, SourcePositionHolder
return null;
}

private static boolean fromSameFile(SourcePositionHolder parent, SourcePositionHolder child) {
SourcePosition parentPos = parent.getPosition();
SourcePosition childPos = child.getPosition();

return parentPos.getFile().equals(childPos.getFile())
// we always consider the children of a compilation unit to be from the same file,
// as this is required e.g. to support sniper printing of renamed classes
|| parent instanceof CtCompilationUnit;
}

private RoleHandler getRoleHandler(CtRole roleInParent, SourcePositionHolder otherElement) {
SourcePositionHolder parent = element;
if (parent == null) {
Expand Down
53 changes: 53 additions & 0 deletions src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtImport;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
Expand Down Expand Up @@ -540,6 +541,58 @@ public void testDefaultsToSingleTabIndentationWhenThereAreNoTypeMembers() {
});
}

@Test
public void testPrintTypeWithMethodImportAboveMethodDefinition() {
// contract: The type references of a method import (e.g. its return type) has source
// positions in the file the method was imported from. The resolved source end position
// of the import should not be affected by the placement of the imported method. This
// test ensures this is the case even when the end position of the imported method is
// greater than the end position of the import statement.

Launcher launcher = createLauncherWithSniperPrinter();
launcher.addInputResource(getResourcePath("methodimport.ClassWithStaticMethod"));
launcher.addInputResource(getResourcePath("methodimport.MethodImportAboveImportedMethod"));

CtModel model = launcher.buildModel();
CtType<?> classWithStaticMethodImport = model.getAllTypes().stream()
.filter(type -> type.getSimpleName().endsWith("AboveImportedMethod"))
.findFirst()
.get();

List<CtImport> imports = classWithStaticMethodImport.getFactory().CompilationUnit().getOrCreate(classWithStaticMethodImport).getImports();

String output = launcher
.getEnvironment()
.createPrettyPrinter().printTypes(classWithStaticMethodImport);

assertThat(output, containsString("import static methodimport.ClassWithStaticMethod.staticMethod;"));
}

@Test
public void testPrintTypeWithMethodImportBelowMethodDefinition() {
// contract: The type references of a method import (e.g. its return type) has source
// positions in the file the method was imported from. The resolved source start position
// of the import should not be affected by the placement of the imported method. This
// test ensures this is the case even when the start position of the imported method is
// less than the start position of the import statement.

Launcher launcher = createLauncherWithSniperPrinter();
launcher.addInputResource(getResourcePath("methodimport.ClassWithStaticMethod"));
launcher.addInputResource(getResourcePath("methodimport.MethodImportBelowImportedMethod"));

CtModel model = launcher.buildModel();
CtType<?> classWithStaticMethodImport = model.getAllTypes().stream()
.filter(type -> type.getSimpleName().endsWith("BelowImportedMethod"))
.findFirst()
.get();

String output = launcher
.getEnvironment()
.createPrettyPrinter().printTypes(classWithStaticMethodImport);

assertThat(output, containsString("import static methodimport.ClassWithStaticMethod.staticMethod;"));
}

/**
* 1) Runs spoon using sniper mode,
* 2) runs `typeChanger` to modify the code,
Expand Down
10 changes: 10 additions & 0 deletions src/test/resources/methodimport/ClassWithStaticMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package methodimport;

public class ClassWithStaticMethod {
public static void main(String[] args) {
}

public static void staticMethod() {
System.out.println(42);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package methodimport;
import static methodimport.ClassWithStaticMethod.staticMethod;

/**
* This compilation unit has a method import of the method staticMethod, and the import has a
* lesser source position than the definition of staticMethod has in its respective file.
*/
public class MethodImportAboveImportedMethod {
public static void main(String[] args) {
staticMethod();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package methodimport;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collections;
import java.util.Set;

import static methodimport.ClassWithStaticMethod.staticMethod;

/**
* This compilation unit has a method import of the method staticMethod, and the import has a
* greater source position than the definition of staticMethod has in its respective file.
*/
public class MethodImportBelowImportedMethod {
public static void main(String[] args) {
staticMethod();
}
}