blob: a2db2e376d84714703ead399c5629b6c4200ec90 [file] [log] [blame]
/*
* Copyright (C) 2014 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 org.jetbrains.android.inspections;
import com.android.SdkConstants;
import com.android.resources.ResourceType;
import com.android.tools.idea.startup.ExternalAnnotationsSupport;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.LightInspectionTestCase;
import org.intellij.lang.annotations.Language;
import org.jetbrains.android.AndroidTestBase;
import org.jetbrains.android.AndroidTestCase;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class ResourceTypeInspectionTest extends LightInspectionTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
//noinspection StatementWithEmptyBody
if (getName().equals("testNotAndroid")) {
// Don't add an Android facet here; we're testing that we're a no-op outside of Android projects
// since the inspection is registered at the .java source type level
return;
}
// Module must have Android facet or resource type inspection will become a no-op
if (AndroidFacet.getInstance(myModule) == null) {
String sdkPath = AndroidTestBase.getDefaultTestSdkPath();
String platform = AndroidTestBase.getDefaultPlatformDir();
AndroidTestCase.addAndroidFacet(myModule, sdkPath, platform, true);
Sdk sdk = ModuleRootManager.getInstance(myModule).getSdk();
assertNotNull(sdk);
@SuppressWarnings("SpellCheckingInspection") SdkModificator sdkModificator = sdk.getSdkModificator();
ExternalAnnotationsSupport.attachJdkAnnotations(sdkModificator);
sdkModificator.commitChanges();
}
// Required by testLibraryRevocablePermissions (but placing it there leads to
// test ordering issues)
myFixture.addFileToProject(SdkConstants.FN_ANDROID_MANIFEST_XML,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
" package=\"test.pkg.permissiontest\">\n" +
"\n" +
" <uses-sdk android:minSdkVersion=\"17\" android:targetSdkVersion=\"23\" />" +
"\n" +
" <permission\n" +
" android:name=\"my.normal.P1\"\n" +
" android:protectionLevel=\"normal\" />\n" +
"\n" +
" <permission\n" +
" android:name=\"my.dangerous.P2\"\n" +
" android:protectionLevel=\"dangerous\" />\n" +
"\n" +
" <uses-permission android:name=\"my.normal.P1\" />\n" +
" <uses-permission android:name=\"my.dangerous.P2\" />\n" +
"\n" +
"</manifest>\n");
}
public void testTypes() {
doCheck("import android.annotation.SuppressLint;\n" +
"import android.annotation.TargetApi;\n" +
"import android.app.Notification;\n" +
"import android.content.Context;\n" +
"import android.content.Intent;\n" +
"import android.content.ServiceConnection;\n" +
"import android.content.res.Resources;\n" +
"import android.os.Build;\n" +
"import android.support.annotation.DrawableRes;\n" +
"import android.view.View;\n" +
"\n" +
"import static android.content.Context.CONNECTIVITY_SERVICE;\n" +
"\n" +
"@SuppressWarnings(\"UnusedDeclaration\")\n" +
"public class X {\n" +
" public void testResourceTypeParameters(Context context, int unknown) {\n" +
" Resources resources = context.getResources();\n" +
" String ok1 = resources.getString(R.string.app_name);\n" +
" String ok2 = resources.getString(unknown);\n" +
" String ok3 = resources.getString(android.R.string.ok);\n" +
" int ok4 = resources.getColor(android.R.color.black);\n" +
" if (testResourceTypeReturnValues(context, true) == R.drawable.ic_launcher) { // ok\n" +
" }\n" +
"\n" +
" //String ok2 = resources.getString(R.string.app_name, 1, 2, 3);\n" +
" float error1 = resources.getDimension(/*Expected resource of type dimen*/R.string.app_name/**/);\n" +
" boolean error2 = resources.getBoolean(/*Expected resource of type bool*/R.string.app_name/**/);\n" +
" boolean error3 = resources.getBoolean(/*Expected resource of type bool*/android.R.drawable.btn_star/**/);\n" +
" if (testResourceTypeReturnValues(context, true) == /*Expected resource of type drawable*/R.string.app_name/**/) {\n" +
" }\n" +
" @SuppressWarnings(\"UnnecessaryLocalVariable\")\n" +
" int flow = R.string.app_name;\n" +
" @SuppressWarnings(\"UnnecessaryLocalVariable\")\n" +
" int flow2 = flow;\n" +
" boolean error4 = resources.getBoolean(/*Expected resource of type bool*/flow2/**/);\n" +
" }\n" +
"\n" +
" @android.support.annotation.DrawableRes\n" +
" public int testResourceTypeReturnValues(Context context, boolean useString) {\n" +
" if (useString) {\n" +
" return /*Expected resource of type drawable*/R.string.app_name/**/; // error\n" +
" } else {\n" +
" return R.drawable.ic_launcher; // ok\n" +
" }\n" +
" }\n" +
"\n" +
" public static final class R {\n" +
" public static final class drawable {\n" +
" public static final int ic_launcher=0x7f020057;\n" +
" }\n" +
" public static final class string {\n" +
" public static final int app_name=0x7f0a000e;\n" +
" }\n" +
" }\n" +
"}\n");
}
public void testTypes2() {
doCheck("package test.pkg;\n" +
"\n" +
"import android.support.annotation.DrawableRes;\n" +
"import android.support.annotation.StringRes;\n" +
"\n" +
"enum X {\n" +
"\n" +
" SKI(/*Expected resource of type drawable*/1/**/, /*Expected resource of type string*/2/**/),\n" +
" SNOWBOARD(/*Expected resource of type drawable*/3/**/, /*Expected resource of type string*/4/**/);\n" +
"\n" +
" private final int mIconResId;\n" +
" private final int mLabelResId;\n" +
"\n" +
" X(@DrawableRes int iconResId, @StringRes int labelResId) {\n" +
" mIconResId = iconResId;\n" +
" mLabelResId = labelResId;\n" +
" }\n" +
"\n" +
"}");
}
public void testIntDef() {
doCheck("import android.annotation.SuppressLint;\n" +
"import android.annotation.TargetApi;\n" +
"import android.app.Notification;\n" +
"import android.content.Context;\n" +
"import android.content.Intent;\n" +
"import android.content.ServiceConnection;\n" +
"import android.content.res.Resources;\n" +
"import android.os.Build;\n" +
"import android.support.annotation.DrawableRes;\n" +
"import android.view.View;\n" +
"\n" +
"import static android.content.Context.CONNECTIVITY_SERVICE;\n" +
"\n" +
"@SuppressWarnings(\"UnusedDeclaration\")\n" +
"public class X {\n" +
"\n" +
" @TargetApi(Build.VERSION_CODES.KITKAT)\n" +
" public void testStringDef(Context context, String unknown) {\n" +
" Object ok1 = context.getSystemService(unknown);\n" +
" Object ok2 = context.getSystemService(Context.CLIPBOARD_SERVICE);\n" +
" Object ok3 = context.getSystemService(android.content.Context.WINDOW_SERVICE);\n" +
" Object ok4 = context.getSystemService(CONNECTIVITY_SERVICE);\n" +
" }\n" +
"\n" +
" @SuppressLint(\"UseCheckPermission\")\n" +
" @TargetApi(Build.VERSION_CODES.KITKAT)\n" +
" public void testIntDef(Context context, int unknown, View view) {\n" +
" view.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); // OK\n" +
" view.setLayoutDirection(/*Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE*/View.TEXT_ALIGNMENT_TEXT_START/**/); // Error\n" +
" view.setLayoutDirection(/*Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL, View.LAYOUT_DIRECTION_INHERIT, View.LAYOUT_DIRECTION_LOCALE*/View.LAYOUT_DIRECTION_RTL | View.LAYOUT_DIRECTION_RTL/**/); // Error\n" +
" }\n" +
"\n" +
" @TargetApi(Build.VERSION_CODES.KITKAT)\n" +
" public void testIntDefFlags(Context context, int unknown, Intent intent,\n" +
" ServiceConnection connection) {\n" +
" // Flags\n" +
" Object ok1 = context.bindService(intent, connection, 0);\n" +
" Object ok2 = context.bindService(intent, connection, -1);\n" +
" Object ok3 = context.bindService(intent, connection, Context.BIND_ABOVE_CLIENT);\n" +
" Object ok4 = context.bindService(intent, connection, Context.BIND_ABOVE_CLIENT\n" +
" | Context.BIND_AUTO_CREATE);\n" +
" int flags1 = Context.BIND_ABOVE_CLIENT | Context.BIND_AUTO_CREATE;\n" +
" Object ok5 = context.bindService(intent, connection, flags1);\n" +
"\n" +
" Object error1 = context.bindService(intent, connection,\n" +
" /*Must be one or more of: Context.BIND_AUTO_CREATE, Context.BIND_DEBUG_UNBIND, Context.BIND_NOT_FOREGROUND, Context.BIND_ABOVE_CLIENT, Context.BIND_ALLOW_OOM_MANAGEMENT, Context.BIND_WAIVE_PRIORITY, Context.BIND_IMPORTANT, Context.BIND_ADJUST_WITH_ACTIVITY*/Context.BIND_ABOVE_CLIENT | Context.CONTEXT_IGNORE_SECURITY/**/);\n" +
" int flags2 = Context.BIND_ABOVE_CLIENT | Context.CONTEXT_IGNORE_SECURITY;\n" +
" Object error2 = context.bindService(intent, connection, /*Must be one or more of: Context.BIND_AUTO_CREATE, Context.BIND_DEBUG_UNBIND, Context.BIND_NOT_FOREGROUND, Context.BIND_ABOVE_CLIENT, Context.BIND_ALLOW_OOM_MANAGEMENT, Context.BIND_WAIVE_PRIORITY, Context.BIND_IMPORTANT, Context.BIND_ADJUST_WITH_ACTIVITY*/flags2/**/);\n" +
" }\n" +
"}\n");
}
public void testFlow() {
doCheck("import android.content.res.Resources;\n" +
"import android.support.annotation.DrawableRes;\n" +
"import android.support.annotation.StringRes;\n" +
"import android.support.annotation.StyleRes;\n" +
"\n" +
"import java.util.Random;\n" +
"\n" +
"@SuppressWarnings(\"UnusedDeclaration\")\n" +
"public class X {\n" +
" public void testLiterals(Resources resources) {\n" +
" resources.getDrawable(0); // OK\n" +
" resources.getDrawable(-1); // OK\n" +
" resources.getDrawable(/*Expected resource of type drawable*/10/**/); // ERROR\n" +
" }\n" +
"\n" +
" public void testConstants(Resources resources) {\n" +
" resources.getDrawable(R.drawable.my_drawable); // OK\n" +
" resources.getDrawable(/*Expected resource of type drawable*/R.string.my_string/**/); // ERROR\n" +
" }\n" +
"\n" +
" public void testFields(String fileExt, Resources resources) {\n" +
" int mimeIconId = MimeTypes.styleAndDrawable;\n" +
" resources.getDrawable(mimeIconId); // OK\n" +
"\n" +
" int s1 = MimeTypes.style;\n" +
" resources.getDrawable(/*Expected resource of type drawable*/s1/**/); // ERROR\n" +
" int s2 = MimeTypes.styleAndDrawable;\n" +
" resources.getDrawable(s2); // OK\n" +
" int w3 = MimeTypes.drawable;\n" +
" resources.getDrawable(w3); // OK\n" +
"\n" +
" // Direct reference\n" +
" resources.getDrawable(/*Expected resource of type drawable*/MimeTypes.style/**/); // ERROR\n" +
" resources.getDrawable(MimeTypes.styleAndDrawable); // OK\n" +
" resources.getDrawable(MimeTypes.drawable); // OK\n" +
" }\n" +
"\n" +
" public void testCalls(String fileExt, Resources resources) {\n" +
" int mimeIconId = MimeTypes.getIconForExt(fileExt);\n" +
" resources.getDrawable(mimeIconId); // OK\n" +
" resources.getDrawable(MimeTypes.getInferredString()); // OK (wrong but can't infer type)\n" +
" resources.getDrawable(MimeTypes.getInferredDrawable()); // OK\n" +
" resources.getDrawable(/*Expected resource of type drawable*/MimeTypes.getAnnotatedString()/**/); // Error\n" +
" resources.getDrawable(MimeTypes.getAnnotatedDrawable()); // OK\n" +
" resources.getDrawable(MimeTypes.getUnknownType()); // OK (unknown/uncertain)\n" +
" }\n" +
"\n" +
" private static class MimeTypes {\n" +
" @android.support.annotation.StyleRes\n" +
" @android.support.annotation.DrawableRes\n" +
" public static int styleAndDrawable;\n" +
"\n" +
" @android.support.annotation.StyleRes\n" +
" public static int style;\n" +
"\n" +
" @android.support.annotation.DrawableRes\n" +
" public static int drawable;\n" +
"\n" +
" @android.support.annotation.DrawableRes\n" +
" public static int getIconForExt(String ext) {\n" +
" return R.drawable.my_drawable;\n" +
" }\n" +
"\n" +
" public static int getInferredString() {\n" +
" // Implied string - can we handle this?\n" +
" return R.string.my_string;\n" +
" }\n" +
"\n" +
" public static int getInferredDrawable() {\n" +
" // Implied drawable - can we handle this?\n" +
" return R.drawable.my_drawable;\n" +
" }\n" +
"\n" +
" @android.support.annotation.StringRes\n" +
" public static int getAnnotatedString() {\n" +
" return R.string.my_string;\n" +
" }\n" +
"\n" +
" @android.support.annotation.DrawableRes\n" +
" public static int getAnnotatedDrawable() {\n" +
" return R.drawable.my_drawable;\n" +
" }\n" +
"\n" +
" public static int getUnknownType() {\n" +
" return new Random(1000).nextInt();\n" +
" }\n" +
" }\n" +
"\n" +
" public static final class R {\n" +
" public static final class drawable {\n" +
" public static final int my_drawable =0x7f020057;\n" +
" }\n" +
" public static final class string {\n" +
" public static final int my_string =0x7f0a000e;\n" +
" }\n" +
" }\n" +
"}\n");
}
public void testColorAsDrawable() {
doCheck("package p1.p2;\n" +
"\n" +
"import android.content.Context;\n" +
"import android.view.View;\n" +
"\n" +
"public class X {\n" +
" static void test(Context context) {\n" +
" View separator = new View(context);\n" +
" separator.setBackgroundResource(android.R.color.black);\n" +
" }\n" +
"}\n");
}
public void testMipmap() {
doCheck("package p1.p2;\n" +
"\n" +
"import android.app.Activity;\n" +
"\n" +
"public class X extends Activity {\n" +
" public void test() {\n" +
" Object o = getResources().getDrawable(R.mipmap.ic_launcher);\n" +
" }\n" +
"\n" +
" public static final class R {\n" +
" public static final class drawable {\n" +
" public static int icon=0x7f020000;\n" +
" }\n" +
" public static final class mipmap {\n" +
" public static int ic_launcher=0x7f020001;\n" +
" }\n" +
" }\n" +
"}");
}
public void testRanges() {
doCheck("import android.support.annotation.FloatRange;\n" +
"import android.support.annotation.IntRange;\n" +
"import android.support.annotation.Size;\n" +
"\n" +
"@SuppressWarnings(\"UnusedDeclaration\")\n" +
"class X {\n" +
" public void printExact(@Size(5) String arg) { System.out.println(arg); }\n" +
" public void printMin(@Size(min=5) String arg) { }\n" +
" public void printMax(@Size(max=8) String arg) { }\n" +
" public void printRange(@Size(min=4,max=6) String arg) { }\n" +
" public void printExact(@Size(5) int[] arg) { }\n" +
" public void printMin(@Size(min=5) int[] arg) { }\n" +
" public void printMax(@Size(max=8) int[] arg) { }\n" +
" public void printRange(@Size(min=4,max=6) int[] arg) { }\n" +
" public void printMultiple(@Size(multiple=3) int[] arg) { }\n" +
" public void printMinMultiple(@Size(min=4,multiple=3) int[] arg) { }\n" +
" public void printAtLeast(@IntRange(from=4) int arg) { }\n" +
" public void printAtMost(@IntRange(to=7) int arg) { }\n" +
" public void printBetween(@IntRange(from=4,to=7) int arg) { }\n" +
" public void printAtLeastInclusive(@FloatRange(from=2.5) float arg) { }\n" +
" public void printAtLeastExclusive(@FloatRange(from=2.5,fromInclusive=false) float arg) { }\n" +
" public void printAtMostInclusive(@FloatRange(to=7) double arg) { }\n" +
" public void printAtMostExclusive(@FloatRange(to=7,toInclusive=false) double arg) { }\n" +
" public void printBetweenFromInclusiveToInclusive(@FloatRange(from=2.5,to=5.0) float arg) { }\n" +
" public void printBetweenFromExclusiveToInclusive(@FloatRange(from=2.5,to=5.0,fromInclusive=false) float arg) { }\n" +
" public void printBetweenFromInclusiveToExclusive(@FloatRange(from=2.5,to=5.0,toInclusive=false) float arg) { }\n" +
" public void printBetweenFromExclusiveToExclusive(@FloatRange(from=2.5,to=5.0,fromInclusive=false,toInclusive=false) float arg) { }\n" +
" public static final int MINIMUM = -1;\n" +
" public static final int MAXIMUM = 42;\n" +
" public static final int SIZE = 5;\n" +
" public void printIndirect(@IntRange(from = MINIMUM, to = MAXIMUM) int arg) { }\n" +
" public void printIndirectSize(@Size(SIZE) String arg) { }\n" +
"\n" +
" public void testLength() {\n" +
" String arg = \"1234\";\n" +
" printExact(/*Length must be exactly 5*/arg/**/); // ERROR\n" +
"\n" +
"\n" +
" printExact(/*Length must be exactly 5*/\"1234\"/**/); // ERROR\n" +
" printExact(\"12345\"); // OK\n" +
" printExact(/*Length must be exactly 5*/\"123456\"/**/); // ERROR\n" +
"\n" +
" printMin(/*Length must be at least 5 (was 4)*/\"1234\"/**/); // ERROR\n" +
" printMin(\"12345\"); // OK\n" +
" printMin(\"123456\"); // OK\n" +
"\n" +
" printMax(\"123456\"); // OK\n" +
" printMax(\"1234567\"); // OK\n" +
" printMax(\"12345678\"); // OK\n" +
" printMax(/*Length must be at most 8 (was 9)*/\"123456789\"/**/); // ERROR\n" +
" printAtMost(1 << 2); // OK\n" +
" printMax(\"123456\" + \"\"); //OK\n" +
" printAtMost(/*Value must be ≤ 7 (was 8)*/1 << 2 + 1/**/); // ERROR\n" +
" printAtMost(/*Value must be ≤ 7 (was 32)*/1 << 5/**/); // ERROR\n" +
" printMax(/*Length must be at most 8 (was 11)*/\"123456\" + \"45678\"/**/); //ERROR\n" +
"\n" +
" printRange(/*Length must be at least 4 and at most 6 (was 3)*/\"123\"/**/); // ERROR\n" +
" printRange(\"1234\"); // OK\n" +
" printRange(\"12345\"); // OK\n" +
" printRange(\"123456\"); // OK\n" +
" printRange(/*Length must be at least 4 and at most 6 (was 7)*/\"1234567\"/**/); // ERROR\n" +
" printIndirectSize(/*Length must be exactly 5*/\"1234567\"/**/); // ERROR\n" +
" }\n" +
"\n" +
" public void testSize() {\n" +
" printExact(/*Size must be exactly 5*/new int[]{1, 2, 3, 4}/**/); // ERROR\n" +
" printExact(new int[]{1, 2, 3, 4, 5}); // OK\n" +
" printExact(/*Size must be exactly 5*/new int[]{1, 2, 3, 4, 5, 6}/**/); // ERROR\n" +
"\n" +
" printMin(/*Size must be at least 5 (was 4)*/new int[]{1, 2, 3, 4}/**/); // ERROR\n" +
" printMin(new int[]{1, 2, 3, 4, 5}); // OK\n" +
" printMin(new int[]{1, 2, 3, 4, 5, 6}); // OK\n" +
"\n" +
" printMax(new int[]{1, 2, 3, 4, 5, 6}); // OK\n" +
" printMax(new int[]{1, 2, 3, 4, 5, 6, 7}); // OK\n" +
" printMax(new int[]{1, 2, 3, 4, 5, 6, 7, 8}); // OK\n" +
" printMax(new int[]{1, 2, 3, 4, 5, 6, 7, 8}); // OK\n" +
" printMax(/*Size must be at most 8 (was 9)*/new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}/**/); // ERROR\n" +
"\n" +
" printRange(/*Size must be at least 4 and at most 6 (was 3)*/new int[] {1,2,3}/**/); // ERROR\n" +
" printRange(new int[] {1,2,3,4}); // OK\n" +
" printRange(new int[] {1,2,3,4,5}); // OK\n" +
" printRange(new int[] {1,2,3,4,5,6}); // OK\n" +
" printRange(/*Size must be at least 4 and at most 6 (was 7)*/new int[] {1,2,3,4,5,6,7}/**/); // ERROR\n" +
"\n" +
" printMultiple(new int[] {1,2,3}); // OK\n" +
" printMultiple(/*Size must be a multiple of 3 (was 4)*/new int[] {1,2,3,4}/**/); // ERROR\n" +
" printMultiple(/*Size must be a multiple of 3 (was 5)*/new int[] {1,2,3,4,5}/**/); // ERROR\n" +
" printMultiple(new int[] {1,2,3,4,5,6}); // OK\n" +
" printMultiple(/*Size must be a multiple of 3 (was 7)*/new int[] {1,2,3,4,5,6,7}/**/); // ERROR\n" +
"\n" +
" printMinMultiple(new int[] {1,2,3,4,5,6}); // OK\n" +
" printMinMultiple(/*Size must be at least 4 and a multiple of 3 (was 3)*/new int[]{1, 2, 3}/**/); // ERROR\n" +
" }\n" +
"\n" +
" public void testSize2(int[] unknownSize) {\n" +
" int[] location1 = new int[5];\n" +
" printExact(location1);\n" +
" int[] location2 = new int[6];\n" +
" printExact(/*Size must be exactly 5*/location2/**/);\n" +
" printExact(unknownSize);\n" +
" }\n" +
"\n" +
" public void testIntRange() {\n" +
" printAtLeast(/*Value must be ≥ 4 (was 3)*/3/**/); // ERROR\n" +
" printAtLeast(4); // OK\n" +
" printAtLeast(5); // OK\n" +
"\n" +
" printAtMost(5); // OK\n" +
" printAtMost(6); // OK\n" +
" printAtMost(7); // OK\n" +
" printAtMost(/*Value must be ≤ 7 (was 8)*/8/**/); // ERROR\n" +
"\n" +
" printBetween(/*Value must be ≥ 4 and ≤ 7 (was 3)*/3/**/); // ERROR\n" +
" printBetween(4); // OK\n" +
" printBetween(5); // OK\n" +
" printBetween(6); // OK\n" +
" printBetween(7); // OK\n" +
" printBetween(/*Value must be ≥ 4 and ≤ 7 (was 8)*/8/**/); // ERROR\n" +
" int value = 8;\n" +
" printBetween(/*Value must be ≥ 4 and ≤ 7 (was 8)*/value/**/); // ERROR\n" +
" printBetween(/*Value must be ≥ 4 and ≤ 7 (was -7)*/-7/**/);\n" +
" printIndirect(/*Value must be ≥ -1 and ≤ 42 (was -2)*/-2/**/);\n" +
" }\n" +
"\n" +
" public void testFloatRange() {\n" +
" printAtLeastInclusive(/*Value must be ≥ 2.5 (was 2.49f)*/2.49f/**/); // ERROR\n" +
" printAtLeastInclusive(2.5f); // OK\n" +
" printAtLeastInclusive(2.6f); // OK\n" +
"\n" +
" printAtLeastExclusive(/*Value must be > 2.5 (was 2.49f)*/2.49f/**/); // ERROR\n" +
" printAtLeastExclusive(/*Value must be > 2.5 (was 2.5f)*/2.5f/**/); // ERROR\n" +
" printAtLeastExclusive(2.501f); // OK\n" +
" printAtLeastExclusive(/*Value must be > 2.5 (was -10.0)*/-10/**/);\n" +
"\n" +
" printAtMostInclusive(6.8f); // OK\n" +
" printAtMostInclusive(6.9f); // OK\n" +
" printAtMostInclusive(7.0f); // OK\n" +
" printAtMostInclusive(/*Value must be ≤ 7.0 (was 7.1f)*/7.1f/**/); // ERROR\n" +
"\n" +
" printAtMostExclusive(6.9f); // OK\n" +
" printAtMostExclusive(6.99f); // OK\n" +
" printAtMostExclusive(/*Value must be < 7.0 (was 7.0f)*/7.0f/**/); // ERROR\n" +
" printAtMostExclusive(/*Value must be < 7.0 (was 7.1f)*/7.1f/**/); // ERROR\n" +
"\n" +
" printBetweenFromInclusiveToInclusive(/*Value must be ≥ 2.5 and ≤ 5.0 (was 2.4f)*/2.4f/**/); // ERROR\n" +
" printBetweenFromInclusiveToInclusive(2.5f); // OK\n" +
" printBetweenFromInclusiveToInclusive(3f); // OK\n" +
" printBetweenFromInclusiveToInclusive(5.0f); // OK\n" +
" printBetweenFromInclusiveToInclusive(/*Value must be ≥ 2.5 and ≤ 5.0 (was 5.1f)*/5.1f/**/); // ERROR\n" +
"\n" +
" printBetweenFromExclusiveToInclusive(/*Value must be > 2.5 and ≤ 5.0 (was 2.4f)*/2.4f/**/); // ERROR\n" +
" printBetweenFromExclusiveToInclusive(/*Value must be > 2.5 and ≤ 5.0 (was 2.5f)*/2.5f/**/); // ERROR\n" +
" printBetweenFromExclusiveToInclusive(5.0f); // OK\n" +
" printBetweenFromExclusiveToInclusive(/*Value must be > 2.5 and ≤ 5.0 (was 5.1f)*/5.1f/**/); // ERROR\n" +
"\n" +
" printBetweenFromInclusiveToExclusive(/*Value must be ≥ 2.5 and < 5.0 (was 2.4f)*/2.4f/**/); // ERROR\n" +
" printBetweenFromInclusiveToExclusive(2.5f); // OK\n" +
" printBetweenFromInclusiveToExclusive(3f); // OK\n" +
" printBetweenFromInclusiveToExclusive(4.99f); // OK\n" +
" printBetweenFromInclusiveToExclusive(/*Value must be ≥ 2.5 and < 5.0 (was 5.0f)*/5.0f/**/); // ERROR\n" +
"\n" +
" printBetweenFromExclusiveToExclusive(/*Value must be > 2.5 and < 5.0 (was 2.4f)*/2.4f/**/); // ERROR\n" +
" printBetweenFromExclusiveToExclusive(/*Value must be > 2.5 and < 5.0 (was 2.5f)*/2.5f/**/); // ERROR\n" +
" printBetweenFromExclusiveToExclusive(2.51f); // OK\n" +
" printBetweenFromExclusiveToExclusive(4.99f); // OK\n" +
" printBetweenFromExclusiveToExclusive(/*Value must be > 2.5 and < 5.0 (was 5.0f)*/5.0f/**/); // ERROR\n" +
" }\n" +
"}\n");
}
public void testColorInt() {
doCheck("import android.app.Activity;\n" +
"import android.graphics.Paint;\n" +
"import android.widget.TextView;\n" +
"\n" +
"public class X extends Activity {\n" +
" public void foo(TextView textView, int foo) {\n" +
" Paint paint2 = new Paint();\n" +
" paint2.setColor(/*Should pass resolved color instead of resource id here: `getResources().getColor(R.color.blue)`*/R.color.blue/**/);\n" +
" // Wrong\n" +
" textView.setTextColor(/*Should pass resolved color instead of resource id here: `getResources().getColor(R.color.red)`*/R.color.red/**/);\n" +
" textView.setTextColor(/*Should pass resolved color instead of resource id here: `getResources().getColor(android.R.color.black)`*/android.R.color.black/**/);\n" +
" textView.setTextColor(/*Should pass resolved color instead of resource id here: `getResources().getColor(foo > 0 ? R.color.green : R.color.blue)`*/foo > 0 ? R.color.green : R.color.blue/**/);\n" +
" // OK\n" +
" textView.setTextColor(getResources().getColor(R.color.red));\n" +
" // OK\n" +
" foo1(R.color.blue);\n" +
" foo2(0xffff0000);\n" +
" // Wrong\n" +
" foo1(/*Expected resource of type color*/0xffff0000/**/);\n" +
" foo2(/*Should pass resolved color instead of resource id here: `getResources().getColor(R.color.blue)`*/R.color.blue/**/);\n" +
" }\n" +
"\n" +
" private void foo1(@android.support.annotation.ColorRes int c) {\n" +
" }\n" +
"\n" +
" private void foo2(@android.support.annotation.ColorInt int c) {\n" +
" }\n" +
"\n" +
" private static class R {\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" +
"}\n");
}
public void testColorInt2() {
doCheck("package test.pkg;\n" +
"import android.content.Context;\n" +
"import android.content.res.Resources;\n" +
"import android.support.annotation.ColorInt;\n" +
"import android.support.annotation.ColorRes;\n" +
"\n" +
"public abstract class X {\n" +
" @ColorInt\n" +
" public abstract int getColor1();\n" +
" public abstract void setColor1(@ColorRes int color);\n" +
" @ColorRes\n" +
" public abstract int getColor2();\n" +
" public abstract void setColor2(@ColorInt int color);\n" +
"\n" +
" public void test1(Context context) {\n" +
" int actualColor = getColor1();\n" +
" setColor1(/*Expected resource of type color*/actualColor/**/); // ERROR\n" +
" setColor1(/*Expected resource of type color*/getColor1()/**/); // ERROR\n" +
" setColor1(getColor2()); // OK\n" +
" }\n" +
" public void test2(Context context) {\n" +
" int actualColor = getColor2();\n" +
" setColor2(/*Should pass resolved color instead of resource id here: `getResources().getColor(actualColor)`*/actualColor/**/); // ERROR\n" +
" setColor2(/*Should pass resolved color instead of resource id here: `getResources().getColor(getColor2())`*/getColor2()/**/); // ERROR\n" +
" setColor2(getColor1()); // OK\n" +
" }\n" +
"}\n");
}
public void testCheckResult() {
doCheck("import android.Manifest;\n" +
"import android.content.Context;\n" +
"import android.content.pm.PackageManager;\n" +
"import android.graphics.Bitmap;\n" +
"\n" +
"public class X {\n" +
" private void foo(Context context) {\n" +
" /*The result of 'checkCallingOrSelfPermission' is not used; did you mean to call 'enforceCallingOrSelfPermission(String,String)'?*/context.checkCallingOrSelfPermission(Manifest.permission.INTERNET)/**/; // WRONG\n" +
" /*The result of 'checkPermission' is not used; did you mean to call 'enforcePermission(String,int,int,String)'?*/context.checkPermission(Manifest.permission.INTERNET, 1, 1)/**/;\n" +
" check(context.checkCallingOrSelfPermission(Manifest.permission.INTERNET)); // OK\n" +
" int check = context.checkCallingOrSelfPermission(Manifest.permission.INTERNET); // OK\n" +
" if (context.checkCallingOrSelfPermission(Manifest.permission.INTERNET) // OK\n" +
" != PackageManager.PERMISSION_GRANTED) {\n" +
" showAlert(context, \"Error\",\n" +
" \"Application requires permission to access the Internet\");\n" +
" }\n" +
" }\n" +
"\n" +
" private Bitmap checkResult(Bitmap bitmap) {\n" +
" /*The result 'extractAlpha' is not used*/bitmap.extractAlpha()/**/; // WARNING\n" +
" Bitmap bitmap2 = bitmap.extractAlpha(); // OK\n" +
" call(bitmap.extractAlpha()); // OK\n" +
" return bitmap.extractAlpha(); // OK\n" +
" }\n" +
"\n" +
" private void showAlert(Context context, String error, String s) {\n" +
" }\n" +
"\n" +
" private void check(int i) {\n" +
" }\n" +
" private void call(Bitmap bitmap) {\n" +
" }\n" +
"}");
}
public void testMissingPermission() {
doCheck("import android.Manifest;\n" +
"import android.content.Context;\n" +
"import android.content.pm.PackageManager;\n" +
"import android.graphics.Bitmap;\n" +
"import android.support.annotation.RequiresPermission;\n" +
"\n" +
"import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n" +
"import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n" +
"\n" +
"public class X {\n" +
" private static void foo(Context context, LocationManager manager) {\n" +
" /*Missing permissions required by LocationManager.myMethod: android.permission.ACCESS_FINE_LOCATION or android.permission.ACCESS_COARSE_LOCATION*/manager.myMethod(\"myprovider\")/**/;\n" +
" }\n" +
"\n" +
" @SuppressWarnings(\"UnusedDeclaration\")\n" +
" public abstract class LocationManager {\n" +
" @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})\n" +
" public abstract Location myMethod(String provider);\n" +
" public class Location {\n" +
" }\n" +
" }\n" +
"}");
}
public void testImpliedPermissions() {
// Regression test for
// https://code.google.com/p/android/issues/detail?id=177381
doCheck("package test.pkg;\n" +
"import android.support.annotation.RequiresPermission;\n" +
"\n" +
"public class X {\n" +
" @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n" +
" public void method1() {\n" +
" }\n" +
"\n" +
" @RequiresPermission(\"my.permission.PERM1\")\n" +
" public void method2() {\n" +
" /*Missing permissions required by X.method1: my.permission.PERM2*/method1()/**/;\n" +
" }\n" +
"\n" +
" @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n" +
" public void method3() {\n" +
" // The above @RequiresPermission implies that we are holding these\n" +
" // permissions here, so the call to method1() should not be flagged as\n" +
" // missing a permission!\n" +
" method1();\n" +
" }\n" +
"}\n");
}
public void testLibraryRevocablePermission() {
doCheck("package test.pkg;\n" +
"\n" +
"import android.support.annotation.RequiresPermission;\n" +
"\n" +
"public class X {\n" +
" public void something() {\n" +
" /*Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or handle a potential `SecurityException`*/methodRequiresDangerous()/**/;\n" +
" methodRequiresNormal();\n" +
" }\n" +
"\n" +
" @RequiresPermission(\"my.normal.P1\")\n" +
" public void methodRequiresNormal() {\n" +
" }\n" +
"\n" +
" @RequiresPermission(\"my.dangerous.P2\")\n" +
" public void methodRequiresDangerous() {\n" +
" }\n" +
"}\n");
}
public void testIntentsAndContentResolvers() {
doCheck("package test.pkg;\n" +
"\n" +
"import android.Manifest;\n" +
"import android.app.Activity;\n" +
"import android.content.ContentResolver;\n" +
"import android.content.Context;\n" +
"import android.content.Intent;\n" +
"import android.net.Uri;\n" +
"import android.support.annotation.RequiresPermission;\n" +
"\n" +
"import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;\n" +
"import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;\n" +
"\n" +
"@SuppressWarnings({\"deprecation\", \"unused\"})\n" +
"public class X {\n" +
" @RequiresPermission(Manifest.permission.CALL_PHONE)\n" +
" public static final String ACTION_CALL = \"android.intent.action.CALL\";\n" +
"\n" +
" @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))\n" +
" @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))\n" +
" public static final Uri BOOKMARKS_URI = Uri.parse(\"content://browser/bookmarks\");\n" +
"\n" +
" public static final Uri COMBINED_URI = Uri.withAppendedPath(BOOKMARKS_URI, \"bookmarks\");\n" +
"\n" +
" public static void activities1(Activity activity) {\n" +
" Intent intent = new Intent(Intent.ACTION_CALL);\n" +
" intent.setData(Uri.parse(\"tel:1234567890\"));\n" +
" // This one will only be flagged if we have framework metadata on Intent.ACTION_CALL\n" +
" activity.startActivity(intent);\n" +
" }\n" +
"\n" +
" public static void activities2(Activity activity) {\n" +
" Intent intent = new Intent(ACTION_CALL);\n" +
" intent.setData(Uri.parse(\"tel:1234567890\"));\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startActivity(intent)/**/;\n" +
" }\n" +
"\n" +
" public static void activities3(Activity activity) {\n" +
" Intent intent;\n" +
" intent = new Intent(ACTION_CALL);\n" +
" intent.setData(Uri.parse(\"tel:1234567890\"));\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startActivity(intent)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startActivity(intent, null)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startActivityForResult(intent, 0)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startActivityFromChild(activity, intent, 0)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startActivityIfNeeded(intent, 0)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startActivityFromFragment(null, intent, 0)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/activity.startNextMatchingActivity(intent)/**/;\n" +
" startActivity(\"\"); // Not an error!\n" +
" }\n" +
"\n" +
" public static void broadcasts(Context context) {\n" +
" Intent intent;\n" +
" intent = new Intent(ACTION_CALL);\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/context.sendBroadcast(intent)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/context.sendBroadcast(intent, \"\")/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/context.sendBroadcastAsUser(intent, null)/**/;\n" +
" /*Missing permissions required by intent X.ACTION_CALL: android.permission.CALL_PHONE*/context.sendStickyBroadcast(intent)/**/;\n" +
" }\n" +
"\n" +
" public static void contentResolvers(Context context, ContentResolver resolver) {\n" +
" // read\n" +
" /*Missing permissions required to read X.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS*/resolver.query(BOOKMARKS_URI, null, null, null, null)/**/;\n" +
"\n" +
" // write\n" +
" /*Missing permissions required to write X.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS*/resolver.insert(BOOKMARKS_URI, null)/**/;\n" +
" /*Missing permissions required to write X.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS*/resolver.delete(BOOKMARKS_URI, null, null)/**/;\n" +
" /*Missing permissions required to write X.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS*/resolver.update(BOOKMARKS_URI, null, null, null)/**/;\n" +
"\n" +
" // Framework (external) annotation\n" +
" /*Missing permissions required to write Browser.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS*/resolver.update(android.provider.Browser.BOOKMARKS_URI, null, null, null)/**/;\n" +
"\n" +
" // URI manipulations\n" +
" /*Missing permissions required to write X.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS*/resolver.insert(COMBINED_URI, null)/**/;\n" +
" }\n" +
"\n" +
" public static void startActivity(Object other) {\n" +
" // Unrelated\n" +
" }\n" +
"}\n");
}
public void testWrongThread() {
doCheck("import android.support.annotation.MainThread;\n" +
"import android.support.annotation.UiThread;\n" +
"import android.support.annotation.WorkerThread;\n" +
"\n" +
"public class X {\n" +
" public AsyncTask testTask() {\n" +
"\n" +
" return new AsyncTask() {\n" +
" final CustomView view = new CustomView();\n" +
"\n" +
" @Override\n" +
" protected void doInBackground(Object... params) {\n" +
" /*Method onPreExecute must be called from the main thread, currently inferred thread is worker*/onPreExecute()/**/; // ERROR\n" +
" /*Method paint must be called from the UI thread, currently inferred thread is worker*/view.paint()/**/; // ERROR\n" +
" publishProgress(); // OK\n" +
" }\n" +
"\n" +
" @Override\n" +
" protected void onPreExecute() {\n" +
" /*Method publishProgress must be called from the worker thread, currently inferred thread is main*/publishProgress()/**/; // ERROR\n" +
" onProgressUpdate(); // OK\n" +
" }\n" +
" };\n" +
" }\n" +
"\n" +
" @UiThread\n" +
" public static class View {\n" +
" public void paint() {\n" +
" }\n" +
" }\n" +
"\n" +
" public static class CustomView extends View {\n" +
" }\n" +
"\n" +
" public static abstract class AsyncTask {\n" +
" @WorkerThread\n" +
" protected abstract void doInBackground(Object... params);\n" +
"\n" +
" @MainThread\n" +
" protected void onPreExecute() {\n" +
" }\n" +
"\n" +
" @MainThread\n" +
" protected void onProgressUpdate(Object... values) {\n" +
" }\n" +
"\n" +
" @WorkerThread\n" +
" protected final void publishProgress(Object... values) {\n" +
" }\n" +
" }\n" +
"}\n");
}
public void testCombinedIntDefAndIntRange() throws Exception {
doCheck("package test.pkg;\n" +
"\n" +
"import android.support.annotation.IntDef;\n" +
"import android.support.annotation.IntRange;\n" +
"\n" +
"import java.lang.annotation.Retention;\n" +
"import java.lang.annotation.RetentionPolicy;\n" +
"\n" +
"@SuppressWarnings({\"UnusedParameters\", \"unused\", \"SpellCheckingInspection\"})\n" +
"public class X {\n" +
"\n" +
" public static final int UNRELATED = 500;\n" +
"\n" +
" @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n" +
" @IntRange(from = 10)\n" +
" @Retention(RetentionPolicy.SOURCE)\n" +
" public @interface Duration {}\n" +
"\n" +
" public static final int LENGTH_INDEFINITE = -2;\n" +
" public static final int LENGTH_SHORT = -1;\n" +
" public static final int LENGTH_LONG = 0;\n" +
" public void setDuration(@Duration int duration) {\n" +
" }\n" +
"\n" +
" public void test() {\n" +
" setDuration(/*Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was 500)*/UNRELATED/**/); /// ERROR: Not right intdef, even if it's in the right number range\n" +
" setDuration(/*Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was -5)*/-5/**/); // ERROR (not right int def or value\n" +
" setDuration(/*Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was 8)*/8/**/); // ERROR (not matching number range)\n" +
" setDuration(8000); // OK (@IntRange applies)\n" +
" setDuration(LENGTH_INDEFINITE); // OK (@IntDef)\n" +
" setDuration(LENGTH_LONG); // OK (@IntDef)\n" +
" setDuration(LENGTH_SHORT); // OK (@IntDef)\n" +
" }\n" +
"}\n");
}
@Override
protected String[] getEnvironmentClasses() {
@Language("JAVA")
String header = "package android.support.annotation;\n" +
"\n" +
"import java.lang.annotation.Documented;\n" +
"import java.lang.annotation.Retention;\n" +
"import java.lang.annotation.Target;\n" +
"\n" +
"import static java.lang.annotation.ElementType.ANNOTATION_TYPE;\n" +
"import static java.lang.annotation.ElementType.CONSTRUCTOR;\n" +
"import static java.lang.annotation.ElementType.FIELD;\n" +
"import static java.lang.annotation.ElementType.LOCAL_VARIABLE;\n" +
"import static java.lang.annotation.ElementType.METHOD;\n" +
"import static java.lang.annotation.ElementType.PARAMETER;\n" +
"import static java.lang.annotation.ElementType.TYPE;\n" +
"import static java.lang.annotation.RetentionPolicy.SOURCE;\n" +
"import static java.lang.annotation.RetentionPolicy.CLASS;\n" +
"\n";
List<String> classes = Lists.newArrayList();
@Language("JAVA")
String floatRange = "@Retention(CLASS)\n" +
"@Target({CONSTRUCTOR,METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})\n" +
"public @interface FloatRange {\n" +
" double from() default Double.NEGATIVE_INFINITY;\n" +
" double to() default Double.POSITIVE_INFINITY;\n" +
" boolean fromInclusive() default true;\n" +
" boolean toInclusive() default true;\n" +
"}";
classes.add(header + floatRange);
@Language("JAVA")
String intRange = "@Retention(CLASS)\n" +
"@Target({CONSTRUCTOR,METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})\n" +
"public @interface IntRange {\n" +
" long from() default Long.MIN_VALUE;\n" +
" long to() default Long.MAX_VALUE;\n" +
"}";
classes.add(header + intRange);
@Language("JAVA")
String size = "@Retention(CLASS)\n" +
"@Target({PARAMETER, LOCAL_VARIABLE, METHOD, FIELD})\n" +
"public @interface Size {\n" +
" long value() default -1;\n" +
" long min() default Long.MIN_VALUE;\n" +
" long max() default Long.MAX_VALUE;\n" +
" long multiple() default 1;\n" +
"}";
classes.add(header + size);
@Language("JAVA")
String permission = "@Retention(SOURCE)\n" +
"@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD})\n" +
"public @interface RequiresPermission {\n" +
" String value() default \"\";\n" +
" String[] allOf() default {};\n" +
" String[] anyOf() default {};\n" +
" boolean conditional() default false;\n" +
" @Target(FIELD)\n" +
" @interface Read {\n" +
" RequiresPermission value();\n" +
" }\n" +
" @Target(FIELD)\n" +
" @interface Write {\n" +
" RequiresPermission value();\n" +
" }\n" +
"}\n";
classes.add(header + permission);
@Language("JAVA")
String uiThread = "@Retention(SOURCE)\n" +
"@Target({METHOD,CONSTRUCTOR,TYPE})\n" +
"public @interface UiThread {\n" +
"}";
classes.add(header + uiThread);
@Language("JAVA")
String mainThread = "@Retention(SOURCE)\n" +
"@Target({METHOD,CONSTRUCTOR,TYPE})\n" +
"public @interface MainThread {\n" +
"}";
classes.add(header + mainThread);
@Language("JAVA")
String workerThread = "@Retention(SOURCE)\n" +
"@Target({METHOD,CONSTRUCTOR,TYPE})\n" +
"public @interface WorkerThread {\n" +
"}";
classes.add(header + workerThread);
@Language("JAVA")
String binderThread = "@Retention(SOURCE)\n" +
"@Target({METHOD,CONSTRUCTOR,TYPE})\n" +
"public @interface BinderThread {\n" +
"}";
classes.add(header + binderThread);
@Language("JAVA")
String colorInt = "@Retention(SOURCE)\n" +
"@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n" +
"public @interface ColorInt {\n" +
"}";
classes.add(header + colorInt);
@Language("JAVA")
String intDef = "@Retention(SOURCE)\n" +
"@Target({ANNOTATION_TYPE})\n" +
"public @interface IntDef {\n" +
" long[] value() default {};\n" +
" boolean flag() default false;\n" +
"}\n";
classes.add(header + intDef);
for (ResourceType type : ResourceType.values()) {
if (type == ResourceType.FRACTION || type == ResourceType.PUBLIC) {
continue;
}
@Language("JAVA")
String resourceTypeAnnotation = "@Documented\n" +
"@Retention(SOURCE)\n" +
"@Target({METHOD, PARAMETER, FIELD})\n" +
"public @interface " + StringUtil.capitalize(type.getName()) + "Res {\n" +
"}";
classes.add(header + resourceTypeAnnotation);
}
String anyRes = "@Documented\n" +
"@Retention(SOURCE)\n" +
"@Target({METHOD, PARAMETER, FIELD})\n" +
"public @interface AnyRes {\n" +
"}";
classes.add(header + anyRes);
return ArrayUtil.toStringArray(classes);
}
// Like doTest in parent class, but uses <error> instead of <warning>
protected final void doCheck(@Language("JAVA") @NotNull @NonNls String classText) {
@NonNls final StringBuilder newText = new StringBuilder();
int start = 0;
int end = classText.indexOf("/*");
while (end >= 0) {
newText.append(classText, start, end);
start = end + 2;
end = classText.indexOf("*/", end);
if (end < 0) {
throw new IllegalArgumentException("invalid class text");
}
final String warning = classText.substring(start, end);
if (warning.isEmpty()) {
newText.append("</error>");
}
else {
newText.append("<error descr=\"").append(warning).append("\">");
}
start = end + 2;
end = classText.indexOf("/*", end + 1);
}
newText.append(classText, start, classText.length());
// Now delegate to the real test implementation (it won't find comments to replace with <warning>)
super.doTest(newText.toString());
}
protected void checkQuickFix(@NotNull String quickFixName, @NotNull String expected) {
final IntentionAction quickFix = myFixture.getAvailableIntention(quickFixName);
assertNotNull(quickFix);
myFixture.launchAction(quickFix);
myFixture.checkResult(expected);
}
@Nullable
@Override
protected InspectionProfileEntry getInspection() {
return new ResourceTypeInspection();
}
}