Skip to content

Commit

Permalink
fix: Improve noclasspath handling of mixed (un)qualified references (#…
Browse files Browse the repository at this point in the history
…5981)

Co-authored-by: I-Al-Istannen <i-al-istannen@users.noreply.github.com>
  • Loading branch information
treenwang and I-Al-Istannen authored Oct 8, 2024
1 parent 2936e34 commit 344adc5
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 1 deletion.
18 changes: 17 additions & 1 deletion src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -1188,7 +1189,11 @@ private CtTypeReference<?> getTypeReferenceFromProblemReferenceBinding(ProblemRe

CtTypeReference<?> ref = this.jdtTreeBuilder.getFactory().Core().createTypeReference();
ref.setSimpleName(stripPackageName(readableName));
final CtReference declaring = this.getDeclaringReferenceFromImports(binding.sourceName());
CtReference declaring = this.getDeclaringReferenceFromImports(binding.sourceName());
Optional<String> packageName = getPackageName(readableName);
if (declaring == null && packageName.isPresent()) {
declaring = this.jdtTreeBuilder.getFactory().Package().createReference(packageName.get());
}
setPackageOrDeclaringType(ref, declaring);

return ref;
Expand All @@ -1207,6 +1212,17 @@ private static String stripPackageName(String fullyQualifiedName) {
return fullyQualifiedName.substring(s);
}

private static Optional<String> getPackageName(String fullyQualifiedName) {
if (!fullyQualifiedName.contains(".")) {
return Optional.empty();
}
String className = stripPackageName(fullyQualifiedName);
if (fullyQualifiedName.equals(className)) {
return Optional.empty();
}
return Optional.of(fullyQualifiedName.substring(0, fullyQualifiedName.length() - className.length()));
}

private CtTypeReference<?> getTypeReferenceFromIntersectionTypeBinding(IntersectionTypeBinding18 binding) {
List<CtTypeReference<?>> boundingTypes = new ArrayList<>();
for (ReferenceBinding superInterface : binding.getIntersectingTypes()) {
Expand Down
62 changes: 62 additions & 0 deletions src/test/java/spoon/test/api/APITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,18 @@
import spoon.template.TemplateParameter;
import spoon.test.api.processors.AwesomeProcessor;
import spoon.test.api.testclasses.Bar;
import spoon.testing.utils.GitHubIssue;


import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static spoon.testing.assertions.SpoonAssertions.assertThat;

public class APITest {

Expand Down Expand Up @@ -639,4 +643,62 @@ public void testProcessModelsTwice() {
}
}

@Test
public void testRespectPackageOfQualifiedUnknownClass() {
// contract: Classes appearing with qualified names in the source should be put in an appropriate package,
// even if we do not have them in our classpath
CtClass<?> theClass = Launcher.parseClass("""
package io.example.pack1.pack2;
public class Example {
void add(io.example.other.Class1<io.example.other.Class2> value){
}
}
""");
List<CtParameter<?>> addParameters = theClass.getMethodsByName("add").get(0).getParameters();
assertThat(addParameters).hasSize(1);

CtParameter<?> parameter = addParameters.get(0);
assertThat(parameter).getSimpleName().isEqualTo("value");

CtTypeReference<?> parameterType = parameter.getType();
assertThat(parameterType).getQualifiedName().isEqualTo("io.example.other.Class1");
assertThat(parameterType).getActualTypeArguments().hasSize(1);
assertThat(parameterType.getActualTypeArguments().get(0)).getQualifiedName()
.isEqualTo("io.example.other.Class2");
}

@Test
@GitHubIssue(issueNumber = 4783, fixed = true)
public void testRespectPackageOfQualifiedUnknownClassPreserveUnqualified() {
// contract: Classes appearing with qualified names in the source should be put in an appropriate package,
// even if we do not have them in our classpath. Unqualified classes should be re-homed, however.
CtClass<?> theClass = Launcher.parseClass("""
package io.example.pack1.pack2;
public class Example {
void add(io.example.other.Class1<io.example.other.Class2> value1, Class1<Class2> value2) {
}
}
""");
List<CtParameter<?>> addParameters = theClass.getMethodsByName("add").get(0).getParameters();
assertThat(addParameters).hasSize(2);

CtParameter<?> firstParameter = addParameters.get(0);
assertThat(firstParameter).getSimpleName().isEqualTo("value1");

CtParameter<?> secondParameter = addParameters.get(1);
assertThat(secondParameter).getSimpleName().isEqualTo("value2");

CtTypeReference<?> firstParameterType = firstParameter.getType();
assertThat(firstParameterType).getQualifiedName().isEqualTo("io.example.other.Class1");
assertThat(firstParameterType).getActualTypeArguments().hasSize(1);
assertThat(firstParameterType.getActualTypeArguments().get(0)).getQualifiedName()
.isEqualTo("io.example.other.Class2");

CtTypeReference<?> secondParameterType = secondParameter.getType();
assertThat(secondParameterType).getQualifiedName().isEqualTo("io.example.pack1.pack2.Class1");
assertThat(secondParameterType).getActualTypeArguments().hasSize(1);
assertThat(secondParameterType.getActualTypeArguments().get(0)).getQualifiedName()
.isEqualTo("io.example.pack1.pack2.Class2");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import java.util.Collection;
import org.assertj.core.api.AbstractCollectionAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.AbstractStringAssert;
import org.assertj.core.api.Assertions;
import spoon.reflect.declaration.CtTypeInformation;
import spoon.reflect.declaration.ModifierKind;
Expand All @@ -11,6 +12,10 @@ public interface CtTypeInformationAssertInterface<A extends AbstractObjectAssert
return Assertions.assertThat(actual().getModifiers());
}

default AbstractStringAssert<?> getQualifiedName() {
return Assertions.assertThat(actual().getQualifiedName());
}

default AbstractCollectionAssert<?, Collection<? extends CtTypeReference<?>>, CtTypeReference<?>, ?> getSuperInterfaces() {
return Assertions.assertThat(actual().getSuperInterfaces());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ private void createExtractingMethods(CtType<?> type, CtInterface<?> assertInterf
Metamodel instance = Metamodel.getInstance();
MetamodelConcept concept = instance.getConcept((Class<? extends CtElement>) type.getActualClass());
TreeSet<CtMethod<?>> methods = new TreeSet<>(Comparator.comparing(CtMethod::getSimpleName));
methods.addAll(assertInterface.getMethods());

for (var entry : concept.getRoleToProperty().entrySet()) {
List<CtMethod<?>> declaredMethods = entry.getValue().getMethod(MMMethodKind.GET).getDeclaredMethods();
CtMethod<?> method = declaredMethods.stream().reduce((l, r) -> l.getType().isSubtypeOf(r.getType()) ? l : r).orElseThrow();
Expand Down

0 comments on commit 344adc5

Please sign in to comment.