From 49e395f95b1dbae38515e4cf5a4ab12177e4e165 Mon Sep 17 00:00:00 2001 From: Egor Bredikhin <32983915+Egor18@users.noreply.github.com> Date: Thu, 25 Jul 2019 01:25:36 -0400 Subject: [PATCH] fix: Fix missing position in rare cases (#3056) --- .../spoon/reflect/factory/ClassFactory.java | 3 +- .../reflect/factory/InterfaceFactory.java | 8 ++- .../support/compiler/jdt/ContextBuilder.java | 14 +++- .../spoon/test/position/PositionTest.java | 64 ++++++++++++++++++- .../lambdas/InheritedClassesWithLambda1.java | 14 ++++ .../lambdas/InheritedClassesWithLambda2.java | 15 +++++ .../InheritedInterfacesWithLambda.java | 12 ++++ 7 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda1.java create mode 100644 src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda2.java create mode 100644 src/test/resources/noclasspath/lambdas/InheritedInterfacesWithLambda.java diff --git a/src/main/java/spoon/reflect/factory/ClassFactory.java b/src/main/java/spoon/reflect/factory/ClassFactory.java index 303c8ea0c3f..6c4b4b6556d 100644 --- a/src/main/java/spoon/reflect/factory/ClassFactory.java +++ b/src/main/java/spoon/reflect/factory/ClassFactory.java @@ -59,8 +59,7 @@ public CtClass create(CtPackage owner, String simpleName) { * @param * type of created class * @param qualifiedName - * full name of class to create. Name can contain . or $ for - * inner types + * full name of class to create. Name can contain $ for inner types */ public CtClass create(String qualifiedName) { if (hasInnerType(qualifiedName) > 0) { diff --git a/src/main/java/spoon/reflect/factory/InterfaceFactory.java b/src/main/java/spoon/reflect/factory/InterfaceFactory.java index 7fe58c80139..a0203a30d38 100644 --- a/src/main/java/spoon/reflect/factory/InterfaceFactory.java +++ b/src/main/java/spoon/reflect/factory/InterfaceFactory.java @@ -45,7 +45,13 @@ public CtInterface create(CtType owner, String simpleName) { } /** - * Creates an interface. + * Creates an interface from its qualified name. + * + * @param + * type of created interface + * + * @param qualifiedName + * full name of interface to create. Name can contain $ for inner types */ @SuppressWarnings("unchecked") public CtInterface create(String qualifiedName) { diff --git a/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java b/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java index 0fedcba2d4b..b03d6ff23ab 100644 --- a/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java +++ b/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java @@ -204,6 +204,15 @@ CtVariable getVariableDeclaration(final String name) { return variable; } + /** + * Returns qualified name with appropriate package separator and inner type separator + */ + private static String getNormalQualifiedName(ReferenceBinding referenceBinding) { + String pkg = new String(referenceBinding.getPackage().readableName()).replaceAll("\\.", "\\" + CtPackage.PACKAGE_SEPARATOR); + String name = new String(referenceBinding.qualifiedSourceName()).replaceAll("\\.", "\\" + CtType.INNERTTYPE_SEPARATOR); + return pkg.equals("") ? name : pkg + "." + name; + } + @SuppressWarnings("unchecked") private > U getVariableDeclaration( final String name, final Class clazz) { @@ -250,11 +259,12 @@ private > U getVariableDeclaration( final ReferenceBinding referenceBinding = referenceBindings.pop(); for (final FieldBinding fieldBinding : referenceBinding.fields()) { if (name.equals(new String(fieldBinding.readableName()))) { - final String qualifiedNameOfParent = - new String(referenceBinding.readableName()); + final String qualifiedNameOfParent = getNormalQualifiedName(referenceBinding); + final CtType parentOfField = referenceBinding.isClass() ? classFactory.create(qualifiedNameOfParent) : interfaceFactory.create(qualifiedNameOfParent); + U field = (U) fieldFactory.create(parentOfField, EnumSet.noneOf(ModifierKind.class), referenceBuilder.getTypeReference(fieldBinding.type), diff --git a/src/test/java/spoon/test/position/PositionTest.java b/src/test/java/spoon/test/position/PositionTest.java index ddd1ab2fa69..5256cdd3a2f 100644 --- a/src/test/java/spoon/test/position/PositionTest.java +++ b/src/test/java/spoon/test/position/PositionTest.java @@ -19,6 +19,7 @@ import org.apache.commons.io.FileUtils; import org.junit.Test; import spoon.Launcher; +import spoon.reflect.CtModel; import spoon.reflect.code.CtAssignment; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtCase; @@ -31,6 +32,7 @@ import spoon.reflect.code.CtForEach; import spoon.reflect.code.CtIf; import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLambda; import spoon.reflect.code.CtLocalVariable; import spoon.reflect.code.CtNewClass; import spoon.reflect.code.CtReturn; @@ -49,6 +51,7 @@ import spoon.reflect.declaration.CtEnum; import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtImport; +import spoon.reflect.declaration.CtInterface; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtPackageDeclaration; import spoon.reflect.declaration.CtParameter; @@ -94,7 +97,6 @@ import java.io.IOException; import java.util.Iterator; import java.util.List; -import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1314,4 +1316,64 @@ public void testPositionBuilderFailureIsCaugth() { fail("Error while parsing incomplete class declaration"); } } + + @Test + public void testNoClasspathVariableAccessInInnerClass1() { + // contract: creating variable access in no classpath should not break source position + // https://github.com/INRIA/spoon/issues/3052 + Launcher launcher = new Launcher(); + launcher.addInputResource("./src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda1.java"); + launcher.getEnvironment().setNoClasspath(true); + CtModel model = launcher.buildModel(); + List allClasses = model.getElements(new TypeFilter<>(CtClass.class)); + assertEquals(3, allClasses.size()); + CtClass failing = allClasses.stream().filter(t -> t.getSimpleName().equals("Failing")).findFirst().get(); + assertEquals("InheritedClassesWithLambda1.java", failing.getPosition().getFile().getName()); + assertEquals(11, failing.getPosition().getLine()); + + // in addition check that the variable reference is correct + CtLambda lambda = model.getElements(new TypeFilter<>(CtLambda.class)).get(0); + CtFieldRead field = (CtFieldRead) (((CtInvocation) lambda.getExpression()).getTarget()); + assertEquals("com.pkg.InheritedClassesWithLambda1.Failing", field.getVariable().getDeclaringType().toString()); + } + + @Test + public void testNoClasspathVariableAccessInInnerClass2() { + // contract: same as for testNoClasspathVariableAccessInInnerClass1, + // but here we have inner class inside another inner class + Launcher launcher = new Launcher(); + launcher.addInputResource("./src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda2.java"); + launcher.getEnvironment().setNoClasspath(true); + CtModel model = launcher.buildModel(); + List allClasses = model.getElements(new TypeFilter<>(CtClass.class)); + assertEquals(4, allClasses.size()); + CtClass failing = allClasses.stream().filter(t -> t.getSimpleName().equals("Failing")).findFirst().get(); + assertEquals("InheritedClassesWithLambda2.java", failing.getPosition().getFile().getName()); + assertEquals(11, failing.getPosition().getLine()); + + // in addition check that the variable reference is correct + CtLambda lambda = model.getElements(new TypeFilter<>(CtLambda.class)).get(0); + CtFieldRead field = (CtFieldRead) (((CtInvocation) lambda.getExpression()).getTarget()); + assertEquals("InheritedClassesWithLambda2.OneMoreClass.Failing", field.getVariable().getDeclaringType().toString()); + } + + @Test + public void testNoClasspathVariableAccessInInnerInterface() { + // contract: same as for testNoClasspathVariableAccessInInnerClass1, + // but here we have interface instead of class + Launcher launcher = new Launcher(); + launcher.addInputResource("./src/test/resources/noclasspath/lambdas/InheritedInterfacesWithLambda.java"); + launcher.getEnvironment().setNoClasspath(true); + CtModel model = launcher.buildModel(); + List allInterfaces = model.getElements(new TypeFilter<>(CtInterface.class)); + assertEquals(1, allInterfaces.size()); + CtInterface failing = allInterfaces.stream().filter(t -> t.getSimpleName().equals("Failing")).findFirst().get(); + assertEquals("InheritedInterfacesWithLambda.java", failing.getPosition().getFile().getName()); + assertEquals(3, failing.getPosition().getLine()); + + // in addition check that the variable reference is correct + CtLambda lambda = model.getElements(new TypeFilter<>(CtLambda.class)).get(0); + CtFieldRead field = (CtFieldRead) (((CtInvocation) lambda.getExpression()).getTarget()); + assertEquals("InheritedInterfacesWithLambda.Failing", field.getVariable().getDeclaringType().toString()); + } } diff --git a/src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda1.java b/src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda1.java new file mode 100644 index 00000000000..1a0043baf16 --- /dev/null +++ b/src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda1.java @@ -0,0 +1,14 @@ +package com.pkg; + +public class InheritedClassesWithLambda1 { + + private static class ExtendedFailClass extends Failing { + public void test(View itemView) { + itemView.setOnClickListener(v -> listener.foo()); + } + } + + public static class Failing { + ClickListener listener; + } +} diff --git a/src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda2.java b/src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda2.java new file mode 100644 index 00000000000..3fb5cdffaff --- /dev/null +++ b/src/test/resources/noclasspath/lambdas/InheritedClassesWithLambda2.java @@ -0,0 +1,15 @@ +public class InheritedClassesWithLambda2 { + + private static class ExtendedFailClass extends OneMoreClass.Failing { + public void test(View itemView) { + itemView.setOnClickListener(v -> listener.foo()); + } + } + + public static class OneMoreClass + { + public static class Failing { + ClickListener listener; + } + } +} \ No newline at end of file diff --git a/src/test/resources/noclasspath/lambdas/InheritedInterfacesWithLambda.java b/src/test/resources/noclasspath/lambdas/InheritedInterfacesWithLambda.java new file mode 100644 index 00000000000..8b2e4066f25 --- /dev/null +++ b/src/test/resources/noclasspath/lambdas/InheritedInterfacesWithLambda.java @@ -0,0 +1,12 @@ +public class InheritedInterfacesWithLambda { + + public interface Failing { + ClickListener listener; + } + + private static class ExtendedFailClass implements Failing { + public void test(View itemView) { + itemView.setOnClickListener(v -> listener.foo()); + } + } +}