/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint;
import static com.android.tools.lint.EcjParser.equalsCompound;
import static com.android.tools.lint.EcjParser.startsWithCompound;
import static com.android.tools.lint.client.api.JavaParser.ResolvedClass;
import static com.android.tools.lint.client.api.JavaParser.ResolvedField;
import static com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
import static com.android.tools.lint.client.api.JavaParser.ResolvedVariable;
import com.android.annotations.NonNull;
import com.android.tools.lint.checks.AbstractCheckTest;
import com.android.tools.lint.checks.SdCardDetector;
import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.LintUtilsTest;
import com.android.tools.lint.detector.api.Project;
import com.google.common.collect.Lists;
import org.intellij.lang.annotations.Language;
import org.junit.Assert;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.ast.AnnotationElement;
import lombok.ast.BinaryExpression;
import lombok.ast.Block;
import lombok.ast.ClassDeclaration;
import lombok.ast.DescribedNode;
import lombok.ast.ExpressionStatement;
import lombok.ast.ForwardingAstVisitor;
import lombok.ast.Identifier;
import lombok.ast.KeywordModifier;
import lombok.ast.MethodInvocation;
import lombok.ast.Modifiers;
import lombok.ast.Node;
import lombok.ast.NormalTypeBody;
import lombok.ast.Select;
import lombok.ast.TypeReferencePart;
import lombok.ast.VariableDeclaration;
import lombok.ast.VariableDefinition;
import lombok.ast.VariableReference;
import lombok.ast.printer.SourceFormatter;
import lombok.ast.printer.SourcePrinter;
import lombok.ast.printer.TextFormatter;
public class EcjParserTest extends AbstractCheckTest {
public void testTryCatchHang() throws Exception {
// Ensure that we're really using this parser
JavaParser javaParser = createClient().getJavaParser(null);
assertNotNull(javaParser);
assertTrue(javaParser.getClass().getName(), javaParser instanceof EcjParser);
// See https://code.google.com/p/projectlombok/issues/detail?id=573#c6
// With lombok.ast 0.2.1 and the parboiled-based Java parser this test will hang forever.
assertEquals(
"No warnings.",
lintProject("src/test/pkg/TryCatchHang.java.txt=>src/test/pkg/TryCatchHang.java"));
}
public void testKitKatLanguageFeatures() throws Exception {
String testClass = "" +
"package test.pkg;\n" +
"\n" +
"import java.io.BufferedReader;\n" +
"import java.io.FileReader;\n" +
"import java.io.IOException;\n" +
"import java.lang.reflect.InvocationTargetException;\n" +
"import java.util.List;\n" +
"import java.util.Map;\n" +
"import java.util.TreeMap;\n" +
"\n" +
"public class Java7LanguageFeatureTest {\n" +
" public void testDiamondOperator() {\n" +
" Map<String, List<Integer>> map = new TreeMap<>();\n" +
" }\n" +
"\n" +
" public int testStringSwitches(String value) {\n" +
" final String first = \"first\";\n" +
" final String second = \"second\";\n" +
"\n" +
" switch (value) {\n" +
" case first:\n" +
" return 41;\n" +
" case second:\n" +
" return 42;\n" +
" default:\n" +
" return 0;\n" +
" }\n" +
" }\n" +
"\n" +
" public String testTryWithResources(String path) throws IOException {\n" +
" try (BufferedReader br = new BufferedReader(new FileReader(path))) {\n" +
" return br.readLine();\n" +
" }\n" +
" }\n" +
"\n" +
" public void testNumericLiterals() {\n" +
" int thousand = 1_000;\n" +
" int million = 1_000_000;\n" +
" int binary = 0B01010101;\n" +
" }\n" +
"\n" +
" public void testMultiCatch() {\n" +
"\n" +
" try {\n" +
" Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n" +
" } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {\n" +
" e.printStackTrace();\n" +
" } catch (ClassNotFoundException e) {\n" +
" // TODO: Logging here\n" +
" }\n" +
" }\n" +
"}\n";
Node unit = LintUtilsTest.getCompilationUnit(testClass);
assertNotNull(unit);
// Now print the AST back and make sure that it contains at least the essence of the AST
TextFormatter formatter = new TextFormatter();
unit.accept(new SourcePrinter(formatter));
String actual = formatter.finish();
assertEquals(""
+ "package test.pkg;\n"
+ "\n"
+ "import java.io.BufferedReader;\n"
+ "import java.io.FileReader;\n"
+ "import java.io.IOException;\n"
+ "import java.lang.reflect.InvocationTargetException;\n"
+ "import java.util.List;\n"
+ "import java.util.Map;\n"
+ "import java.util.TreeMap;\n"
+ "\n"
+ "public class Java7LanguageFeatureTest {\n"
+ " public void testDiamondOperator() {\n"
+ " Map<String, List<Integer>> map = new TreeMap();\n" // missing types on rhs
+ " }\n"
+ " \n"
+ " public int testStringSwitches(String value) {\n"
+ " final String first = \"first\";\n"
+ " final String second = \"second\";\n"
+ " switch (value) {\n"
+ " case first:\n"
+ " return 41;\n"
+ " case second:\n"
+ " return 42;\n"
+ " default:\n"
+ " return 0;\n"
+ " }\n"
+ " }\n"
+ " \n"
+ " public String testTryWithResources(String path) throws IOException {\n"
+ " try {\n" // Note how the initialization clause is gone here
+ " return br.readLine();\n"
+ " }\n"
+ " }\n"
+ " \n"
+ " public void testNumericLiterals() {\n"
+ " int thousand = 1_000;\n"
+ " int million = 1_000_000;\n"
+ " int binary = 0B01010101;\n"
+ " }\n"
+ " \n"
+ " public void testMultiCatch() {\n"
+ " try {\n"
+ " Class.forName(\"java.lang.Integer\").getMethod(\"toString\").invoke(null);\n"
+ " } catch (IllegalAccessException e) {\n" // Note: missing other union types
+ " e.printStackTrace();\n"
+ " } catch (ClassNotFoundException e) {\n"
+ " }\n"
+ " }\n"
+ "}",
actual);
}
@SuppressWarnings("ClassNameDiffersFromFileName")
public void testGetFields() throws Exception {
@Language("JAVA")
String source = ""
+ "public class FieldTest {\n"
+ " public int field1 = 1;\n"
+ " public int field2 = 3;\n"
+ " public int field3 = 5;\n"
+ " \n"
+ " public static class Inner extends FieldTest {\n"
+ " public int field2 = 5;\n"
+ " }\n"
+ "}\n";
final JavaContext context = LintUtilsTest.parse(source,
new File("src/test/pkg/FieldTest.java"));
assertNotNull(context);
Node compilationUnit = context.getCompilationUnit();
assertNotNull(compilationUnit);
final AtomicBoolean found = new AtomicBoolean();
compilationUnit.accept(new ForwardingAstVisitor() {
@Override
public boolean visitClassDeclaration(ClassDeclaration node) {
if (node.astName().astValue().equals("Inner")) {
found.set(true);
ResolvedNode resolved = context.resolve(node);
assertNotNull(resolved);
ResolvedClass cls = (ResolvedClass) resolved;
List<ResolvedField> declaredFields = Lists.newArrayList(cls.getFields(false));
assertEquals(1, declaredFields.size());
assertEquals("field2", declaredFields.get(0).getName());
declaredFields = Lists.newArrayList(cls.getFields(true));
assertEquals(3, declaredFields.size());
assertEquals("field2", declaredFields.get(0).getName());
assertEquals("FieldTest.Inner", declaredFields.get(0).getContainingClassName());
assertEquals("field1", declaredFields.get(1).getName());
assertEquals("FieldTest", declaredFields.get(1).getContainingClassName());
assertEquals("field3", declaredFields.get(2).getName());
assertEquals("FieldTest", declaredFields.get(2).getContainingClassName());
}
return super.visitClassDeclaration(node);
}
});
assertTrue(found.get());
}
public void testResolution() throws Exception {
String source =
"package test.pkg;\n" +
"\n" +
"import java.io.File;\n" +
"\n" +
"public class TypeResolutionTest {\n" +
" public static class Inner extends File {\n" +
" public float myField = 5f;\n" +
" public int[] myInts;\n" +
"\n" +
" public Inner(File dir, String name) {\n" +
" super(dir, name);\n" +
" }\n" +
"\n" +
" public void call(int arg1, double arg2) {\n" +
" boolean x = super.canRead();\n" +
" System.out.println(x);\n" +
" }\n" +
" }\n" +
"\n" +
" @SuppressWarnings(\"all\")\n" +
" public static class Other {\n" +
" private void client(int z) {\n" +
" int x = z;\n" +
" int y = x + 5;\n" +
" Inner inner = new Inner(null, null);\n" +
" inner.myField = 6;\n" +
" System.out.println(inner.myInts);\n" +
" }\n" +
" }\n" +
"}\n";
Node unit = LintUtilsTest.getCompilationUnit(source,
new File("src/test/pkg/TypeResolutionTest.java"));
// Visit all nodes and assert nativeNode != null unless I expect it!
unit.accept(new ForwardingAstVisitor() {
@SuppressWarnings("Contract")
@Override
public boolean visitNode(Node node) {
if (node.getNativeNode() == null && requiresNativeNode(node)) {
fail("Expected native node on node of type " +
node.getClass().getSimpleName());
}
return super.visitNode(node);
}
private boolean requiresNativeNode(Node node) {
if (node instanceof TypeReferencePart &&
node.getParent().getNativeNode() != null) {
return false;
}
if (node instanceof Identifier
|| node instanceof NormalTypeBody
|| node instanceof Block
|| node instanceof VariableDeclaration
|| node instanceof VariableDefinition
|| node instanceof AnnotationElement
|| node instanceof BinaryExpression
|| node instanceof Modifiers
|| node instanceof KeywordModifier) {
return false;
}
if (node instanceof VariableReference) {
VariableReference reference = (VariableReference)node;
if (reference.getParent() instanceof Select) {
return false;
}
} else if (node instanceof MethodInvocation) {
Node parent = node.getParent();
if (parent instanceof ExpressionStatement &&
parent.getNativeNode() != null) {
return false;
}
}
return true;
}
});
JavaParser parser = new EcjParser(new LintCliClient(), null);
AstPrettyPrinter astPrettyPrinter = new AstPrettyPrinter(parser);
unit.accept(new SourcePrinter(astPrettyPrinter));
String actual = astPrettyPrinter.finish();
assertEquals(
"[CompilationUnit]\n" +
" [PackageDeclaration]\n" +
" [Identifier test]\n" +
" PROPERTY: name = test\n" +
" [Identifier pkg]\n" +
" PROPERTY: name = pkg\n" +
" [ImportDeclaration]\n" +
" PROPERTY: static = false\n" +
" PROPERTY: star = false\n" +
" [Identifier java]\n" +
" PROPERTY: name = java\n" +
" [Identifier io]\n" +
" PROPERTY: name = io\n" +
" [Identifier File]\n" +
" PROPERTY: name = File\n" +
" [ClassDeclaration TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
" [Modifiers], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
" [KeywordModifier public]\n" +
" PROPERTY: modifier = public\n" +
" typeName: [Identifier TypeResolutionTest], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
" PROPERTY: name = TypeResolutionTest\n" +
" [NormalTypeBody], type: test.pkg.TypeResolutionTest, resolved class: test.pkg.TypeResolutionTest \n" +
" [ClassDeclaration Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" [Modifiers], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" [KeywordModifier public]\n" +
" PROPERTY: modifier = public\n" +
" [KeywordModifier static]\n" +
" PROPERTY: modifier = static\n" +
" typeName: [Identifier Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" PROPERTY: name = Inner\n" +
" extends: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
" [Identifier File]\n" +
" PROPERTY: name = File\n" +
" [NormalTypeBody], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" [VariableDeclaration], type: float, resolved class: float \n" +
" [VariableDefinition]\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" [KeywordModifier public]\n" +
" PROPERTY: modifier = public\n" +
" type: [TypeReference float], type: float, resolved class: float \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: float, resolved class: float \n" +
" [Identifier float]\n" +
" PROPERTY: name = float\n" +
" [VariableDefinitionEntry], resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier myField], resolved field: myField test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: name = myField\n" +
" [FloatingPointLiteral 5.0], type: float\n" +
" PROPERTY: value = 5f\n" +
" [VariableDeclaration], type: int[], resolved class: int[] \n" +
" [VariableDefinition]\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" [KeywordModifier public]\n" +
" PROPERTY: modifier = public\n" +
" type: [TypeReference int[]], type: int[], resolved class: int[] \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 1\n" +
" [TypeReferencePart], type: int[], resolved class: int[] \n" +
" [Identifier int]\n" +
" PROPERTY: name = int\n" +
" [VariableDefinitionEntry], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier myInts], resolved field: myInts test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: name = myInts\n" +
" [ConstructorDeclaration], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
" [Modifiers], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
" [KeywordModifier public]\n" +
" PROPERTY: modifier = public\n" +
" typeName: [Identifier Inner], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: name = Inner\n" +
" parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference File], type: java.io.File, resolved class: java.io.File \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: java.io.File, resolved class: java.io.File \n" +
" [Identifier File]\n" +
" PROPERTY: name = File\n" +
" [VariableDefinitionEntry], resolved variable: dir java.io.File\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier dir], resolved variable: dir java.io.File\n" +
" PROPERTY: name = dir\n" +
" parameter: [VariableDefinition], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference String], type: java.lang.String, resolved class: java.lang.String \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: java.lang.String, resolved class: java.lang.String \n" +
" [Identifier String]\n" +
" PROPERTY: name = String\n" +
" [VariableDefinitionEntry], resolved variable: name java.lang.String\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier name], resolved variable: name java.lang.String\n" +
" PROPERTY: name = name\n" +
" [Block], type: void, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
" [SuperConstructorInvocation], resolved method: java.io.File java.io.File\n" +
" [VariableReference], type: java.io.File, resolved variable: dir java.io.File\n" +
" [Identifier dir], type: java.io.File, resolved variable: dir java.io.File\n" +
" PROPERTY: name = dir\n" +
" [VariableReference], type: java.lang.String, resolved variable: name java.lang.String\n" +
" [Identifier name], type: java.lang.String, resolved variable: name java.lang.String\n" +
" PROPERTY: name = name\n" +
" [MethodDeclaration call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
" [Modifiers], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
" [KeywordModifier public]\n" +
" PROPERTY: modifier = public\n" +
" returnType: [TypeReference void], type: void, resolved class: void \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: void, resolved class: void \n" +
" [Identifier void]\n" +
" PROPERTY: name = void\n" +
" methodName: [Identifier call], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: name = call\n" +
" parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference int], type: int, resolved class: int \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: int, resolved class: int \n" +
" [Identifier int]\n" +
" PROPERTY: name = int\n" +
" [VariableDefinitionEntry], resolved variable: arg1 int\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier arg1], resolved variable: arg1 int\n" +
" PROPERTY: name = arg1\n" +
" parameter: [VariableDefinition], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference double], type: double, resolved class: double \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: double, resolved class: double \n" +
" [Identifier double]\n" +
" PROPERTY: name = double\n" +
" [VariableDefinitionEntry], resolved variable: arg2 double\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier arg2], resolved variable: arg2 double\n" +
" PROPERTY: name = arg2\n" +
" [Block], type: void, resolved method: call test.pkg.TypeResolutionTest.Inner\n" +
" [VariableDeclaration], type: boolean, resolved class: boolean \n" +
" [VariableDefinition]\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference boolean], type: boolean, resolved class: boolean \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: boolean, resolved class: boolean \n" +
" [Identifier boolean]\n" +
" PROPERTY: name = boolean\n" +
" [VariableDefinitionEntry], resolved variable: x boolean\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier x], resolved variable: x boolean\n" +
" PROPERTY: name = x\n" +
" [MethodInvocation canRead], type: boolean, resolved method: canRead java.io.File\n" +
" operand: [Super], type: java.io.File\n" +
" methodName: [Identifier canRead], type: boolean, resolved method: canRead java.io.File\n" +
" PROPERTY: name = canRead\n" +
" [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
" [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
" operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
" operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
" [Identifier System]\n" +
" PROPERTY: name = System\n" +
" selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
" PROPERTY: name = out\n" +
" methodName: [Identifier println]\n" +
" PROPERTY: name = println\n" +
" [VariableReference], type: boolean, resolved variable: x boolean\n" +
" [Identifier x], type: boolean, resolved variable: x boolean\n" +
" PROPERTY: name = x\n" +
" [ClassDeclaration Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
" [Modifiers], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
" [Annotation SuppressWarnings], type: java.lang.SuppressWarnings, resolved annotation: java.lang.SuppressWarnings \n" +
" [TypeReference SuppressWarnings], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: java.lang.SuppressWarnings, resolved class: java.lang.SuppressWarnings \n" +
" [Identifier SuppressWarnings]\n" +
" PROPERTY: name = SuppressWarnings\n" +
" [AnnotationElement null], type: java.lang.SuppressWarnings, resolved annotation: java.lang.SuppressWarnings \n" +
" [StringLiteral all], type: java.lang.String\n" +
" PROPERTY: value = \"all\"\n" +
" [KeywordModifier public]\n" +
" PROPERTY: modifier = public\n" +
" [KeywordModifier static]\n" +
" PROPERTY: modifier = static\n" +
" typeName: [Identifier Other], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
" PROPERTY: name = Other\n" +
" [NormalTypeBody], type: test.pkg.TypeResolutionTest.Other, resolved class: test.pkg.TypeResolutionTest.Other \n" +
" [MethodDeclaration client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
" [Modifiers], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
" [KeywordModifier private]\n" +
" PROPERTY: modifier = private\n" +
" returnType: [TypeReference void], type: void, resolved class: void \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: void, resolved class: void \n" +
" [Identifier void]\n" +
" PROPERTY: name = void\n" +
" methodName: [Identifier client], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
" PROPERTY: name = client\n" +
" parameter: [VariableDefinition], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference int], type: int, resolved class: int \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: int, resolved class: int \n" +
" [Identifier int]\n" +
" PROPERTY: name = int\n" +
" [VariableDefinitionEntry], resolved variable: z int\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier z], resolved variable: z int\n" +
" PROPERTY: name = z\n" +
" [Block], type: void, resolved method: client test.pkg.TypeResolutionTest.Other\n" +
" [VariableDeclaration], type: int, resolved class: int \n" +
" [VariableDefinition]\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference int], type: int, resolved class: int \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: int, resolved class: int \n" +
" [Identifier int]\n" +
" PROPERTY: name = int\n" +
" [VariableDefinitionEntry], resolved variable: x int\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier x], resolved variable: x int\n" +
" PROPERTY: name = x\n" +
" [VariableReference], type: int, resolved variable: z int\n" +
" [Identifier z], type: int, resolved variable: z int\n" +
" PROPERTY: name = z\n" +
" [VariableDeclaration], type: int, resolved class: int \n" +
" [VariableDefinition]\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference int], type: int, resolved class: int \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: int, resolved class: int \n" +
" [Identifier int]\n" +
" PROPERTY: name = int\n" +
" [VariableDefinitionEntry], resolved variable: y int\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier y], resolved variable: y int\n" +
" PROPERTY: name = y\n" +
" [BinaryExpression +], type: int\n" +
" PROPERTY: operator = +\n" +
" left: [VariableReference], type: int, resolved variable: x int\n" +
" [Identifier x], type: int, resolved variable: x int\n" +
" PROPERTY: name = x\n" +
" right: [IntegralLiteral 5], type: int\n" +
" PROPERTY: value = 5\n" +
" [VariableDeclaration], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" [VariableDefinition]\n" +
" PROPERTY: varargs = false\n" +
" [Modifiers]\n" +
" type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" [Identifier Inner]\n" +
" PROPERTY: name = Inner\n" +
" [VariableDefinitionEntry], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: arrayDimensions = 0\n" +
" varName: [Identifier inner], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: name = inner\n" +
" [ConstructorInvocation Inner], type: test.pkg.TypeResolutionTest.Inner, resolved method: test.pkg.TypeResolutionTest.Inner test.pkg.TypeResolutionTest.Inner\n" +
" type: [TypeReference Inner], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" PROPERTY: WildcardKind = NONE\n" +
" PROPERTY: arrayDimensions = 0\n" +
" [TypeReferencePart], type: test.pkg.TypeResolutionTest.Inner, resolved class: test.pkg.TypeResolutionTest.Inner \n" +
" [Identifier Inner]\n" +
" PROPERTY: name = Inner\n" +
" [NullLiteral], type: null\n" +
" [NullLiteral], type: null\n" +
" [ExpressionStatement], type: float\n" +
" [BinaryExpression =], type: float\n" +
" PROPERTY: operator = =\n" +
" left: [Select], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" operand: [VariableReference], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" [Identifier inner]\n" +
" PROPERTY: name = inner\n" +
" selected: [Identifier myField], type: float, resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: name = myField\n" +
" right: [IntegralLiteral 6], type: int\n" +
" PROPERTY: value = 6\n" +
" [ExpressionStatement], type: void, resolved method: println java.io.PrintStream\n" +
" [MethodInvocation println], type: void, resolved method: println java.io.PrintStream\n" +
" operand: [Select], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
" operand: [VariableReference], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
" [Identifier System]\n" +
" PROPERTY: name = System\n" +
" selected: [Identifier out], type: java.io.PrintStream, resolved field: out java.lang.System\n" +
" PROPERTY: name = out\n" +
" methodName: [Identifier println]\n" +
" PROPERTY: name = println\n" +
" [Select], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" operand: [VariableReference], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" [Identifier inner]\n" +
" PROPERTY: name = inner\n" +
" selected: [Identifier myInts], type: int[], resolved variable: inner test.pkg.TypeResolutionTest.Inner\n" +
" PROPERTY: name = myInts\n",
actual);
}
public void testStartsWithCompound() throws Exception {
assertTrue(startsWithCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray()
}));
assertTrue(startsWithCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray(),
"other".toCharArray(),
}));
assertFalse(startsWithCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"other".toCharArray()
}));
// Corner cases
assertFalse(startsWithCompound("test.pk",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray(),
}));
assertFalse(startsWithCompound("test.pkg",
new char[][]{
"test".toCharArray()
}));
assertTrue(startsWithCompound("test.",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray()
}));
assertFalse(startsWithCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"pk".toCharArray()
}));
}
public void testEqualsWithCompound() throws Exception {
assertTrue(equalsCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray()
}));
assertFalse(equalsCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray(),
"other".toCharArray(),
}));
assertFalse(equalsCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"other".toCharArray()
}));
// Corner cases
assertFalse(equalsCompound("test.pk",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray(),
}));
assertFalse(equalsCompound("test.pkg",
new char[][]{
"test".toCharArray()
}));
assertFalse(equalsCompound("test.",
new char[][]{
"test".toCharArray(),
"pkg".toCharArray()
}));
assertFalse(equalsCompound("test.pkg",
new char[][]{
"test".toCharArray(),
"pk".toCharArray()
}));
}
@Override
protected Detector getDetector() {
return new SdCardDetector();
}
@Override
protected TestLintClient createClient() {
return new TestLintClient() {
@NonNull
@Override
protected ClassPathInfo getClassPath(@NonNull Project project) {
ClassPathInfo classPath = super.getClassPath(project);
// Insert fake classpath entries (non existent directories) to
// make sure the parser handles that gracefully. See issue 87740.
classPath.getLibraries().add(new File("nonexistent path"));
return classPath;
}
};
}
public static class AstPrettyPrinter implements SourceFormatter {
private final StringBuilder mOutput = new StringBuilder(1000);
private final JavaParser mResolver;
private int mIndent;
private String mName;
public AstPrettyPrinter(JavaParser resolver) {
mResolver = resolver;
}
private void add(String in, Object... args) {
for (int i = 0; i < mIndent; i++) {
mOutput.append(" ");
}
if (mName != null) {
mOutput.append(mName).append(": ");
mName = null;
}
if (args.length == 0) {
mOutput.append(in);
} else {
mOutput.append(String.format(in, args));
}
}
@Override
public void buildInline(Node node) {
buildNode(node);
}
@Override
public void buildBlock(Node node) {
buildNode(node);
}
private void buildNode(Node node) {
if (node == null) {
mIndent++;
return;
}
String name = node.getClass().getSimpleName();
String description = "";
if (node instanceof DescribedNode) {
description = " " + ((DescribedNode) node).getDescription();
}
String typeDescription = "";
String resolutionDescription = "";
JavaParser.TypeDescriptor t = mResolver.getType(null, node);
if (t != null) {
typeDescription = ", type: " + t.getName();
}
ResolvedNode resolved = mResolver.resolve(null, node);
if (resolved != null) {
String c = "unknown";
String extra = "";
if (resolved instanceof ResolvedClass) {
c = "class";
} else if (resolved instanceof ResolvedMethod) {
c = "method";
ResolvedMethod method = (ResolvedMethod) resolved;
extra = method.getContainingClass().getName();
} else if (resolved instanceof ResolvedField) {
c = "field";
ResolvedField field = (ResolvedField) resolved;
extra = field.getContainingClass().getName();
} else if (resolved instanceof ResolvedVariable) {
c = "variable";
ResolvedVariable variable = (ResolvedVariable) resolved;
extra = variable.getType().getName();
} else if (resolved instanceof ResolvedAnnotation) {
c = "annotation";
}
resolutionDescription = String.format(", resolved %1$s: %2$s %3$s",
c, resolved.getName(), extra);
}
add("[%1$s%2$s]%3$s%4$s\n", name, description, typeDescription, resolutionDescription);
mIndent++;
}
@Override
public void fail(String fail) {
Assert.fail(fail);
}
@Override
public void property(String name, Object value) {
add("PROPERTY: %s = %s\n", name, value);
}
@Override
public void keyword(String text) {
}
@Override
public void operator(String text) {
}
@Override
public void verticalSpace() {
}
@Override
public void space() {
}
@Override
public void append(String text) {
}
@Override
public void startSuppressBlock() {
}
@Override
public void endSuppressBlock() {
}
@Override
public void startSuppressIndent() {
}
@Override
public void endSuppressIndent() {
}
@Override
public void closeInline() {
mIndent--;
}
@Override
public void closeBlock() {
mIndent--;
}
@Override
public void addError(int start, int end, String message) {
fail(message);
}
@Override
public String finish() {
return mOutput.toString();
}
@Override
public void setTimeTaken(long taken) {
}
@Override
public void nameNextElement(String name) {
mName = name;
}
}
}