blob: 04a856bcee668b86609c1ee5d63e2fa5a1a0f159 [file] [log] [blame]
/*
* Copyright (C) 2016 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.detector.api;
import com.android.resources.ResourceType;
import com.android.resources.ResourceUrl;
import com.android.utils.Pair;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import com.intellij.psi.JavaRecursiveElementVisitor;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLocalVariable;
import java.io.File;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.TestCase;
import org.intellij.lang.annotations.Language;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UFile;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
@SuppressWarnings("ClassNameDiffersFromFileName")
public class ResourceEvaluatorTest extends TestCase {
@Language("JAVA")
private static String getString(String statementsSource) {
@Language("JAVA")
String s =
""
+ "package test.pkg;\n"
+ "public class Test {\n"
+ " public void test() {\n"
+ " "
+ statementsSource
+ "\n"
+ " }\n"
+ " public static final int RED = R.color.red;\n"
+ " public static final int MY_COLOR = RED;\n"
+ " public void someMethod(@android.support.annotation.DrawableRes @android.support.annotation.StringRes int param) { }\n"
+ "\n"
+ " private static class R {\n"
+ " private static class color {\n"
+ " public static final int foo=0x7f050000;\n"
+ " }\n"
+ " private static class color {\n"
+ " public static final int red=0x7f060000;\n"
+ " public static final int green=0x7f060001;\n"
+ " public static final int blue=0x7f060002;\n"
+ " }\n"
+ " }"
+ "}\n";
return s;
}
private static void checkUast(
String expected,
String statementsSource,
final String targetVariable,
boolean getSpecificType,
boolean allowDereference) {
@Language("JAVA")
String source = getString(statementsSource);
Pair<JavaContext, Disposable> pair =
LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
JavaContext context = pair.getFirst();
Disposable disposable = pair.getSecond();
assertNotNull(context);
UFile uFile = context.getUastFile();
assertNotNull(uFile);
// Find the expression
final AtomicReference<UExpression> reference = new AtomicReference<>();
uFile.accept(
new AbstractUastVisitor() {
@Override
public boolean visitVariable(UVariable variable) {
String name = variable.getName();
if (name != null && name.equals(targetVariable)) {
reference.set(variable.getUastInitializer());
}
return super.visitVariable(variable);
}
});
UExpression expression = reference.get();
ResourceEvaluator evaluator =
new ResourceEvaluator(context.getEvaluator()).allowDereference(allowDereference);
if (getSpecificType) {
ResourceUrl actual = evaluator.getResource(expression);
if (expected == null) {
assertNull(actual);
} else {
assertNotNull(
"Couldn't compute resource for " + source + ", expected " + expected,
actual);
assertEquals(expected, actual.toString());
}
} else {
EnumSet<ResourceType> types = evaluator.getResourceTypes(expression);
if (expected == null) {
assertNull(types);
} else {
assertNotNull(
"Couldn't compute resource types for " + source + ", expected " + expected,
types);
assertEquals(expected, types.toString());
}
}
Disposer.dispose(disposable);
}
private static void check(
String expected,
String statementsSource,
final String targetVariable,
boolean getSpecificType,
boolean allowDereference) {
checkUast(expected, statementsSource, targetVariable, getSpecificType, allowDereference);
@Language("JAVA")
String source = getString(statementsSource);
Pair<JavaContext, Disposable> pair =
LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
JavaContext context = pair.getFirst();
Disposable disposable = pair.getSecond();
assertNotNull(context);
PsiFile javaFile = context.getPsiFile();
assertNotNull(javaFile);
// Find the expression
final AtomicReference<PsiExpression> reference = new AtomicReference<>();
javaFile.accept(
new JavaRecursiveElementVisitor() {
@Override
public void visitLocalVariable(PsiLocalVariable variable) {
super.visitLocalVariable(variable);
String name = variable.getName();
if (name != null && name.equals(targetVariable)) {
reference.set(variable.getInitializer());
}
}
});
PsiExpression expression = reference.get();
ResourceEvaluator evaluator =
new ResourceEvaluator(context.getEvaluator()).allowDereference(allowDereference);
if (getSpecificType) {
ResourceUrl actual = evaluator.getResource(expression);
if (expected == null) {
assertNull(actual);
} else {
assertNotNull(
"Couldn't compute resource for " + source + ", expected " + expected,
actual);
assertEquals(expected, actual.toString());
}
} else {
EnumSet<ResourceType> types = evaluator.getResourceTypes(expression);
if (expected == null) {
assertNull(types);
} else {
assertNotNull(
"Couldn't compute resource types for " + source + ", expected " + expected,
types);
assertEquals(expected, types.toString());
}
}
Disposer.dispose(disposable);
}
private static void checkType(
String expected, String statementsSource, final String targetVariable) {
check(expected, statementsSource, targetVariable, true, true);
}
private static void checkTypes(
String expected, String statementsSource, final String targetVariable) {
check(expected, statementsSource, targetVariable, false, true);
}
private static void checkTypes(
String expected,
String statementsSource,
final String targetVariable,
boolean allowDereference) {
check(expected, statementsSource, targetVariable, false, allowDereference);
}
public void testBasic() {
checkType("@string/foo", "int x = R.string.foo", "x");
}
public void testIndirectFieldReference() {
checkType("@color/red", "int z = RED;\nint w = true ? z : 0", "w");
}
public void testMethodCall() {
checkType(
"@color/green",
""
+ "android.app.Activity context = null;"
+ "int w = context.getResources().getColor(R.color.green);",
"w");
}
public void testMethodCallNoDereference() {
check(
null,
""
+ "android.app.Activity context = null;"
+ "int w = context.getResources().getColor(R.color.green);",
"w",
true,
false);
}
public void testReassignment() {
checkType(
"@string/foo",
""
+ "int x = R.string.foo;\n"
+ "int y = x;\n"
+ "int w;\n"
+ "w = -1;\n"
+ "int z = y;\n",
"z");
}
// Resource Types
public void testReassignmentType() {
checkTypes(
"[string]",
""
+ "int x = R.string.foo;\n"
+ "int y = x;\n"
+ "int w;\n"
+ "w = -1;\n"
+ "int z = y;\n",
"z");
}
public void testMethodCallTypes() {
// public=color int marker
checkTypes(
"[public]",
""
+ "android.app.Activity context = null;"
+ "int w = context.getResources().getColor(R.color.green);",
"w");
}
public void testConditionalTypes() {
// Constant expression: we know exactly which branch to take
checkTypes("[color]", "int z = RED;\nint w = true ? z : R.string.foo", "w");
}
public void testConditionalTypesUnknownCondition() {
// Constant expression: we know exactly which branch to take
checkTypes(
"[color, string]",
"int z = RED;\nint w = toString().indexOf('x') > 2 ? z : R.string.foo",
"w");
}
public void testResourceTypes() {
assertEquals(
ResourceType.ANIM,
ResourceEvaluator.getTypeFromAnnotationSignature(
ResourceEvaluator.ANIM_RES_ANNOTATION.defaultName()));
assertEquals(
ResourceType.STRING,
ResourceEvaluator.getTypeFromAnnotationSignature(
ResourceEvaluator.STRING_RES_ANNOTATION.defaultName()));
assertEquals(
ResourceType.LAYOUT,
ResourceEvaluator.getTypeFromAnnotationSignature(
ResourceEvaluator.LAYOUT_RES_ANNOTATION.defaultName()));
}
}