Skip to content

Commit

Permalink
fix: Include static imports of nested types in AST (#5213)
Browse files Browse the repository at this point in the history
Co-authored-by: sonatype-lift[bot] <37194012+sonatype-lift[bot]@users.noreply.github.com>
Co-authored-by: I-Al-Istannen <[email protected]>
  • Loading branch information
3 people authored Jun 4, 2023
1 parent afa5f57 commit b645b94
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 17 deletions.
32 changes: 20 additions & 12 deletions src/main/java/spoon/support/compiler/jdt/JDTImportBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

Expand Down Expand Up @@ -83,28 +84,35 @@ void build() {
}
}
} else {
// A static import can be either a static field, a static method or a static type
// It is possible that this method will add duplicate imports
// Logically, if `foo` is a static method and `foo` is also a static field, then both should be
// imported with `import static example.Foo.foo;` repeated twice.
int lastDot = importName.lastIndexOf('.');
String className = importName.substring(0, lastDot);
String methodOrFieldName = importName.substring(lastDot + 1);
String methodOrFieldOrTypeName = importName.substring(lastDot + 1);

CtType klass = this.getOrLoadClass(className);
CtType<?> klass = this.getOrLoadClass(className);
if (klass != null) {
if ("*".equals(methodOrFieldName)) {
if (Objects.equals(methodOrFieldOrTypeName, "*")) {
this.imports.add(createImportWithPosition(factory.Type().createTypeMemberWildcardImportReference(klass.getReference()), importRef));
} else {
CtNamedElement methodOrField = null;
CtNamedElement methodOrFieldOrType;

methodOrField = klass.getField(methodOrFieldName);
methodOrFieldOrType = klass.getField(methodOrFieldOrTypeName);
if (methodOrFieldOrType != null) {
this.imports.add(createImportWithPosition(methodOrFieldOrType.getReference(), importRef));
}

if (methodOrField == null) {
List<CtMethod> methods = klass.getMethodsByName(methodOrFieldName);
if (methods.size() > 0) {
methodOrField = methods.get(0);
}
List<CtMethod<?>> methods = klass.getMethodsByName(methodOrFieldOrTypeName);
if (methods.size() > 0) {
methodOrFieldOrType = methods.get(0);
this.imports.add(createImportWithPosition(methodOrFieldOrType.getReference(), importRef));
}

if (methodOrField != null) {
this.imports.add(createImportWithPosition(methodOrField.getReference(), importRef));
methodOrFieldOrType = klass.getNestedType(methodOrFieldOrTypeName);
if (methodOrFieldOrType != null) {
this.imports.add(createImportWithPosition(methodOrFieldOrType.getReference(), importRef));
}
}
} else {
Expand Down
10 changes: 5 additions & 5 deletions src/test/java/spoon/reflect/visitor/ImportCleanerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import spoon.reflect.declaration.CtImport;
import spoon.reflect.declaration.CtType;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.hamcrest.CoreMatchers.equalTo;
Expand Down Expand Up @@ -68,19 +68,19 @@ private static void testImportCleanerDoesNotAlterImports(String source, String t
CtModel model = launcher.buildModel();
CtType<?> type = model.getUnnamedModule().getFactory().Type().get(targetClassQualname);
CtCompilationUnit cu = type.getFactory().CompilationUnit().getOrCreate(type);
List<String> importsBefore = getTextualImports(cu);
Set<String> importsBefore = getTextualImports(cu);

// act
new ImportCleaner().process(cu);

// assert
List<String> importsAfter = getTextualImports(cu);
Set<String> importsAfter = getTextualImports(cu);
assertThat(importsAfter, equalTo(importsBefore));
}

private static List<String> getTextualImports(CtCompilationUnit cu) {
private static Set<String> getTextualImports(CtCompilationUnit cu) {
return cu.getImports().stream()
.map(CtImport::toString)
.collect(Collectors.toList());
.collect(Collectors.toSet());
}
}
39 changes: 39 additions & 0 deletions src/test/java/spoon/test/imports/ImportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import spoon.test.imports.testclasses.Tacos;
import spoon.test.imports.testclasses.ToBeModified;
import spoon.test.imports.testclasses.badimportissue3320.source.TestSource;
import spoon.testing.utils.GitHubIssue;
import spoon.testing.utils.LineSeparatorExtension;
import spoon.testing.utils.ModelTest;
import spoon.testing.utils.ModelUtils;
Expand All @@ -99,6 +100,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.junit.jupiter.api.Assertions.*;
import static spoon.testing.utils.ModelUtils.canBeBuilt;
Expand Down Expand Up @@ -1861,4 +1863,41 @@ void testAutoimportConflictingSimpleNames() {
);
}

@GitHubIssue(issueNumber = 5210, fixed = true)
@ModelTest(value = {"src/test/resources/inner-class"}, complianceLevel = 11, autoImport = true)
void staticImports_ofNestedTypes_shouldBeRecorded(CtModel model) {
// contract: static imports of nested types should be recorded
// arrange
CtType<?> mainType = model.getElements(new TypeFilter<>(CtType.class)).stream()
.filter(t -> t.getSimpleName().equals("Main"))
.findFirst().orElseThrow();

// assert
List<CtImport> imports = mainType.getPosition().getCompilationUnit().getImports();
assertThat(imports, hasSize(2));

CtImport import0 = imports.get(0);
assertThat(import0.getReference().getSimpleName(), is("InnerClass"));
}

@ModelTest(value = {"src/test/resources/static-method-and-type"}, autoImport = true)
void staticTypeAndMethodImport_importShouldAppearOnlyOnceIfTheirSimpleNamesAreEqual(CtModel model) {
// contract: static type and method import should appear only once if their simple names are equal
// arrange
CtType<?> mainType = model.getElements(new TypeFilter<>(CtType.class)).stream()
.filter(t -> t.getSimpleName().equals("Main"))
.findFirst().orElseThrow();

// assert
List<CtImport> imports = mainType.getPosition().getCompilationUnit().getImports();
assertThat(imports, hasSize(2));

CtImport import0 = imports.get(0);
assertThat(import0.getImportKind(), is(CtImportKind.METHOD));
assertThat(import0.getReference().getSimpleName(), is("foo"));

CtImport import1 = imports.get(1);
assertThat(import1.getImportKind(), is(CtImportKind.TYPE));
assertThat(import1.getReference().getSimpleName(), is("foo"));
}
}
12 changes: 12 additions & 0 deletions src/test/resources/inner-class/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package inner_class;

import java.util.List;
import static inner_class.constants.Constants.InnerClass;

public class Main {
public void fun() {
List list = List.of(1, 2, 3);
InnerClass innerClass = new InnerClass();
innerClass.print();
}
}
11 changes: 11 additions & 0 deletions src/test/resources/inner-class/constants/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package inner_class.constants;

public class Constants {
public static int ZERO = 0;

public static class InnerClass {
public void print() {

}
}
}
12 changes: 12 additions & 0 deletions src/test/resources/static-method-and-type/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package static_method_and_type;

import static static_method_and_type.imports_are_here.Bar.foo;

public class Main {
public void fun() {
foo();
}

static class another_bar extends foo { }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package static_method_and_type.imports_are_here;

public class Bar {
public static void foo() {}
public static class foo {}
}

0 comments on commit b645b94

Please sign in to comment.