[java] Fix typehelper fallback for simple class name

If there is no auxclasspath, then we still can use imports to
check the full name before we fallback to simple name only.

Also improve RuleTst to actually test without auxclasspath
This commit is contained in:
Andreas Dangel
2020-05-22 16:17:36 +02:00
parent a0e2558ca2
commit 5a6cb7be8b
4 changed files with 87 additions and 19 deletions

View File

@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.java.typeresolution;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -16,6 +17,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTImplementsList; import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.ast.TypeNode;
import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration; import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader; import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
@ -85,6 +87,18 @@ public final class TypeHelper {
} }
private static boolean fallbackIsA(TypeNode n, String clazzName) { private static boolean fallbackIsA(TypeNode n, String clazzName) {
if (n.getImage() != null && !n.getImage().contains(".")) {
// simple name detected, check the imports to get the full name and use that for fallback
List<ASTImportDeclaration> imports = n.getRoot().findChildrenOfType(ASTImportDeclaration.class);
for (ASTImportDeclaration importDecl : imports) {
if (n.hasImageEqualTo(importDecl.getImportedSimpleName())) {
// found the import, compare the full names
return clazzName.equals(importDecl.getImportedName());
}
}
}
// fall back on using the simple name of the class only
if (clazzName.equals(n.getImage()) || clazzName.endsWith("." + n.getImage())) { if (clazzName.equals(n.getImage()) || clazzName.endsWith("." + n.getImage())) {
return true; return true;
} }

View File

@ -18,6 +18,8 @@ import org.junit.rules.ExpectedException;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.ast.TypeNode;
import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest; import net.sourceforge.pmd.lang.java.symboltable.BaseNonParserTest;
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader; import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
@ -78,6 +80,22 @@ public class TypeHelperTest extends BaseNonParserTest {
assertIsA(klass, Object.class); assertIsA(klass, Object.class);
} }
/**
* If we don't have the annotation on the classpath,
* we should resolve the full name via the import, if possible
* and compare then. Only after that, we should compare the
* simple names.
*/
@Test
public void testIsAFallbackAnnotationSimpleNameImport() {
ASTName annotation = java.parse("package org; import foo.Stuff; @Stuff public class FooBar {}")
.getFirstDescendantOfType(ASTMarkerAnnotation.class).getFirstChildOfType(ASTName.class);
Assert.assertNull(annotation.getType());
Assert.assertTrue(TypeHelper.isA(annotation, "foo.Stuff"));
Assert.assertFalse(TypeHelper.isA(annotation, "other.Stuff"));
}
private void assertIsA(TypeNode node, Class<?> type) { private void assertIsA(TypeNode node, Class<?> type) {
Assert.assertTrue("TypeHelper::isA with class arg: " + type.getCanonicalName(), Assert.assertTrue("TypeHelper::isA with class arg: " + type.getCanonicalName(),
TypeHelper.isA(node, type)); TypeHelper.isA(node, type));

View File

@ -150,41 +150,63 @@ public class MethodNamingConventions implements SomeInterface {
]]> ]]>
</code> </code>
</test-code> </test-code>
<test-code> <test-code>
<description>JUnit 4 test detection</description> <description>JUnit 4 test detection</description>
<rule-property name="junit4TestPattern">[a-z][A-Za-z]*Test</rule-property> <rule-property name="junit4TestPattern">[a-z][A-Za-z]*Test</rule-property>
<expected-problems>2</expected-problems> <expected-problems>2</expected-problems>
<expected-linenumbers>12,16</expected-linenumbers> <expected-linenumbers>8,12</expected-linenumbers>
<expected-messages> <expected-messages>
<message>The JUnit 4 test method name 'testGetBestTeam' doesn't match '[a-z][A-Za-z]*Test'</message> <message>The JUnit 4 test method name 'testGetBestTeam' doesn't match '[a-z][A-Za-z]*Test'</message>
<message>The JUnit 4 test method name 'getBestTeam' doesn't match '[a-z][A-Za-z]*Test'</message> <message>The JUnit 4 test method name 'getBestTeam' doesn't match '[a-z][A-Za-z]*Test'</message>
</expected-messages> </expected-messages>
<code><![CDATA[ <code><![CDATA[
import junit.framework.Assert; import junit.framework.TestCase;
import junit.framework.TestCase; import org.junit.Test;
import org.junit.Test; public class TournamentTest extends TestCase {
Tournament tournament;
@Test // note that this is also a junit 3 test, but the junit4 rule applies
public void testGetBestTeam() {
}
public class TournamentTest extends TestCase { @Test // this is just a junit 4 test
Tournament tournament; public void getBestTeam() {
}
// this is ok
@Test
public void getBestTeamTest() {
}
}
]]></code>
</test-code>
@Test // note that this is also a junit 3 test, but the junit4 rule applies <test-code useAuxClasspath="false">
public void testGetBestTeam() { <description>JUnit 4 test detection without proper auxclasspath</description>
} <expected-problems>1</expected-problems>
<expected-linenumbers>8</expected-linenumbers>
<expected-messages>
<message>The JUnit 4 test method name 'get_best_team' doesn't match '[a-z][a-zA-Z0-9]*'</message>
</expected-messages>
<code><![CDATA[
import org.junit.Test; // note: test case uses "useAuxClasspath=false"!!
@Test // this is just a junit 4 test public class TournamentTest {
public void getBestTeam() { Tournament tournament;
}
// this is ok // wrong test name pattern
@Test @Test
public void getBestTeamTest() { public void get_best_team() {
} }
}
]]> // this is ok
</code> @Test
public void getBestTeam() {
}
}
]]></code>
</test-code> </test-code>
<test-code> <test-code>

View File

@ -274,6 +274,20 @@ public abstract class RuleTst {
if (isUseAuxClasspath) { if (isUseAuxClasspath) {
// configure the "auxclasspath" option for unit testing // configure the "auxclasspath" option for unit testing
p.getConfiguration().prependClasspath("."); p.getConfiguration().prependClasspath(".");
} else {
// simple class loader, that doesn't delegate to parent.
// this allows us in the tests to simulate PMD run without
// auxclasspath, not even the classes from the test dependencies
// will be found.
p.getConfiguration().setClassLoader(new ClassLoader() {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.") || name.startsWith("javax.")) {
return super.loadClass(name, resolve);
}
throw new ClassNotFoundException(name);
}
});
} }
RuleContext ctx = new RuleContext(); RuleContext ctx = new RuleContext();
ctx.setReport(report); ctx.setReport(report);