Build against Android plugin 3.0
diff --git a/idea/idea-android/build.gradle.kts b/idea/idea-android/build.gradle.kts index 7c4c469..ae803b8 100644 --- a/idea/idea-android/build.gradle.kts +++ b/idea/idea-android/build.gradle.kts
@@ -14,7 +14,7 @@ compile(ideaSdkDeps("openapi", "idea")) compile(ideaPluginDeps("gradle-api", plugin = "gradle")) - compile(ideaPluginDeps("android", "android-common", "sdklib", "sdk-common", "sdk-tools", "layoutlib-api", plugin = "android")) + compile(ideaPluginDeps("android", "android-common", "android-base-common", "sdklib", "sdk-common", "sdk-tools", "layoutlib-api", plugin = "android")) compile(preloadedDeps("dx", subdir = "android-5.0/lib")) testCompile(projectDist(":kotlin-test:kotlin-test-jvm"))
diff --git a/idea/idea-android/idea-android-output-parser/build.gradle.kts b/idea/idea-android/idea-android-output-parser/build.gradle.kts index 571930d..9e341a0 100644 --- a/idea/idea-android/idea-android-output-parser/build.gradle.kts +++ b/idea/idea-android/idea-android-output-parser/build.gradle.kts
@@ -5,7 +5,7 @@ compile(project(":compiler:util")) compile(ideaSdkCoreDeps("intellij-core")) compile(ideaPluginDeps("gradle-api", plugin = "gradle")) - compile(ideaPluginDeps("android", "android-common", "sdk-common", plugin = "android")) + compile(ideaPluginDeps("android", "android-common", "android-base-common", "sdk-common", plugin = "android")) } sourceSets {
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/KotlinAndroidLineMarkerProvider.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/KotlinAndroidLineMarkerProvider.kt index fcbc1a1..09d7d37 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/KotlinAndroidLineMarkerProvider.kt +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/KotlinAndroidLineMarkerProvider.kt
@@ -34,6 +34,7 @@ import com.intellij.util.Function import org.jetbrains.android.dom.manifest.Manifest import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.android.resourceManagers.ModuleResourceManagers import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor import org.jetbrains.kotlin.psi.* @@ -95,7 +96,8 @@ return } - val files = androidFacet + val files = ModuleResourceManagers + .getInstance(androidFacet) .localResourceManager .findResourcesByFieldName(resClassName, info.fieldName) .filterIsInstance<PsiFile>()
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/ResourceReferenceAnnotatorUtil.java b/idea/idea-android/src/org/jetbrains/kotlin/android/ResourceReferenceAnnotatorUtil.java index 6e1d66d..aa05125 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/ResourceReferenceAnnotatorUtil.java +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/ResourceReferenceAnnotatorUtil.java
@@ -23,8 +23,8 @@ import com.android.ide.common.resources.ResourceResolver; import com.android.resources.ResourceType; import com.android.tools.idea.configurations.Configuration; +import com.android.tools.idea.configurations.ConfigurationManager; import com.android.tools.idea.res.AppResourceRepository; -import com.android.tools.idea.res.LocalResourceRepository; import com.android.tools.idea.res.ResourceHelper; import com.android.tools.idea.ui.resourcechooser.ColorPicker; import com.android.utils.XmlUtils; @@ -61,15 +61,14 @@ import java.io.File; import static com.android.SdkConstants.*; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_DRAWABLE; -import static com.android.tools.idea.uibuilder.property.renderer.NlDefaultRenderer.ICON_SIZE; -import static org.jetbrains.android.AndroidColorAnnotator.pickLayoutFile; /** * Contains copied privates from AndroidColorAnnotator, so we could use them for Kotlin AndroidResourceReferenceAnnotator */ public class ResourceReferenceAnnotatorUtil { + + public static final int ICON_SIZE = 8; + @Nullable public static File pickBitmapFromXml(@NotNull File file, @NotNull ResourceResolver resourceResolver, @NotNull Project project) { try { @@ -146,7 +145,7 @@ ResourceItem item = frameworkResources.getResourceItem(type, name); return item.getResourceValue(type, configuration.getFullConfig(), false); } else { - LocalResourceRepository appResources = AppResourceRepository.getAppResources(module, true); + AppResourceRepository appResources = AppResourceRepository.getOrCreateInstance(module); if (appResources == null) { return null; } @@ -161,26 +160,27 @@ @Nullable public static Configuration pickConfiguration(AndroidFacet facet, Module module, PsiFile file) { VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { + if(virtualFile == null) { return null; - } - - VirtualFile parent = virtualFile.getParent(); - if (parent == null) { - return null; - } - VirtualFile layout; - String parentName = parent.getName(); - if (!parentName.startsWith(FD_RES_LAYOUT)) { - layout = pickLayoutFile(module, facet); - if (layout == null) { - return null; - } } else { - layout = virtualFile; - } + VirtualFile parent = virtualFile.getParent(); + if(parent == null) { + return null; + } else { + String parentName = parent.getName(); + VirtualFile layout; + if(!parentName.startsWith("layout")) { + layout = ResourceHelper.pickAnyLayoutFile(module, facet); + if(layout == null) { + return null; + } + } else { + layout = virtualFile; + } - return facet.getConfigurationManager().getConfiguration(layout); + return ConfigurationManager.getOrCreateInstance(module).getConfiguration(layout); + } + } } public static class ColorRenderer extends GutterIconRenderer {
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt index 0cafa35..40c99f8 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt
@@ -22,6 +22,7 @@ import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.android.sdk.AndroidSdkData import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.AndroidDexer import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad import java.io.File @@ -56,7 +57,7 @@ private fun doGetAndroidDexFile(): File? { for (module in ModuleManager.getInstance(project).modules) { val androidFacet = AndroidFacet.getInstance(module) ?: continue - val sdkData = androidFacet.sdkData ?: continue + val sdkData = AndroidSdkData.getSdkData(androidFacet) ?: continue val latestBuildTool = sdkData.getLatestBuildTool(/* allowPreview = */ false) ?: sdkData.getLatestBuildTool(/* allowPreview = */ true) ?: continue
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/folding/ResourceFoldingBuilder.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/folding/ResourceFoldingBuilder.kt index 8d4992a..30255ec 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/folding/ResourceFoldingBuilder.kt +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/folding/ResourceFoldingBuilder.kt
@@ -47,7 +47,6 @@ // See lint's StringFormatDetector private val FORMAT = Pattern.compile("%(\\d+\\$)?([-+#, 0(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])") private val FOLD_MAX_LENGTH = 60 - private val FORCE_PROJECT_RESOURCE_LOADING = true private val UNIT_TEST_MODE: Boolean = ApplicationManager.getApplication().isUnitTestMode private val RESOURCE_TYPES = listOf(ResourceType.STRING, ResourceType.DIMEN, @@ -254,6 +253,6 @@ } private fun getAppResources(element: PsiElement): LocalResourceRepository? = ModuleUtilCore.findModuleForPsiElement(element)?.let { - AppResourceRepository.getAppResources(it, FORCE_PROJECT_RESOURCE_LOADING) + AppResourceRepository.findExistingInstance(it) } }
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/TypeParameterFindViewByIdInspection.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/TypeParameterFindViewByIdInspection.kt index 19f058a..ec26b3c 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/TypeParameterFindViewByIdInspection.kt +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/inspection/TypeParameterFindViewByIdInspection.kt
@@ -16,6 +16,7 @@ package org.jetbrains.kotlin.android.inspection +import com.android.tools.idea.model.AndroidModuleInfo import com.intellij.codeInspection.* import com.intellij.openapi.project.Project import com.intellij.psi.PsiElementVisitor @@ -29,7 +30,11 @@ class TypeParameterFindViewByIdInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { - val compileSdk = AndroidFacet.getInstance(session.file)?.androidModuleInfo?.buildSdkVersion?.apiLevel + val compileSdk = AndroidFacet.getInstance(session.file) + ?.let { facet -> AndroidModuleInfo.getInstance(facet) } + ?.buildSdkVersion + ?.apiLevel + if (compileSdk == null || compileSdk < 26) { return KtVisitorVoid() }
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java b/idea/idea-android/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java index 0410dfb1..89243e8 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/navigation/KotlinAndroidGotoDeclarationHandler.java
@@ -29,6 +29,7 @@ import org.jetbrains.android.dom.resources.DeclareStyleable; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.resourceManagers.LocalResourceManager; +import org.jetbrains.android.resourceManagers.ModuleResourceManagers; import org.jetbrains.android.resourceManagers.ResourceManager; import org.jetbrains.android.util.AndroidResourceUtil; import org.jetbrains.android.util.AndroidUtils; @@ -68,9 +69,10 @@ collectManifestElements(nestedClassName, fieldName, facet, resourceList); } else { + ModuleResourceManagers managers = ModuleResourceManagers.getInstance(facet); ResourceManager manager = info.isSystem() - ? facet.getSystemResourceManager(false) - : facet.getLocalResourceManager(); + ? managers.getSystemResourceManager(false) + : managers.getLocalResourceManager(); if (manager == null) { return null; }
diff --git a/idea/src/META-INF/android-lint.xml b/idea/src/META-INF/android-lint.xml index dd09960..aba27ac 100644 --- a/idea/src/META-INF/android-lint.xml +++ b/idea/src/META-INF/android-lint.xml
@@ -1,118 +1,9 @@ <idea-plugin> <extensions defaultExtensionNs="com.intellij"> - <externalAnnotator language="kotlin" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintExternalAnnotator"/> + <externalAnnotator language="kotlin" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintExternalAnnotator"/> + </extensions> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintAddJavascriptInterface" displayName="addJavascriptInterface Called" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAddJavascriptInterfaceInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintAllowAllHostnameVerifier" displayName="Insecure HostnameVerifier" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAllowAllHostnameVerifierInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintAlwaysShowAction" displayName="Usage of showAsAction=always" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAlwaysShowActionInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintAppCompatMethod" displayName="Using Wrong AppCompat Method" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAppCompatMethodInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintAuthLeak" displayName="Code contains url auth" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintAuthLeakInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintBadHostnameVerifier" displayName="Insecure HostnameVerifier" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintBadHostnameVerifierInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintBatteryLife" displayName="Battery Life Issues" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintBatteryLifeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintCommitPrefEdits" displayName="Missing commit() on SharedPreference editor" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitPrefEditsInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintCommitTransaction" displayName="Missing commit() calls" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitTransactionInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintCustomViewStyleable" displayName="Mismatched Styleable/Custom View Name" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCustomViewStyleableInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintCutPasteId" displayName="Likely cut & paste mistakes" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCutPasteIdInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintDefaultLocale" displayName="Implied default locale in case conversion" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintDefaultLocaleInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintDrawAllocation" displayName="Memory allocations within drawing code" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintDrawAllocationInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintEasterEgg" displayName="Code contains easter egg" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="false" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintEasterEggInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintExportedContentProvider" displayName="Content provider does not require permission" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintExportedContentProviderInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintExportedPreferenceActivity" displayName="PreferenceActivity should not be exported" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintExportedPreferenceActivityInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintExportedReceiver" displayName="Receiver does not require permission" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintExportedReceiverInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintExportedService" displayName="Exported service does not require permission" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintExportedServiceInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintFloatMath" displayName="Using FloatMath instead of Math" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintFloatMathInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintGetInstance" displayName="Cipher.getInstance with ECB" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGetInstanceInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintGifUsage" displayName="Using .gif format for bitmaps is discouraged" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGifUsageInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintGoogleAppIndexingApiWarning" displayName="Missing support for Google App Indexing Api" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="false" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGoogleAppIndexingApiWarningInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintGoogleAppIndexingUrlError" displayName="URL not supported by app for Google App Indexing" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGoogleAppIndexingUrlErrorInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintGoogleAppIndexingWarning" displayName="Missing support for Google App Indexing" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGoogleAppIndexingWarningInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintGrantAllUris" displayName="Content provider shares everything" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintGrantAllUrisInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintHandlerLeak" displayName="Handler reference leaks" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintHandlerLeakInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconColors" displayName="Icon colors do not follow the recommended visual style" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconColorsInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconDensities" displayName="Icon densities validation" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconDensitiesInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconDipSize" displayName="Icon density-independent size validation" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconDipSizeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconDuplicates" displayName="Duplicated icons under different names" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconDuplicatesInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconDuplicatesConfig" displayName="Identical bitmaps across various configurations" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconDuplicatesConfigInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconExpectedSize" displayName="Icon has incorrect size" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="false" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconExpectedSizeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconExtension" displayName="Icon format does not match the file extension" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconExtensionInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconLauncherShape" displayName="The launcher icon shape should use a distinct silhouette" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconLauncherShapeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconLocation" displayName="Image defined in density-independent drawable folder" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconLocationInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconMissingDensityFolder" displayName="Missing density folder" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconMissingDensityFolderInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconMixedNinePatch" displayName="Clashing PNG and 9-PNG files" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconMixedNinePatchInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconNoDpi" displayName="Icon appears in both -nodpi and dpi folders" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconNoDpiInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintIconXmlAndPng" displayName="Icon is specified both as .xml file and as a bitmap" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintIconXmlAndPngInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintInconsistentLayout" displayName="Inconsistent Layouts" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInconsistentLayoutInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintInflateParams" displayName="Layout Inflation without a Parent" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInflateParamsInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintInlinedApi" displayName="Using inlined constants on older versions" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintInvalidUsesTagAttribute" displayName="Invalid name attribute for uses element." groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInvalidUsesTagAttributeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintJavascriptInterface" displayName="Missing @JavascriptInterface on methods" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintJavascriptInterfaceInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintLocalSuppress" displayName="@SuppressLint on invalid element" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLocalSuppressInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintLogConditional" displayName="Unconditional Logging Calls" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="false" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogConditionalInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintLogTagMismatch" displayName="Mismatched Log Tags" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogTagMismatchInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintLongLogTag" displayName="Too Long Log Tags" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLongLogTagInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintMergeRootFrame" displayName="FrameLayout can be replaced with <merge> tag" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMergeRootFrameInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintMissingIntentFilterForMediaSearch" displayName="Missing intent-filter with action android.media.action.MEDIA_PLAY_FROM_SEARCH" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMissingIntentFilterForMediaSearchInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintMissingMediaBrowserServiceIntentFilter" displayName="Missing intent-filter with action android.media.browse.MediaBrowserService." groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMissingMediaBrowserServiceIntentFilterInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintMissingOnPlayFromSearch" displayName="Missing onPlayFromSearch." groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMissingOnPlayFromSearchInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintMissingSuperCall" displayName="Missing Super Call" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMissingSuperCallInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintNewApi" displayName="Calling new methods on older versions" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintOverdraw" displayName="Overdraw: Painting regions more than once" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverdrawInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintOverride" displayName="Method conflicts with new inherited method" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintOverrideAbstract" displayName="Not overriding abstract methods on older platforms" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideAbstractInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintPackageManagerGetSignatures" displayName="Potential Multiple Certificate Exploit" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintPackageManagerGetSignaturesInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintParcelClassLoader" displayName="Default Parcel Class Loader" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelClassLoaderInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintParcelCreator" displayName="Missing Parcelable CREATOR field" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintPendingBindings" displayName="Missing Pending Bindings" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintPendingBindingsInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintPluralsCandidate" displayName="Potential Plurals" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintPluralsCandidateInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintPrivateResource" displayName="Using private resources" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintPrivateResourceInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRecycle" displayName="Missing recycle() calls" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRecycleInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRecyclerView" displayName="RecyclerView Problems" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRecyclerViewInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRegistered" displayName="Class is not registered in the manifest" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRegisteredInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRequiredSize" displayName="Missing layout_width or layout_height attributes" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRequiredSizeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRtlCompat" displayName="Right-to-left text compatibility issues" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRtlCompatInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRtlEnabled" displayName="Using RTL attributes without enabling RTL support" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRtlEnabledInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRtlHardcoded" displayName="Using left/right instead of start/end attributes" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRtlHardcodedInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintRtlSymmetry" displayName="Padding and margin symmetry" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRtlSymmetryInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSdCardPath" displayName="Hardcoded reference to /sdcard" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSecureRandom" displayName="Using a fixed seed with SecureRandom" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSecureRandomInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintServiceCast" displayName="Wrong system service casts" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintServiceCastInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSetJavaScriptEnabled" displayName="Using setJavaScriptEnabled" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSetJavaScriptEnabledInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSetTextI18n" displayName="TextView Internationalization" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSetTextI18nInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSetWorldReadable" displayName="File.setReadable() used to make file world-readable" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSetWorldReadableInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSetWorldWritable" displayName="File.setWritable() used to make file world-writable" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSetWorldWritableInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintShiftFlags" displayName="Dangerous Flag Constant Declaration" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintShiftFlagsInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintShortAlarm" displayName="Short or Frequent Alarm" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintShortAlarmInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintShowToast" displayName="Toast created but not shown" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintShowToastInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSimpleDateFormat" displayName="Implied locale in date format" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSimpleDateFormatInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSQLiteString" displayName="Using STRING instead of TEXT" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSQLiteStringInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSSLCertificateSocketFactoryCreateSocket" displayName="Insecure call to SSLCertificateSocketFactory.createSocket()" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSSLCertificateSocketFactoryCreateSocketInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSSLCertificateSocketFactoryGetInsecure" displayName="Call to SSLCertificateSocketFactory.getInsecure()" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSSLCertificateSocketFactoryGetInsecureInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintStopShip" displayName="Code contains STOPSHIP marker" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="false" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintStopShipInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintStringFormatCount" displayName="Formatting argument types incomplete or inconsistent" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintStringFormatCountInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintStringFormatInvalid" displayName="Invalid format string" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintStringFormatInvalidInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintStringFormatMatches" displayName="String.format string doesn't match the XML format string" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintStringFormatMatchesInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSupportAnnotationUsage" displayName="Incorrect support annotation usage" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSupportAnnotationUsageInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSuspiciousImport" displayName="'import android.R' statement" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSuspiciousImportInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintSwitchIntDef" displayName="Missing @IntDef in Switch" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSwitchIntDefInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintTrustAllX509TrustManager" displayName="Insecure TLS/SSL trust manager" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintTrustAllX509TrustManagerInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUniqueConstants" displayName="Overlapping Enumeration Constants" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUniqueConstantsInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUnlocalizedSms" displayName="SMS phone number missing country code" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnlocalizedSmsInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUnprotectedSMSBroadcastReceiver" displayName="Unprotected SMS BroadcastReceiver" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnprotectedSMSBroadcastReceiverInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUnsafeDynamicallyLoadedCode" displayName="load used to dynamically load code" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnsafeDynamicallyLoadedCodeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUnsafeNativeCodeLocation" displayName="Native code outside library directory" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnsafeNativeCodeLocationInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUnsafeProtectedBroadcastReceiver" displayName="Unsafe Protected BroadcastReceiver" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnsafeProtectedBroadcastReceiverInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUnusedAttribute" displayName="Attribute unused on older versions" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnusedAttributeInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUseSparseArrays" displayName="HashMap can be replaced with SparseArray" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseSparseArraysInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintUseValueOf" displayName="Should use valueOf instead of new" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseValueOfInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintValidFragment" displayName="Fragment not instantiatable" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintValidFragmentInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintViewConstructor" displayName="Missing View constructors for XML inflation" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintViewConstructorInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintViewHolder" displayName="View Holder Candidates" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintViewHolderInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintViewTag" displayName="Tagged object leaks" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintViewTagInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintWorldReadableFiles" displayName="openFileOutput() or similar call passing MODE_WORLD_READABLE" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintWorldReadableFilesInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintWorldWriteableFiles" displayName="openFileOutput() or similar call passing MODE_WORLD_WRITEABLE" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintWorldWriteableFilesInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintWrongCall" displayName="Using wrong draw/layout method" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintWrongCallInspection"/> - <globalInspection language="kotlin" hasStaticDescription="true" shortName="AndroidKLintWrongViewCast" displayName="Mismatched view type" groupKey="android.klint.inspections.group.name" bundle="org.jetbrains.kotlin.idea.KotlinBundle" enabledByDefault="true" level="ERROR" implementationClass="org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintWrongViewCastInspection"/> - - <codeInspection.InspectionExtension implementation="org.jetbrains.android.inspections.klint.AndroidInspectionExtensionsFactory"/> + <extensions defaultExtensionNs="org.jetbrains.uast"> + <uastLanguagePlugin implementation="org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin"/> </extensions> </idea-plugin> \ No newline at end of file
diff --git a/plugins/android-extensions/android-extensions-idea/build.gradle.kts b/plugins/android-extensions/android-extensions-idea/build.gradle.kts index a465986..b01956d 100644 --- a/plugins/android-extensions/android-extensions-idea/build.gradle.kts +++ b/plugins/android-extensions/android-extensions-idea/build.gradle.kts
@@ -12,7 +12,7 @@ compile(project(":idea")) compile(project(":idea:idea-gradle")) compile(project(":plugins:android-extensions-compiler")) - compile(ideaPluginDeps("android", "android-common", "sdk-tools", "sdk-common", plugin = "android")) + compile(ideaPluginDeps("android", "android-common", "android-base-common", "sdk-tools", "sdk-common", plugin = "android")) compile(ideaPluginDeps("Groovy", plugin = "Groovy")) compileOnly(project(":kotlin-android-extensions-runtime"))
diff --git a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/res/IDEAndroidLayoutXmlFileManager.kt b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/res/IDEAndroidLayoutXmlFileManager.kt index ab6ebb5..adad6eb 100644 --- a/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/res/IDEAndroidLayoutXmlFileManager.kt +++ b/plugins/android-extensions/android-extensions-idea/src/org/jetbrains/kotlin/android/synthetic/idea/res/IDEAndroidLayoutXmlFileManager.kt
@@ -140,18 +140,7 @@ private fun getAndroidModuleInfoExperimental(androidFacet: AndroidFacet): AndroidModule? { val applicationPackage = androidFacet.manifest?.`package`?.toString() ?: return null - val allResDirectories = androidFacet.getAppResources(true)?.resourceDirs.orEmpty().mapNotNull { it.canonicalPath } - - val resDirectoriesForMainVariant = androidFacet.run { - val resDirsFromSourceProviders = AndroidModuleModel.get(this.module)?.allSourceProviders.orEmpty() - .filter { it.name != "main" } - .flatMap { it.resDirectories } - .map { it.canonicalPath } - - allResDirectories - resDirsFromSourceProviders - } - - val variants = mutableListOf(AndroidVariant("main", resDirectoriesForMainVariant)) + val variants = mutableListOf(androidFacet.mainSourceProvider.toVariant()) AndroidModuleModel.get(androidFacet.module)?.let { androidGradleModel -> androidGradleModel.activeSourceProviders.filter { it.name != "main" }.forEach { sourceProvider ->
diff --git a/plugins/lint/android-annotations/src/com/android/annotations/NonNull.java b/plugins/lint/android-annotations/src/com/android/annotations/NonNull.java deleted file mode 100644 index d16451b..0000000 --- a/plugins/lint/android-annotations/src/com/android/annotations/NonNull.java +++ /dev/null
@@ -1,35 +0,0 @@ -/* - * Copyright (C) 2011 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.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.*; - -/** - * Denotes that a parameter, field or method return value can never be null. - * <p/> - * This is a marker annotation and it has no specific attributes. - */ -@Documented -@Retention(RetentionPolicy.CLASS) -@Target({METHOD,PARAMETER,LOCAL_VARIABLE,FIELD}) -public @interface NonNull { -}
diff --git a/plugins/lint/android-annotations/src/com/android/annotations/NonNullByDefault.java b/plugins/lint/android-annotations/src/com/android/annotations/NonNullByDefault.java deleted file mode 100644 index 9ce54d8..0000000 --- a/plugins/lint/android-annotations/src/com/android/annotations/NonNullByDefault.java +++ /dev/null
@@ -1,47 +0,0 @@ -/* - * Copyright (C) 2011 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.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.PACKAGE; -import static java.lang.annotation.ElementType.TYPE; - -/** - * Denotes that all parameters, fields or methods within a class or method by - * default can not be null. This can be overridden by adding specific - * {@link com.android.annotations.Nullable} annotations on fields, parameters or - * methods that should not use the default. - * <p/> - * NOTE: Eclipse does not yet handle defaults well (in particular, if - * you add this on a class which implements Comparable, then it will insist - * that your compare method is changing the nullness of the compare parameter, - * so you'll need to add @Nullable on it, which also is not right (since - * the method should have implied @NonNull and you do not need to check - * the parameter.). For now, it's best to individually annotate methods, - * parameters and fields. - * <p/> - * This is a marker annotation and it has no specific attributes. - */ -@Documented -@Retention(RetentionPolicy.CLASS) -@Target({PACKAGE, TYPE}) -public @interface NonNullByDefault { -}
diff --git a/plugins/lint/android-annotations/src/com/android/annotations/Nullable.java b/plugins/lint/android-annotations/src/com/android/annotations/Nullable.java deleted file mode 100755 index a0377cb..0000000 --- a/plugins/lint/android-annotations/src/com/android/annotations/Nullable.java +++ /dev/null
@@ -1,46 +0,0 @@ -/* - * Copyright (C) 2011 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.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.*; - -/** - * Denotes that a parameter, field or method return value can be null. - * <b>Note</b>: this is the default assumption for most Java APIs and the - * default assumption made by most static code checking tools, so usually you - * don't need to use this annotation; its primary use is to override a default - * wider annotation like {@link NonNullByDefault}. - * <p/> - * When decorating a method call parameter, this denotes the parameter can - * legitimately be null and the method will gracefully deal with it. Typically - * used on optional parameters. - * <p/> - * When decorating a method, this denotes the method might legitimately return - * null. - * <p/> - * This is a marker annotation and it has no specific attributes. - */ -@Documented -@Retention(RetentionPolicy.CLASS) -@Target({METHOD, PARAMETER, LOCAL_VARIABLE, FIELD}) -public @interface Nullable { -}
diff --git a/plugins/lint/android-annotations/src/com/android/annotations/VisibleForTesting.java b/plugins/lint/android-annotations/src/com/android/annotations/VisibleForTesting.java deleted file mode 100755 index 7f41d70..0000000 --- a/plugins/lint/android-annotations/src/com/android/annotations/VisibleForTesting.java +++ /dev/null
@@ -1,50 +0,0 @@ -/* - * Copyright (C) 2011 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.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Denotes that the class, method or field has its visibility relaxed so - * that unit tests can access it. - * <p/> - * The <code>visibility</code> argument can be used to specific what the original - * visibility should have been if it had not been made public or package-private for testing. - * The default is to consider the element private. - */ -@Retention(RetentionPolicy.SOURCE) -public @interface VisibleForTesting { - /** - * Intended visibility if the element had not been made public or package-private for - * testing. - */ - enum Visibility { - /** The element should be considered protected. */ - PROTECTED, - /** The element should be considered package-private. */ - PACKAGE, - /** The element should be considered private. */ - PRIVATE - } - - /** - * Intended visibility if the element had not been made public or package-private for testing. - * If not specified, one should assume the element originally intended to be private. - */ - Visibility visibility() default Visibility.PRIVATE; -}
diff --git a/plugins/lint/android-annotations/src/com/android/annotations/concurrency/GuardedBy.java b/plugins/lint/android-annotations/src/com/android/annotations/concurrency/GuardedBy.java deleted file mode 100644 index 5a151ac..0000000 --- a/plugins/lint/android-annotations/src/com/android/annotations/concurrency/GuardedBy.java +++ /dev/null
@@ -1,30 +0,0 @@ -/* - * 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.annotations.concurrency; - -import java.lang.annotation.*; - -/** - * Indicates that the target field or method should only be accessed - * with the specified lock being held. - */ -@Documented -@Retention(RetentionPolicy.CLASS) -@Target({ElementType.METHOD, ElementType.FIELD}) -public @interface GuardedBy { - String value(); -}
diff --git a/plugins/lint/android-annotations/src/com/android/annotations/concurrency/Immutable.java b/plugins/lint/android-annotations/src/com/android/annotations/concurrency/Immutable.java deleted file mode 100644 index c83f9f0..0000000 --- a/plugins/lint/android-annotations/src/com/android/annotations/concurrency/Immutable.java +++ /dev/null
@@ -1,29 +0,0 @@ -/* - * 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.annotations.concurrency; - -import java.lang.annotation.*; - -/** - * Indicates that the target class to which this annotation is applied - * is immutable. - */ -@Documented -@Retention(RetentionPolicy.CLASS) -@Target(ElementType.TYPE) -public @interface Immutable { -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AndroidReference.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AndroidReference.java deleted file mode 100644 index db26881..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AndroidReference.java +++ /dev/null
@@ -1,65 +0,0 @@ -/* - * 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.klint.client.api; - -import static com.android.SdkConstants.ANDROID_PKG; - -import com.android.annotations.NonNull; -import com.android.resources.ResourceType; - -import org.jetbrains.uast.UExpression; - -public class AndroidReference { - public final UExpression node; - - private final String rPackage; - - private final ResourceType type; - - private final String name; - - // getPackage() can be empty if not a package-qualified import (e.g. android.R.id.name). - @NonNull - public String getPackage() { - return rPackage; - } - - @NonNull - public ResourceType getType() { - return type; - } - - @NonNull - public String getName() { - return name; - } - - boolean isFramework() { - return rPackage.equals(ANDROID_PKG); - } - - public AndroidReference( - UExpression node, - String rPackage, - ResourceType type, - String name) { - this.node = node; - this.rPackage = rPackage; - this.type = type; - this.name = name; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AsmVisitor.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AsmVisitor.java deleted file mode 100644 index 9c8c861..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AsmVisitor.java +++ /dev/null
@@ -1,202 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.tools.klint.detector.api.ClassContext; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.ClassScanner; -import com.google.common.annotations.Beta; - -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.jetbrains.org.objectweb.asm.tree.InsnList; -import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; -import org.jetbrains.org.objectweb.asm.tree.MethodNode; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Specialized visitor for running detectors on a class object model. - * <p> - * It operates in two phases: - * <ol> - * <li> First, it computes a set of maps where it generates a map from each - * significant method name to a list of detectors to consult for that method - * name. The set of method names that a detector is interested in is provided - * by the detectors themselves. - * <li> Second, it iterates over the DOM a single time. For each method call it finds, - * it dispatches to any check that has registered interest in that method name. - * <li> Finally, it runs a full check on those class scanners that do not register - * specific method names to be checked. This is intended for those detectors - * that do custom work, not related specifically to method calls. - * </ol> - * It also notifies all the detectors before and after the document is processed - * such that they can do pre- and post-processing. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -class AsmVisitor { - /** - * Number of distinct node types specified in {@link AbstractInsnNode}. Sadly - * there isn't a max-constant there, so update this along with ASM library - * updates. - */ - private static final int TYPE_COUNT = AbstractInsnNode.LINE + 1; - private final Map<String, List<ClassScanner>> mMethodNameToChecks = - new HashMap<String, List<ClassScanner>>(); - private final Map<String, List<ClassScanner>> mMethodOwnerToChecks = - new HashMap<String, List<ClassScanner>>(); - private final List<Detector> mFullClassChecks = new ArrayList<Detector>(); - - private final List<? extends Detector> mAllDetectors; - private List<ClassScanner>[] mNodeTypeDetectors; - - // Really want this: - //<T extends List<Detector> & Detector.ClassScanner> ClassVisitor(T xmlDetectors) { - // but it makes client code tricky and ugly. - @SuppressWarnings("unchecked") - AsmVisitor(@NonNull LintClient client, @NonNull List<? extends Detector> classDetectors) { - mAllDetectors = classDetectors; - - // TODO: Check appliesTo() for files, and find a quick way to enable/disable - // rules when running through a full project! - for (Detector detector : classDetectors) { - Detector.ClassScanner scanner = (Detector.ClassScanner) detector; - - boolean checkFullClass = true; - - Collection<String> names = scanner.getApplicableCallNames(); - if (names != null) { - checkFullClass = false; - for (String element : names) { - List<Detector.ClassScanner> list = mMethodNameToChecks.get(element); - if (list == null) { - list = new ArrayList<Detector.ClassScanner>(); - mMethodNameToChecks.put(element, list); - } - list.add(scanner); - } - } - - Collection<String> owners = scanner.getApplicableCallOwners(); - if (owners != null) { - checkFullClass = false; - for (String element : owners) { - List<Detector.ClassScanner> list = mMethodOwnerToChecks.get(element); - if (list == null) { - list = new ArrayList<Detector.ClassScanner>(); - mMethodOwnerToChecks.put(element, list); - } - list.add(scanner); - } - } - - int[] types = scanner.getApplicableAsmNodeTypes(); - if (types != null) { - checkFullClass = false; - for (int type : types) { - if (type < 0 || type >= TYPE_COUNT) { - // Can't support this node type: looks like ASM wasn't updated correctly. - client.log(null, "Out of range node type %1$d from detector %2$s", - type, scanner); - continue; - } - if (mNodeTypeDetectors == null) { - mNodeTypeDetectors = new List[TYPE_COUNT]; - } - List<ClassScanner> checks = mNodeTypeDetectors[type]; - if (checks == null) { - checks = new ArrayList<ClassScanner>(); - mNodeTypeDetectors[type] = checks; - } - checks.add(scanner); - } - } - - if (checkFullClass) { - mFullClassChecks.add(detector); - } - } - } - - @SuppressWarnings("rawtypes") // ASM API uses raw types - void runClassDetectors(ClassContext context) { - ClassNode classNode = context.getClassNode(); - - for (Detector detector : mAllDetectors) { - detector.beforeCheckFile(context); - } - - for (Detector detector : mFullClassChecks) { - Detector.ClassScanner scanner = (Detector.ClassScanner) detector; - scanner.checkClass(context, classNode); - detector.afterCheckFile(context); - } - - if (!mMethodNameToChecks.isEmpty() || !mMethodOwnerToChecks.isEmpty() || - mNodeTypeDetectors != null && mNodeTypeDetectors.length > 0) { - List methodList = classNode.methods; - for (Object m : methodList) { - MethodNode method = (MethodNode) m; - InsnList nodes = method.instructions; - for (int i = 0, n = nodes.size(); i < n; i++) { - AbstractInsnNode instruction = nodes.get(i); - int type = instruction.getType(); - if (type == AbstractInsnNode.METHOD_INSN) { - MethodInsnNode call = (MethodInsnNode) instruction; - - String owner = call.owner; - List<ClassScanner> scanners = mMethodOwnerToChecks.get(owner); - if (scanners != null) { - for (ClassScanner scanner : scanners) { - scanner.checkCall(context, classNode, method, call); - } - } - - String name = call.name; - scanners = mMethodNameToChecks.get(name); - if (scanners != null) { - for (ClassScanner scanner : scanners) { - scanner.checkCall(context, classNode, method, call); - } - } - } - - if (mNodeTypeDetectors != null && type < mNodeTypeDetectors.length) { - List<ClassScanner> scanners = mNodeTypeDetectors[type]; - if (scanners != null) { - for (ClassScanner scanner : scanners) { - scanner.checkInstruction(context, classNode, method, instruction); - } - } - } - } - } - } - - for (Detector detector : mAllDetectors) { - detector.afterCheckFile(context); - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CircularDependencyException.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CircularDependencyException.java deleted file mode 100644 index 94020e0..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CircularDependencyException.java +++ /dev/null
@@ -1,81 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.google.common.annotations.Beta; - -/** - * Exception thrown when there is a circular dependency, such as a circular dependency - * of library mProject references - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class CircularDependencyException extends RuntimeException { - @Nullable - private Project mProject; - - @Nullable - private Location mLocation; - - CircularDependencyException(@NonNull String message) { - super(message); - } - - /** - * Returns the associated project, if any - * - * @return the associated project, if any - */ - @Nullable - public Project getProject() { - return mProject; - } - - /** - * Sets the associated project, if any - * - * @param project the associated project, if any - */ - public void setProject(@Nullable Project project) { - mProject = project; - } - - /** - * Returns the associated location, if any - * - * @return the associated location, if any - */ - @Nullable - public Location getLocation() { - return mLocation; - } - - /** - * Sets the associated location, if any - * - * @param location the associated location, if any - */ - public void setLocation(@Nullable Location location) { - mLocation = location; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ClassEntry.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ClassEntry.java deleted file mode 100644 index 88fb792..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ClassEntry.java +++ /dev/null
@@ -1,335 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.client.api; - -import static com.android.SdkConstants.DOT_CLASS; -import static com.android.SdkConstants.DOT_JAR; -import static org.jetbrains.org.objectweb.asm.Opcodes.ASM5; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.google.common.collect.Maps; -import com.google.common.io.ByteStreams; -import com.google.common.io.Closeables; - -import org.jetbrains.org.objectweb.asm.ClassReader; -import org.jetbrains.org.objectweb.asm.ClassVisitor; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -/** A class, present either as a .class file on disk, or inside a .jar file. */ -@VisibleForTesting -class ClassEntry implements Comparable<ClassEntry> { - public final File file; - public final File jarFile; - public final File binDir; - public final byte[] bytes; - - @VisibleForTesting - ClassEntry( - @NonNull File file, - @Nullable File jarFile, - @NonNull File binDir, - @NonNull byte[] bytes) { - super(); - this.file = file; - this.jarFile = jarFile; - this.binDir = binDir; - this.bytes = bytes; - } - - @NonNull - public String path() { - if (jarFile != null) { - return jarFile.getPath() + ':' + file.getPath(); - } else { - return file.getPath(); - } - } - - @Override - public int compareTo(@NonNull ClassEntry other) { - String p1 = file.getPath(); - String p2 = other.file.getPath(); - int m1 = p1.length(); - int m2 = p2.length(); - if (m1 == m2 && p1.equals(p2)) { - return 0; - } - int m = Math.min(m1, m2); - - for (int i = 0; i < m; i++) { - char c1 = p1.charAt(i); - char c2 = p2.charAt(i); - if (c1 != c2) { - // Sort Foo$Bar.class *after* Foo.class, even though $ < . - if (c1 == '.' && c2 == '$') { - return -1; - } - if (c1 == '$' && c2 == '.') { - return 1; - } - return c1 - c2; - } - } - - return (m == m1) ? -1 : 1; - } - - @Override - public String toString() { - return file.getPath(); - } - - /** - * Creates a list of class entries from the given class path. - * - * @param client the client to report errors to and to use to read files - * @param classPath the class path (directories and jar files) to scan - * @param sort if true, sort the results - * @return the list of class entries, never null. - */ - @NonNull - public static List<ClassEntry> fromClassPath( - @NonNull LintClient client, - @NonNull List<File> classPath, - boolean sort) { - if (!classPath.isEmpty()) { - List<ClassEntry> libraryEntries = new ArrayList<ClassEntry>(64); - addEntries(client, libraryEntries, classPath); - if (sort) { - Collections.sort(libraryEntries); - } - return libraryEntries; - } else { - return Collections.emptyList(); - } - } - - /** - * Creates a list of class entries from the given class path and specific set of - * files within it. - * - * @param client the client to report errors to and to use to read files - * @param classFiles the specific set of class files to look for - * @param classFolders the list of class folders to look in (to determine the - * package root) - * @param sort if true, sort the results - * @return the list of class entries, never null. - */ - @NonNull - public static List<ClassEntry> fromClassFiles( - @NonNull LintClient client, - @NonNull List<File> classFiles, @NonNull List<File> classFolders, - boolean sort) { - List<ClassEntry> entries = new ArrayList<ClassEntry>(classFiles.size()); - - if (!classFolders.isEmpty()) { - for (File file : classFiles) { - String path = file.getPath(); - if (file.isFile() && path.endsWith(DOT_CLASS)) { - try { - byte[] bytes = client.readBytes(file); - for (File dir : classFolders) { - if (path.startsWith(dir.getPath())) { - entries.add(new ClassEntry(file, null /* jarFile*/, dir, - bytes)); - break; - } - } - } catch (IOException e) { - client.log(e, null); - } - } - } - - if (sort && !entries.isEmpty()) { - Collections.sort(entries); - } - } - - return entries; - } - - /** - * Given a classpath, add all the class files found within the directories and inside jar files - */ - private static void addEntries( - @NonNull LintClient client, - @NonNull List<ClassEntry> entries, - @NonNull List<File> classPath) { - for (File classPathEntry : classPath) { - if (classPathEntry.getName().endsWith(DOT_JAR)) { - //noinspection UnnecessaryLocalVariable - File jarFile = classPathEntry; - if (!jarFile.exists()) { - continue; - } - ZipInputStream zis = null; - try { - FileInputStream fis = new FileInputStream(jarFile); - try { - zis = new ZipInputStream(fis); - ZipEntry entry = zis.getNextEntry(); - while (entry != null) { - String name = entry.getName(); - if (name.endsWith(DOT_CLASS)) { - try { - byte[] bytes = ByteStreams.toByteArray(zis); - if (bytes != null) { - File file = new File(entry.getName()); - entries.add(new ClassEntry(file, jarFile, jarFile, bytes)); - } - } catch (Exception e) { - client.log(e, null); - continue; - } - } - - entry = zis.getNextEntry(); - } - } finally { - Closeables.close(fis, true); - } - } catch (IOException e) { - client.log(e, "Could not read jar file contents from %1$s", jarFile); - } finally { - try { - Closeables.close(zis, true); - } catch (IOException e) { - // cannot happen - } - } - } else if (classPathEntry.isDirectory()) { - //noinspection UnnecessaryLocalVariable - File binDir = classPathEntry; - List<File> classFiles = new ArrayList<File>(); - addClassFiles(binDir, classFiles); - - for (File file : classFiles) { - try { - byte[] bytes = client.readBytes(file); - entries.add(new ClassEntry(file, null /* jarFile*/, binDir, bytes)); - } catch (IOException e) { - client.log(e, null); - } - } - } else { - client.log(null, "Ignoring class path entry %1$s", classPathEntry); - } - } - } - - /** Adds in all the .class files found recursively in the given directory */ - private static void addClassFiles(@NonNull File dir, @NonNull List<File> classFiles) { - // Process the resource folder - File[] files = dir.listFiles(); - if (files != null && files.length > 0) { - for (File file : files) { - if (file.isFile() && file.getName().endsWith(DOT_CLASS)) { - classFiles.add(file); - } else if (file.isDirectory()) { - // Recurse - addClassFiles(file, classFiles); - } - } - } - } - - /** - * Creates a super class map (from class to its super class) for the given set of entries - * - * @param client the client to report errors to and to use to access files - * @param libraryEntries the set of library entries to consult - * @param classEntries the set of class entries to consult - * @return a map from name to super class internal names - */ - @NonNull - public static Map<String, String> createSuperClassMap( - @NonNull LintClient client, - @NonNull List<ClassEntry> libraryEntries, - @NonNull List<ClassEntry> classEntries) { - int size = libraryEntries.size() + classEntries.size(); - Map<String, String> map = Maps.newHashMapWithExpectedSize(size); - SuperclassVisitor visitor = new SuperclassVisitor(map); - addSuperClasses(client, visitor, libraryEntries); - addSuperClasses(client, visitor, classEntries); - return map; - } - - /** - * Creates a super class map (from class to its super class) for the given set of entries - * - * @param client the client to report errors to and to use to access files - * @param entries the set of library entries to consult - * @return a map from name to super class internal names - */ - @NonNull - public static Map<String, String> createSuperClassMap( - @NonNull LintClient client, - @NonNull List<ClassEntry> entries) { - Map<String, String> map = Maps.newHashMapWithExpectedSize(entries.size()); - SuperclassVisitor visitor = new SuperclassVisitor(map); - addSuperClasses(client, visitor, entries); - return map; - } - - /** Adds in all the super classes found for the given class entries into the given map */ - private static void addSuperClasses( - @NonNull LintClient client, - @NonNull SuperclassVisitor visitor, - @NonNull List<ClassEntry> entries) { - for (ClassEntry entry : entries) { - try { - ClassReader reader = new ClassReader(entry.bytes); - int flags = ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG - | ClassReader.SKIP_FRAMES; - reader.accept(visitor, flags); - } catch (Throwable t) { - client.log(null, "Error processing %1$s: broken class file?", entry.path()); - } - } - } - - /** Visitor skimming classes and initializing a map of super classes */ - private static class SuperclassVisitor extends ClassVisitor { - private final Map<String, String> mMap; - - public SuperclassVisitor(Map<String, String> map) { - super(ASM5); - mMap = map; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, - String[] interfaces) { - // Record super class in the map (but don't waste space on java.lang.Object) - if (superName != null && !"java/lang/Object".equals(superName)) { - mMap.put(name, superName); - } - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CompositeIssueRegistry.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CompositeIssueRegistry.java deleted file mode 100644 index b66a8418..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CompositeIssueRegistry.java +++ /dev/null
@@ -1,52 +0,0 @@ -/* - * 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.tools.klint.detector.api.Issue; -import com.google.common.collect.Lists; - -import java.util.List; - -/** - * Registry which merges many issue registries into one, and presents a unified list - * of issues. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -class CompositeIssueRegistry extends IssueRegistry { - private final List<IssueRegistry> myRegistries; - private List<Issue> myIssues; - - public CompositeIssueRegistry(@NonNull List<IssueRegistry> registries) { - myRegistries = registries; - } - - @NonNull - @Override - public List<Issue> getIssues() { - if (myIssues == null) { - List<Issue> issues = Lists.newArrayListWithExpectedSize(200); - for (IssueRegistry registry : myRegistries) { - issues.addAll(registry.getIssues()); - } - myIssues = issues; - } - - return myIssues; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/Configuration.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/Configuration.java deleted file mode 100644 index 5952b10..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/Configuration.java +++ /dev/null
@@ -1,124 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Severity; -import com.google.common.annotations.Beta; - -/** - * Lint configuration for an Android project such as which specific rules to include, - * which specific rules to exclude, and which specific errors to ignore. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class Configuration { - /** - * Checks whether this issue should be ignored because the user has already - * suppressed the error? Note that this refers to individual issues being - * suppressed/ignored, not a whole detector being disabled via something - * like {@link #isEnabled(Issue)}. - * - * @param context the context used by the detector when the issue was found - * @param issue the issue that was found - * @param location the location of the issue - * @param message the associated user message - * @return true if this issue should be suppressed - */ - public boolean isIgnored( - @NonNull Context context, - @NonNull Issue issue, - @Nullable Location location, - @NonNull String message) { - return false; - } - - /** - * Returns false if the given issue has been disabled. This is just - * a convenience method for {@code getSeverity(issue) != Severity.IGNORE}. - * - * @param issue the issue to check - * @return false if the issue has been disabled - */ - public boolean isEnabled(@NonNull Issue issue) { - return getSeverity(issue) != Severity.IGNORE; - } - - /** - * Returns the severity for a given issue. This is the same as the - * {@link Issue#getDefaultSeverity()} unless the user has selected a custom - * severity (which is tool context dependent). - * - * @param issue the issue to look up the severity from - * @return the severity use for issues for the given detector - */ - public Severity getSeverity(@NonNull Issue issue) { - return issue.getDefaultSeverity(); - } - - // Editing configurations - - /** - * Marks the given warning as "ignored". - * - * @param context The scanning context - * @param issue the issue to be ignored - * @param location The location to ignore the warning at, if any - * @param message The message for the warning - */ - public abstract void ignore( - @NonNull Context context, - @NonNull Issue issue, - @Nullable Location location, - @NonNull String message); - - /** - * Sets the severity to be used for this issue. - * - * @param issue the issue to set the severity for - * @param severity the severity to associate with this issue, or null to - * reset the severity to the default - */ - public abstract void setSeverity(@NonNull Issue issue, @Nullable Severity severity); - - // Bulk editing support - - /** - * Marks the beginning of a "bulk" editing operation with repeated calls to - * {@link #setSeverity} or {@link #ignore}. After all the values have been - * set, the client <b>must</b> call {@link #finishBulkEditing()}. This - * allows configurations to avoid doing expensive I/O (such as writing out a - * config XML file) for each and every editing operation when they are - * applied in bulk, such as from a configuration dialog's "Apply" action. - */ - public void startBulkEditing() { - } - - /** - * Marks the end of a "bulk" editing operation, where values should be - * committed to persistent storage. See {@link #startBulkEditing()} for - * details. - */ - public void finishBulkEditing() { - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultConfiguration.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultConfiguration.java deleted file mode 100644 index eb2d9f0..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultConfiguration.java +++ /dev/null
@@ -1,608 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import static com.android.SdkConstants.CURRENT_PLATFORM; -import static com.android.SdkConstants.PLATFORM_WINDOWS; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TextFormat; -import com.android.utils.XmlUtils; -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXParseException; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * Default implementation of a {@link Configuration} which reads and writes - * configuration data into {@code lint.xml} in the project directory. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class DefaultConfiguration extends Configuration { - private final LintClient mClient; - /** Default name of the configuration file */ - public static final String CONFIG_FILE_NAME = "lint.xml"; //$NON-NLS-1$ - - // Lint XML File - @NonNull - private static final String TAG_ISSUE = "issue"; //$NON-NLS-1$ - @NonNull - private static final String ATTR_ID = "id"; //$NON-NLS-1$ - @NonNull - private static final String ATTR_SEVERITY = "severity"; //$NON-NLS-1$ - @NonNull - private static final String ATTR_PATH = "path"; //$NON-NLS-1$ - @NonNull - private static final String ATTR_REGEXP = "regexp"; //$NON-NLS-1$ - @NonNull - private static final String TAG_IGNORE = "ignore"; //$NON-NLS-1$ - @NonNull - private static final String VALUE_ALL = "all"; //$NON-NLS-1$ - - private final Configuration mParent; - private final Project mProject; - private final File mConfigFile; - private boolean mBulkEditing; - - /** Map from id to list of project-relative paths for suppressed warnings */ - private Map<String, List<String>> mSuppressed; - - /** Map from id to regular expressions. */ - @Nullable - private Map<String, List<Pattern>> mRegexps; - - /** - * Map from id to custom {@link Severity} override - */ - private Map<String, Severity> mSeverity; - - protected DefaultConfiguration( - @NonNull LintClient client, - @Nullable Project project, - @Nullable Configuration parent, - @NonNull File configFile) { - mClient = client; - mProject = project; - mParent = parent; - mConfigFile = configFile; - } - - protected DefaultConfiguration( - @NonNull LintClient client, - @NonNull Project project, - @Nullable Configuration parent) { - this(client, project, parent, new File(project.getDir(), CONFIG_FILE_NAME)); - } - - /** - * Creates a new {@link DefaultConfiguration} - * - * @param client the client to report errors to etc - * @param project the associated project - * @param parent the parent/fallback configuration or null - * @return a new configuration - */ - @NonNull - public static DefaultConfiguration create( - @NonNull LintClient client, - @NonNull Project project, - @Nullable Configuration parent) { - return new DefaultConfiguration(client, project, parent); - } - - /** - * Creates a new {@link DefaultConfiguration} for the given lint config - * file, not affiliated with a project. This is used for global - * configurations. - * - * @param client the client to report errors to etc - * @param lintFile the lint file containing the configuration - * @return a new configuration - */ - @NonNull - public static DefaultConfiguration create(@NonNull LintClient client, @NonNull File lintFile) { - return new DefaultConfiguration(client, null /*project*/, null /*parent*/, lintFile); - } - - @Override - public boolean isIgnored( - @NonNull Context context, - @NonNull Issue issue, - @Nullable Location location, - @NonNull String message) { - ensureInitialized(); - - String id = issue.getId(); - List<String> paths = mSuppressed.get(id); - if (paths == null) { - paths = mSuppressed.get(VALUE_ALL); - } - if (paths != null && location != null) { - File file = location.getFile(); - String relativePath = context.getProject().getRelativePath(file); - for (String suppressedPath : paths) { - if (suppressedPath.equals(relativePath)) { - return true; - } - // Also allow a prefix - if (relativePath.startsWith(suppressedPath)) { - return true; - } - } - } - - if (mRegexps != null) { - List<Pattern> regexps = mRegexps.get(id); - if (regexps == null) { - regexps = mRegexps.get(VALUE_ALL); - } - if (regexps != null && location != null) { - // Check message - for (Pattern regexp : regexps) { - Matcher matcher = regexp.matcher(message); - if (matcher.find()) { - return true; - } - } - - // Check location - File file = location.getFile(); - String relativePath = context.getProject().getRelativePath(file); - boolean checkUnixPath = false; - for (Pattern regexp : regexps) { - Matcher matcher = regexp.matcher(relativePath); - if (matcher.find()) { - return true; - } else if (regexp.pattern().indexOf('/') != -1) { - checkUnixPath = true; - } - } - - if (checkUnixPath && CURRENT_PLATFORM == PLATFORM_WINDOWS) { - relativePath = relativePath.replace('\\', '/'); - for (Pattern regexp : regexps) { - Matcher matcher = regexp.matcher(relativePath); - if (matcher.find()) { - return true; - } - } - } - } - } - - return mParent != null && mParent.isIgnored(context, issue, location, message); - } - - @NonNull - protected Severity getDefaultSeverity(@NonNull Issue issue) { - if (!issue.isEnabledByDefault()) { - return Severity.IGNORE; - } - - return issue.getDefaultSeverity(); - } - - @Override - @NonNull - public Severity getSeverity(@NonNull Issue issue) { - ensureInitialized(); - - Severity severity = mSeverity.get(issue.getId()); - if (severity == null) { - severity = mSeverity.get(VALUE_ALL); - } - - if (severity != null) { - return severity; - } - - if (mParent != null) { - return mParent.getSeverity(issue); - } - - return getDefaultSeverity(issue); - } - - private void ensureInitialized() { - if (mSuppressed == null) { - readConfig(); - } - } - - private void formatError(String message, Object... args) { - if (args != null && args.length > 0) { - message = String.format(message, args); - } - message = "Failed to parse `lint.xml` configuration file: " + message; - LintDriver driver = new LintDriver(new IssueRegistry() { - @Override @NonNull public List<Issue> getIssues() { - return Collections.emptyList(); - } - }, mClient); - mClient.report(new Context(driver, mProject, mProject, mConfigFile), - IssueRegistry.LINT_ERROR, - mProject.getConfiguration(driver).getSeverity(IssueRegistry.LINT_ERROR), - Location.create(mConfigFile), message, TextFormat.RAW); - } - - private void readConfig() { - mSuppressed = new HashMap<String, List<String>>(); - mSeverity = new HashMap<String, Severity>(); - - if (!mConfigFile.exists()) { - return; - } - - try { - Document document = XmlUtils.parseUtfXmlFile(mConfigFile, false); - NodeList issues = document.getElementsByTagName(TAG_ISSUE); - Splitter splitter = Splitter.on(',').trimResults().omitEmptyStrings(); - for (int i = 0, count = issues.getLength(); i < count; i++) { - Node node = issues.item(i); - Element element = (Element) node; - String idList = element.getAttribute(ATTR_ID); - if (idList.isEmpty()) { - formatError("Invalid lint config file: Missing required issue id attribute"); - continue; - } - Iterable<String> ids = splitter.split(idList); - - NamedNodeMap attributes = node.getAttributes(); - for (int j = 0, n = attributes.getLength(); j < n; j++) { - Node attribute = attributes.item(j); - String name = attribute.getNodeName(); - String value = attribute.getNodeValue(); - if (ATTR_ID.equals(name)) { - // already handled - } else if (ATTR_SEVERITY.equals(name)) { - for (Severity severity : Severity.values()) { - if (value.equalsIgnoreCase(severity.name())) { - for (String id : ids) { - mSeverity.put(id, severity); - } - break; - } - } - } else { - formatError("Unexpected attribute \"%1$s\"", name); - } - } - - // Look up ignored errors - NodeList childNodes = element.getChildNodes(); - if (childNodes.getLength() > 0) { - for (int j = 0, n = childNodes.getLength(); j < n; j++) { - Node child = childNodes.item(j); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element ignore = (Element) child; - String path = ignore.getAttribute(ATTR_PATH); - if (path.isEmpty()) { - String regexp = ignore.getAttribute(ATTR_REGEXP); - if (regexp.isEmpty()) { - formatError("Missing required attribute %1$s or %2$s under %3$s", - ATTR_PATH, ATTR_REGEXP, idList); - } else { - addRegexp(idList, ids, n, regexp, false); - } - } else { - // Normalize path format to File.separator. Also - // handle the file format containing / or \. - if (File.separatorChar == '/') { - path = path.replace('\\', '/'); - } else { - path = path.replace('/', File.separatorChar); - } - - if (path.indexOf('*') != -1) { - String regexp = globToRegexp(path); - addRegexp(idList, ids, n, regexp, false); - } else { - for (String id : ids) { - List<String> paths = mSuppressed.get(id); - if (paths == null) { - paths = new ArrayList<String>(n / 2 + 1); - mSuppressed.put(id, paths); - } - paths.add(path); - } - } - } - } - } - } - } - } catch (SAXParseException e) { - formatError(e.getMessage()); - } catch (Exception e) { - mClient.log(e, null); - } - } - - @NonNull - public static String globToRegexp(@NonNull String glob) { - StringBuilder sb = new StringBuilder(glob.length() * 2); - int begin = 0; - sb.append('^'); - for (int i = 0, n = glob.length(); i < n; i++) { - char c = glob.charAt(i); - if (c == '*') { - begin = appendQuoted(sb, glob, begin, i) + 1; - if (i < n - 1 && glob.charAt(i + 1) == '*') { - i++; - begin++; - } - sb.append(".*?"); - } else if (c == '?') { - begin = appendQuoted(sb, glob, begin, i) + 1; - sb.append(".?"); - } - } - appendQuoted(sb, glob, begin, glob.length()); - sb.append('$'); - return sb.toString(); - } - - private static int appendQuoted(StringBuilder sb, String s, int from, int to) { - if (to > from) { - boolean isSimple = true; - for (int i = from; i < to; i++) { - char c = s.charAt(i); - if (!Character.isLetterOrDigit(c) && c != '/' && c != ' ') { - isSimple = false; - break; - } - } - if (isSimple) { - for (int i = from; i < to; i++) { - sb.append(s.charAt(i)); - } - return to; - } - sb.append(Pattern.quote(s.substring(from, to))); - } - return to; - } - - private void addRegexp(@NonNull String idList, @NonNull Iterable<String> ids, int n, - @NonNull String regexp, boolean silent) { - try { - if (mRegexps == null) { - mRegexps = new HashMap<String, List<Pattern>>(); - } - Pattern pattern = Pattern.compile(regexp); - for (String id : ids) { - List<Pattern> paths = mRegexps.get(id); - if (paths == null) { - paths = new ArrayList<Pattern>(n / 2 + 1); - mRegexps.put(id, paths); - } - paths.add(pattern); - } - } catch (PatternSyntaxException e) { - if (!silent) { - formatError("Invalid pattern %1$s under %2$s: %3$s", - regexp, idList, e.getDescription()); - } - } - } - - private void writeConfig() { - try { - // Write the contents to a new file first such that we don't clobber the - // existing file if some I/O error occurs. - File file = new File(mConfigFile.getParentFile(), - mConfigFile.getName() + ".new"); //$NON-NLS-1$ - - Writer writer = new BufferedWriter(new FileWriter(file)); - writer.write( - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //$NON-NLS-1$ - "<lint>\n"); //$NON-NLS-1$ - - if (!mSuppressed.isEmpty() || !mSeverity.isEmpty()) { - // Process the maps in a stable sorted order such that if the - // files are checked into version control with the project, - // there are no random diffs just because hashing algorithms - // differ: - Set<String> idSet = new HashSet<String>(); - for (String id : mSuppressed.keySet()) { - idSet.add(id); - } - for (String id : mSeverity.keySet()) { - idSet.add(id); - } - List<String> ids = new ArrayList<String>(idSet); - Collections.sort(ids); - - for (String id : ids) { - writer.write(" <"); //$NON-NLS-1$ - writer.write(TAG_ISSUE); - writeAttribute(writer, ATTR_ID, id); - Severity severity = mSeverity.get(id); - if (severity != null) { - writeAttribute(writer, ATTR_SEVERITY, - severity.name().toLowerCase(Locale.US)); - } - - List<Pattern> regexps = mRegexps != null ? mRegexps.get(id) : null; - List<String> paths = mSuppressed.get(id); - if (paths != null && !paths.isEmpty() - || regexps != null && !regexps.isEmpty()) { - writer.write('>'); - writer.write('\n'); - // The paths are already kept in sorted order when they are modified - // by ignore(...) - if (paths != null) { - for (String path : paths) { - writer.write(" <"); //$NON-NLS-1$ - writer.write(TAG_IGNORE); - writeAttribute(writer, ATTR_PATH, path.replace('\\', '/')); - writer.write(" />\n"); //$NON-NLS-1$ - } - } - if (regexps != null) { - for (Pattern regexp : regexps) { - writer.write(" <"); //$NON-NLS-1$ - writer.write(TAG_IGNORE); - writeAttribute(writer, ATTR_REGEXP, regexp.pattern()); - writer.write(" />\n"); //$NON-NLS-1$ - } - } - writer.write(" </"); //$NON-NLS-1$ - writer.write(TAG_ISSUE); - writer.write('>'); - writer.write('\n'); - } else { - writer.write(" />\n"); //$NON-NLS-1$ - } - } - } - - writer.write("</lint>"); //$NON-NLS-1$ - writer.close(); - - // Move file into place: move current version to lint.xml~ (removing the old ~ file - // if it exists), then move the new version to lint.xml. - File oldFile = new File(mConfigFile.getParentFile(), - mConfigFile.getName() + '~'); //$NON-NLS-1$ - if (oldFile.exists()) { - oldFile.delete(); - } - if (mConfigFile.exists()) { - mConfigFile.renameTo(oldFile); - } - boolean ok = file.renameTo(mConfigFile); - if (ok && oldFile.exists()) { - oldFile.delete(); - } - } catch (Exception e) { - mClient.log(e, null); - } - } - - private static void writeAttribute( - @NonNull Writer writer, @NonNull String name, @NonNull String value) - throws IOException { - writer.write(' '); - writer.write(name); - writer.write('='); - writer.write('"'); - writer.write(value); - writer.write('"'); - } - - @Override - public void ignore( - @NonNull Context context, - @NonNull Issue issue, - @Nullable Location location, - @NonNull String message) { - // This configuration only supports suppressing warnings on a per-file basis - if (location != null) { - ignore(issue, location.getFile()); - } - } - - /** - * Marks the given issue and file combination as being ignored. - * - * @param issue the issue to be ignored in the given file - * @param file the file to ignore the issue in - */ - public void ignore(@NonNull Issue issue, @NonNull File file) { - ensureInitialized(); - - String path = mProject != null ? mProject.getRelativePath(file) : file.getPath(); - - List<String> paths = mSuppressed.get(issue.getId()); - if (paths == null) { - paths = new ArrayList<String>(); - mSuppressed.put(issue.getId(), paths); - } - paths.add(path); - - // Keep paths sorted alphabetically; makes XML output stable - Collections.sort(paths); - - if (!mBulkEditing) { - writeConfig(); - } - } - - @Override - public void setSeverity(@NonNull Issue issue, @Nullable Severity severity) { - ensureInitialized(); - - String id = issue.getId(); - if (severity == null) { - mSeverity.remove(id); - } else { - mSeverity.put(id, severity); - } - - if (!mBulkEditing) { - writeConfig(); - } - } - - @Override - public void startBulkEditing() { - mBulkEditing = true; - } - - @Override - public void finishBulkEditing() { - mBulkEditing = false; - writeConfig(); - } - - @VisibleForTesting - File getConfigFile() { - return mConfigFile; - } -} \ No newline at end of file
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultSdkInfo.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultSdkInfo.java deleted file mode 100644 index cc7b157..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultSdkInfo.java +++ /dev/null
@@ -1,288 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import static com.android.SdkConstants.ABSOLUTE_LAYOUT; -import static com.android.SdkConstants.ABS_LIST_VIEW; -import static com.android.SdkConstants.ABS_SEEK_BAR; -import static com.android.SdkConstants.ABS_SPINNER; -import static com.android.SdkConstants.ADAPTER_VIEW; -import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW; -import static com.android.SdkConstants.BUTTON; -import static com.android.SdkConstants.CHECKABLE; -import static com.android.SdkConstants.CHECKED_TEXT_VIEW; -import static com.android.SdkConstants.CHECK_BOX; -import static com.android.SdkConstants.COMPOUND_BUTTON; -import static com.android.SdkConstants.EDIT_TEXT; -import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; -import static com.android.SdkConstants.FRAME_LAYOUT; -import static com.android.SdkConstants.GALLERY; -import static com.android.SdkConstants.GRID_VIEW; -import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW; -import static com.android.SdkConstants.IMAGE_BUTTON; -import static com.android.SdkConstants.IMAGE_VIEW; -import static com.android.SdkConstants.LINEAR_LAYOUT; -import static com.android.SdkConstants.LIST_VIEW; -import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW; -import static com.android.SdkConstants.PROGRESS_BAR; -import static com.android.SdkConstants.RADIO_BUTTON; -import static com.android.SdkConstants.RADIO_GROUP; -import static com.android.SdkConstants.RELATIVE_LAYOUT; -import static com.android.SdkConstants.SCROLL_VIEW; -import static com.android.SdkConstants.SEEK_BAR; -import static com.android.SdkConstants.SPINNER; -import static com.android.SdkConstants.SURFACE_VIEW; -import static com.android.SdkConstants.SWITCH; -import static com.android.SdkConstants.TABLE_LAYOUT; -import static com.android.SdkConstants.TABLE_ROW; -import static com.android.SdkConstants.TAB_HOST; -import static com.android.SdkConstants.TAB_WIDGET; -import static com.android.SdkConstants.TEXT_VIEW; -import static com.android.SdkConstants.TOGGLE_BUTTON; -import static com.android.SdkConstants.VIEW; -import static com.android.SdkConstants.VIEW_ANIMATOR; -import static com.android.SdkConstants.VIEW_GROUP; -import static com.android.SdkConstants.VIEW_PKG_PREFIX; -import static com.android.SdkConstants.VIEW_STUB; -import static com.android.SdkConstants.VIEW_SWITCHER; -import static com.android.SdkConstants.WEB_VIEW; -import static com.android.SdkConstants.WIDGET_PKG_PREFIX; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.google.common.annotations.Beta; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * Default simple implementation of an {@link SdkInfo} - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -class DefaultSdkInfo extends SdkInfo { - @Override - @Nullable - public String getParentViewName(@NonNull String name) { - name = getRawType(name); - - return PARENTS.get(name); - } - - @Override - @Nullable - public String getParentViewClass(@NonNull String fqcn) { - int index = fqcn.lastIndexOf('.'); - if (index != -1) { - fqcn = fqcn.substring(index + 1); - } - - String parent = PARENTS.get(fqcn); - if (parent == null) { - return null; - } - // The map only stores class names internally; correct for full package paths - if (parent.equals(VIEW) || parent.equals(VIEW_GROUP) || parent.equals(SURFACE_VIEW)) { - return VIEW_PKG_PREFIX + parent; - } else { - return WIDGET_PKG_PREFIX + parent; - } - } - - @Override - public boolean isSubViewOf(@NonNull String parentType, @NonNull String childType) { - String parent = getRawType(parentType); - String child = getRawType(childType); - - // Do analysis just on non-fqcn paths - if (parent.indexOf('.') != -1) { - parent = parent.substring(parent.lastIndexOf('.') + 1); - } - if (child.indexOf('.') != -1) { - child = child.substring(child.lastIndexOf('.') + 1); - } - - if (parent.equals(VIEW)) { - return true; - } - - while (!child.equals(VIEW)) { - if (parent.equals(child)) { - return true; - } - if (implementsInterface(child, parent)) { - return true; - } - child = PARENTS.get(child); - if (child == null) { - // Unknown view - err on the side of caution - return true; - } - } - - return false; - } - - private static boolean implementsInterface(String className, String interfaceName) { - return interfaceName.equals(INTERFACES.get(className)); - } - - // Strip off type parameters, e.g. AdapterView<?> ⇒ AdapterView - private static String getRawType(String type) { - if (type != null) { - int index = type.indexOf('<'); - if (index != -1) { - type = type.substring(0, index); - } - } - - return type; - } - - @Override - public boolean isLayout(@NonNull String tag) { - // TODO: Read in widgets.txt from the platform install area to look up this information - // dynamically instead! - - if (super.isLayout(tag)) { - return true; - } - - return LAYOUTS.contains(tag); - } - - private static final int CLASS_COUNT = 59; - private static final int LAYOUT_COUNT = 20; - - private static final Map<String,String> PARENTS = Maps.newHashMapWithExpectedSize(CLASS_COUNT); - private static final Set<String> LAYOUTS = Sets.newHashSetWithExpectedSize(CLASS_COUNT); - - static { - PARENTS.put(COMPOUND_BUTTON, BUTTON); - PARENTS.put(ABS_SPINNER, ADAPTER_VIEW); - PARENTS.put(ABS_LIST_VIEW, ADAPTER_VIEW); - PARENTS.put(ABS_SEEK_BAR, ADAPTER_VIEW); - PARENTS.put(ADAPTER_VIEW, VIEW_GROUP); - PARENTS.put(VIEW_GROUP, VIEW); - - PARENTS.put(TEXT_VIEW, VIEW); - PARENTS.put(CHECKED_TEXT_VIEW, TEXT_VIEW); - PARENTS.put(RADIO_BUTTON, COMPOUND_BUTTON); - PARENTS.put(SPINNER, ABS_SPINNER); - PARENTS.put(IMAGE_BUTTON, IMAGE_VIEW); - PARENTS.put(IMAGE_VIEW, VIEW); - PARENTS.put(EDIT_TEXT, TEXT_VIEW); - PARENTS.put(PROGRESS_BAR, VIEW); - PARENTS.put(TOGGLE_BUTTON, COMPOUND_BUTTON); - PARENTS.put(VIEW_STUB, VIEW); - PARENTS.put(BUTTON, TEXT_VIEW); - PARENTS.put(SEEK_BAR, ABS_SEEK_BAR); - PARENTS.put(CHECK_BOX, COMPOUND_BUTTON); - PARENTS.put(SWITCH, COMPOUND_BUTTON); - PARENTS.put(GALLERY, ABS_SPINNER); - PARENTS.put(SURFACE_VIEW, VIEW); - PARENTS.put(ABSOLUTE_LAYOUT, VIEW_GROUP); - PARENTS.put(LINEAR_LAYOUT, VIEW_GROUP); - PARENTS.put(RELATIVE_LAYOUT, VIEW_GROUP); - PARENTS.put(LIST_VIEW, ABS_LIST_VIEW); - PARENTS.put(VIEW_SWITCHER, VIEW_ANIMATOR); - PARENTS.put(FRAME_LAYOUT, VIEW_GROUP); - PARENTS.put(HORIZONTAL_SCROLL_VIEW, FRAME_LAYOUT); - PARENTS.put(VIEW_ANIMATOR, FRAME_LAYOUT); - PARENTS.put(TAB_HOST, FRAME_LAYOUT); - PARENTS.put(TABLE_ROW, LINEAR_LAYOUT); - PARENTS.put(RADIO_GROUP, LINEAR_LAYOUT); - PARENTS.put(TAB_WIDGET, LINEAR_LAYOUT); - PARENTS.put(EXPANDABLE_LIST_VIEW, LIST_VIEW); - PARENTS.put(TABLE_LAYOUT, LINEAR_LAYOUT); - PARENTS.put(SCROLL_VIEW, FRAME_LAYOUT); - PARENTS.put(GRID_VIEW, ABS_LIST_VIEW); - PARENTS.put(WEB_VIEW, ABSOLUTE_LAYOUT); - PARENTS.put(AUTO_COMPLETE_TEXT_VIEW, EDIT_TEXT); - PARENTS.put(MULTI_AUTO_COMPLETE_TEXT_VIEW, AUTO_COMPLETE_TEXT_VIEW); - PARENTS.put(CHECKED_TEXT_VIEW, TEXT_VIEW); - - PARENTS.put("MediaController", FRAME_LAYOUT); //$NON-NLS-1$ - PARENTS.put("SlidingDrawer", VIEW_GROUP); //$NON-NLS-1$ - PARENTS.put("DialerFilter", RELATIVE_LAYOUT); //$NON-NLS-1$ - PARENTS.put("DigitalClock", TEXT_VIEW); //$NON-NLS-1$ - PARENTS.put("Chronometer", TEXT_VIEW); //$NON-NLS-1$ - PARENTS.put("ImageSwitcher", VIEW_SWITCHER); //$NON-NLS-1$ - PARENTS.put("TextSwitcher", VIEW_SWITCHER); //$NON-NLS-1$ - PARENTS.put("AnalogClock", VIEW); //$NON-NLS-1$ - PARENTS.put("TwoLineListItem", RELATIVE_LAYOUT); //$NON-NLS-1$ - PARENTS.put("ZoomControls", LINEAR_LAYOUT); //$NON-NLS-1$ - PARENTS.put("DatePicker", FRAME_LAYOUT); //$NON-NLS-1$ - PARENTS.put("TimePicker", FRAME_LAYOUT); //$NON-NLS-1$ - PARENTS.put("VideoView", SURFACE_VIEW); //$NON-NLS-1$ - PARENTS.put("ZoomButton", IMAGE_BUTTON); //$NON-NLS-1$ - PARENTS.put("RatingBar", ABS_SEEK_BAR); //$NON-NLS-1$ - PARENTS.put("ViewFlipper", VIEW_ANIMATOR); //$NON-NLS-1$ - PARENTS.put("NumberPicker", LINEAR_LAYOUT); //$NON-NLS-1$ - - assert PARENTS.size() <= CLASS_COUNT : PARENTS.size(); - - /* - // Check that all widgets lead to the root view - if (LintUtils.assertionsEnabled()) { - for (String key : PARENTS.keySet()) { - String parent = PARENTS.get(key); - if (!parent.equals(VIEW)) { - String grandParent = PARENTS.get(parent); - assert grandParent != null : parent; - } - } - } - */ - - LAYOUTS.add(TAB_HOST); - LAYOUTS.add(HORIZONTAL_SCROLL_VIEW); - LAYOUTS.add(VIEW_SWITCHER); - LAYOUTS.add(TAB_WIDGET); - LAYOUTS.add(VIEW_ANIMATOR); - LAYOUTS.add(SCROLL_VIEW); - LAYOUTS.add(GRID_VIEW); - LAYOUTS.add(TABLE_ROW); - LAYOUTS.add(RADIO_GROUP); - LAYOUTS.add(LIST_VIEW); - LAYOUTS.add(EXPANDABLE_LIST_VIEW); - LAYOUTS.add("MediaController"); //$NON-NLS-1$ - LAYOUTS.add("DialerFilter"); //$NON-NLS-1$ - LAYOUTS.add("ViewFlipper"); //$NON-NLS-1$ - LAYOUTS.add("SlidingDrawer"); //$NON-NLS-1$ - LAYOUTS.add("StackView"); //$NON-NLS-1$ - LAYOUTS.add("SearchView"); //$NON-NLS-1$ - LAYOUTS.add("TextSwitcher"); //$NON-NLS-1$ - LAYOUTS.add("AdapterViewFlipper"); //$NON-NLS-1$ - LAYOUTS.add("ImageSwitcher"); //$NON-NLS-1$ - assert LAYOUTS.size() <= LAYOUT_COUNT : LAYOUTS.size(); - } - - // Currently using a map; this should really be a list, but using a map until we actually - // start adding more than one item - @NonNull - private static final Map<String, String> INTERFACES = new HashMap<String, String>(2); - static { - INTERFACES.put(CHECKED_TEXT_VIEW, CHECKABLE); - INTERFACES.put(COMPOUND_BUTTON, CHECKABLE); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ExternalReferenceExpression.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ExternalReferenceExpression.java deleted file mode 100644 index c7e19a6..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ExternalReferenceExpression.java +++ /dev/null
@@ -1,25 +0,0 @@ -/* - * 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.klint.client.api; - -import com.android.annotations.Nullable; -import com.intellij.psi.PsiElement; - -public interface ExternalReferenceExpression { - @Nullable - PsiElement resolve(PsiElement context); -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/IssueRegistry.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/IssueRegistry.java deleted file mode 100644 index bc822ec..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/IssueRegistry.java +++ /dev/null
@@ -1,346 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.google.common.annotations.Beta; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Registry which provides a list of checks to be performed on an Android project - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class IssueRegistry { - private static volatile List<Category> sCategories; - private static volatile Map<String, Issue> sIdToIssue; - private static Map<EnumSet<Scope>, List<Issue>> sScopeIssues = Maps.newHashMap(); - - /** - * Creates a new {@linkplain IssueRegistry} - */ - protected IssueRegistry() { - } - - private static final Implementation DUMMY_IMPLEMENTATION = new Implementation(Detector.class, - EnumSet.noneOf(Scope.class)); - /** - * Issue reported by lint (not a specific detector) when it cannot even - * parse an XML file prior to analysis - */ - @NonNull - public static final Issue PARSER_ERROR = Issue.create( - "ParserError", //$NON-NLS-1$ - "Parser Errors", - "Lint will ignore any files that contain fatal parsing errors. These may contain " + - "other errors, or contain code which affects issues in other files.", - Category.CORRECTNESS, - 10, - Severity.ERROR, - DUMMY_IMPLEMENTATION); - - /** - * Issue reported by lint for various other issues which prevents lint from - * running normally when it's not necessarily an error in the user's code base. - */ - @NonNull - public static final Issue LINT_ERROR = Issue.create( - "LintError", //$NON-NLS-1$ - "Lint Failure", - "This issue type represents a problem running lint itself. Examples include " + - "failure to find bytecode for source files (which means certain detectors " + - "could not be run), parsing errors in lint configuration files, etc." + - "\n" + - "These errors are not errors in your own code, but they are shown to make " + - "it clear that some checks were not completed.", - - Category.LINT, - 10, - Severity.ERROR, - DUMMY_IMPLEMENTATION); - - /** - * Issue reported when lint is canceled - */ - @NonNull - public static final Issue CANCELLED = Issue.create( - "LintCanceled", //$NON-NLS-1$ - "Lint Canceled", - "Lint canceled by user; the issue report may not be complete.", - - Category.LINT, - 0, - Severity.INFORMATIONAL, - DUMMY_IMPLEMENTATION); - - /** - * Returns the list of issues that can be found by all known detectors. - * - * @return the list of issues to be checked (including those that may be - * disabled!) - */ - @NonNull - public abstract List<Issue> getIssues(); - - /** - * Get an approximate issue count for a given scope. This is just an optimization, - * so the number does not have to be accurate. - * - * @param scope the scope set - * @return an approximate ceiling of the number of issues expected for a given scope set - */ - protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) { - return 20; - } - - /** - * Returns all available issues of a given scope (regardless of whether - * they are actually enabled for a given configuration etc) - * - * @param scope the applicable scope set - * @return a list of issues - */ - @NonNull - protected List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) { - List<Issue> list = sScopeIssues.get(scope); - if (list == null) { - List<Issue> issues = getIssues(); - if (scope.equals(Scope.ALL)) { - list = issues; - } else { - list = new ArrayList<Issue>(getIssueCapacity(scope)); - for (Issue issue : issues) { - // Determine if the scope matches - if (issue.getImplementation().isAdequate(scope)) { - list.add(issue); - } - } - } - sScopeIssues.put(scope, list); - } - - return list; - } - - /** - * Creates a list of detectors applicable to the given scope, and with the - * given configuration. - * - * @param client the client to report errors to - * @param configuration the configuration to look up which issues are - * enabled etc from - * @param scope the scope for the analysis, to filter out detectors that - * require wider analysis than is currently being performed - * @param scopeToDetectors an optional map which (if not null) will be - * filled by this method to contain mappings from each scope to - * the applicable detectors for that scope - * @return a list of new detector instances - */ - @NonNull - final List<? extends Detector> createDetectors( - @NonNull LintClient client, - @NonNull Configuration configuration, - @NonNull EnumSet<Scope> scope, - @Nullable Map<Scope, List<Detector>> scopeToDetectors) { - - List<Issue> issues = getIssuesForScope(scope); - if (issues.isEmpty()) { - return Collections.emptyList(); - } - - Set<Class<? extends Detector>> detectorClasses = new HashSet<Class<? extends Detector>>(); - Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope = - new HashMap<Class<? extends Detector>, EnumSet<Scope>>(); - - for (Issue issue : issues) { - Implementation implementation = issue.getImplementation(); - Class<? extends Detector> detectorClass = implementation.getDetectorClass(); - EnumSet<Scope> issueScope = implementation.getScope(); - if (!detectorClasses.contains(detectorClass)) { - // Determine if the issue is enabled - if (!configuration.isEnabled(issue)) { - continue; - } - - assert implementation.isAdequate(scope); // Ensured by getIssuesForScope above - - detectorClass = client.replaceDetector(detectorClass); - - assert detectorClass != null : issue.getId(); - detectorClasses.add(detectorClass); - } - - if (scopeToDetectors != null) { - EnumSet<Scope> s = detectorToScope.get(detectorClass); - if (s == null) { - detectorToScope.put(detectorClass, issueScope); - } else if (!s.containsAll(issueScope)) { - EnumSet<Scope> union = EnumSet.copyOf(s); - union.addAll(issueScope); - detectorToScope.put(detectorClass, union); - } - } - } - - List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size()); - for (Class<? extends Detector> clz : detectorClasses) { - try { - Detector detector = clz.newInstance(); - detectors.add(detector); - - if (scopeToDetectors != null) { - EnumSet<Scope> union = detectorToScope.get(clz); - for (Scope s : union) { - List<Detector> list = scopeToDetectors.get(s); - if (list == null) { - list = new ArrayList<Detector>(); - scopeToDetectors.put(s, list); - } - list.add(detector); - } - - } - } catch (Throwable t) { - client.log(t, "Can't initialize detector %1$s", clz.getName()); //$NON-NLS-1$ - } - } - - return detectors; - } - - /** - * Returns true if the given id represents a valid issue id - * - * @param id the id to be checked - * @return true if the given id is valid - */ - public final boolean isIssueId(@NonNull String id) { - return getIssue(id) != null; - } - - /** - * Returns true if the given category is a valid category - * - * @param name the category name to be checked - * @return true if the given string is a valid category - */ - public final boolean isCategoryName(@NonNull String name) { - for (Category category : getCategories()) { - if (category.getName().equals(name) || category.getFullName().equals(name)) { - return true; - } - } - - return false; - } - - /** - * Returns the available categories - * - * @return an iterator for all the categories, never null - */ - @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod") - @NonNull - public List<Category> getCategories() { - List<Category> categories = sCategories; - if (categories == null) { - synchronized (IssueRegistry.class) { - categories = sCategories; - if (categories == null) { - sCategories = categories = Collections.unmodifiableList(createCategoryList()); - } - } - } - - return categories; - } - - @NonNull - private List<Category> createCategoryList() { - Set<Category> categorySet = Sets.newHashSetWithExpectedSize(20); - for (Issue issue : getIssues()) { - categorySet.add(issue.getCategory()); - } - List<Category> sorted = new ArrayList<Category>(categorySet); - Collections.sort(sorted); - return sorted; - } - - /** - * Returns the issue for the given id, or null if it's not a valid id - * - * @param id the id to be checked - * @return the corresponding issue, or null - */ - @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod") - @Nullable - public final Issue getIssue(@NonNull String id) { - Map<String, Issue> map = sIdToIssue; - if (map == null) { - synchronized (IssueRegistry.class) { - map = sIdToIssue; - if (map == null) { - map = createIdToIssueMap(); - sIdToIssue = map; - } - } - } - - return map.get(id); - } - - @NonNull - private Map<String, Issue> createIdToIssueMap() { - List<Issue> issues = getIssues(); - Map<String, Issue> map = Maps.newHashMapWithExpectedSize(issues.size() + 2); - for (Issue issue : issues) { - map.put(issue.getId(), issue); - } - - map.put(PARSER_ERROR.getId(), PARSER_ERROR); - map.put(LINT_ERROR.getId(), LINT_ERROR); - return map; - } - - /** - * Reset the registry such that it recomputes its available issues. - */ - protected static void reset() { - sIdToIssue = null; - sCategories = null; - sScopeIssues = Maps.newHashMap(); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JarFileIssueRegistry.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JarFileIssueRegistry.java deleted file mode 100644 index 79b4dca..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JarFileIssueRegistry.java +++ /dev/null
@@ -1,236 +0,0 @@ -/* - * 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.klint.client.api; - -import static com.android.SdkConstants.DOT_CLASS; - -import com.android.annotations.NonNull; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.utils.SdkUtils; -import com.google.common.collect.Lists; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.ref.SoftReference; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.JarInputStream; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; - -/** - * <p> An {@link IssueRegistry} for a custom lint rule jar file. The rule jar should provide a - * manifest entry with the key {@code Lint-Registry} and the value of the fully qualified name of an - * implementation of {@link IssueRegistry} (with a default constructor). </p> - * - * <p> NOTE: The custom issue registry should not extend this file; it should be a plain - * IssueRegistry! This file is used internally to wrap the given issue registry.</p> - */ -class JarFileIssueRegistry extends IssueRegistry { - /** - * Manifest constant for declaring an issue provider. Example: Lint-Registry: - * foo.bar.CustomIssueRegistry - */ - private static final String MF_LINT_REGISTRY_OLD = "Lint-Registry"; //$NON-NLS-1$ - private static final String MF_LINT_REGISTRY = "Lint-Registry-v2"; //$NON-NLS-1$ - - private static Map<File, SoftReference<JarFileIssueRegistry>> sCache; - - private final List<Issue> myIssues; - - private boolean mHasLegacyDetectors; - - /** True if one or more java detectors were found that use the old Lombok-based API */ - public boolean hasLegacyDetectors() { - return mHasLegacyDetectors; - } - - @NonNull - static JarFileIssueRegistry get(@NonNull LintClient client, @NonNull File jarFile) - throws IOException, ClassNotFoundException, IllegalAccessException, - InstantiationException { - if (sCache == null) { - sCache = new HashMap<File, SoftReference<JarFileIssueRegistry>>(); - } else { - SoftReference<JarFileIssueRegistry> reference = sCache.get(jarFile); - if (reference != null) { - JarFileIssueRegistry registry = reference.get(); - if (registry != null) { - return registry; - } - } - } - - // Ensure that the scope-to-detector map doesn't return stale results - IssueRegistry.reset(); - - JarFileIssueRegistry registry = new JarFileIssueRegistry(client, jarFile); - sCache.put(jarFile, new SoftReference<JarFileIssueRegistry>(registry)); - return registry; - } - - private JarFileIssueRegistry(@NonNull LintClient client, @NonNull File file) - throws IOException, ClassNotFoundException, IllegalAccessException, - InstantiationException { - myIssues = Lists.newArrayList(); - JarFile jarFile = null; - try { - //noinspection IOResourceOpenedButNotSafelyClosed - jarFile = new JarFile(file); - Manifest manifest = jarFile.getManifest(); - Attributes attrs = manifest.getMainAttributes(); - Object object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY)); - boolean isLegacy = false; - if (object == null) { - object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY_OLD)); - //noinspection VariableNotUsedInsideIf - if (object != null) { - // It's an old rule. We don't yet conclude that - // mHasLegacyDetectors=true - // because the lint checks may not be Java related. - isLegacy = true; - } - } - if (object instanceof String) { - String className = (String) object; - // Make a class loader for this jar - URL url = SdkUtils.fileToUrl(file); - ClassLoader loader = client.createUrlClassLoader(new URL[]{url}, - JarFileIssueRegistry.class.getClassLoader()); - Class<?> registryClass = Class.forName(className, true, loader); - IssueRegistry registry = (IssueRegistry) registryClass.newInstance(); - myIssues.addAll(registry.getIssues()); - - if (isLegacy) { - // If it's an old registry, look through the issues to see if it - // provides Java scanning and if so create the old style visitors - for (Issue issue : myIssues) { - EnumSet<Scope> scope = issue.getImplementation().getScope(); - if (scope.contains(Scope.JAVA_FILE) || scope.contains(Scope.JAVA_LIBRARIES) - || scope.contains(Scope.ALL_JAVA_FILES)) { - mHasLegacyDetectors = true; - break; - } - } - } - - if (loader instanceof URLClassLoader) { - loadAndCloseURLClassLoader(client, file, (URLClassLoader)loader); - } - } else { - client.log(Severity.ERROR, null, - "Custom lint rule jar %1$s does not contain a valid registry manifest key " + - "(%2$s).\n" + - "Either the custom jar is invalid, or it uses an outdated API not supported " + - "this lint client", file.getPath(), MF_LINT_REGISTRY); - } - } finally { - if (jarFile != null) { - jarFile.close(); - } - } - } - - /** - * Work around http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5041014 : - * URLClassLoader, on Windows, locks the .jar file forever. - * As of Java 7, there's a workaround: you can call close() when you're "done" - * with the file. We'll do that here. However, the whole point of the - * {@linkplain JarFileIssueRegistry} is that when lint is run over and over again - * as the user is editing in the IDE and we're background checking the code, we - * don't to keep loading the custom view classes over and over again: we want to - * cache them. Therefore, just closing the URLClassLoader right away isn't great - * either. However, it turns out it's safe to close the URLClassLoader once you've - * loaded the classes you need, since the URLClassLoader will continue to serve - * those classes even after its close() methods has been called. - * <p> - * Therefore, if we can call close() on this URLClassLoader, we'll proactively load - * all class files we find in the .jar file, then close it. - * - * @param client the client to report errors to - * @param file the .jar file - * @param loader the URLClassLoader we should close - */ - private static void loadAndCloseURLClassLoader( - @NonNull LintClient client, - @NonNull File file, - @NonNull URLClassLoader loader) { - try { - // Proactively close out the .jar file. This is only available on Java 7. - Method closeMethod = loader.getClass().getDeclaredMethod("close"); - - // But first, proactively load all classes: - try { - InputStream inputStream = new FileInputStream(file); - try { - JarInputStream jarInputStream = new JarInputStream(inputStream); - try { - ZipEntry entry = jarInputStream.getNextEntry(); - while (entry != null) { - String name = entry.getName(); - // Load non-inner-classes - if (name.endsWith(DOT_CLASS)) { - // Strip .class suffix and change .jar file path (/) - // to class name (.'s). - name = name.substring(0, - name.length() - DOT_CLASS.length()); - name = name.replace('/', '.'); - try { - Class.forName(name, true, loader); - } catch (Throwable e) { - client.log(Severity.ERROR, e, - "Failed to prefetch " + name + " from " + file); - } - } - entry = jarInputStream.getNextEntry(); - } - } finally { - jarInputStream.close(); - } - } finally { - inputStream.close(); - } - } catch (Throwable ignore) { - } finally { - // Finally close the URL class loader - try { - closeMethod.invoke(loader); - } catch (Throwable ignore) { - // Couldn't close. This is unlikely. - } - } - } catch (NoSuchMethodException ignore) { - // No close method - we're on 1.6 - } - } - - @NonNull - @Override - public List<Issue> getIssues() { - return myIssues; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaEvaluator.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaEvaluator.java deleted file mode 100644 index 46eeb1f..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaEvaluator.java +++ /dev/null
@@ -1,244 +0,0 @@ -/* - * 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.ClassContext; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAnonymousClass; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiModifierList; -import com.intellij.psi.PsiModifierListOwner; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiParameterList; -import com.intellij.psi.PsiReference; -import com.intellij.psi.PsiType; -import com.intellij.psi.util.InheritanceUtil; - -import java.io.File; - -@SuppressWarnings("MethodMayBeStatic") // Some of these methods may be overridden by LintClients -public abstract class JavaEvaluator { - public boolean isMemberInSubClassOf( - @NonNull PsiMember method, - @NonNull String className, - boolean strict) { - PsiClass containingClass = method.getContainingClass(); - return containingClass != null && InheritanceUtil.isInheritor(containingClass, strict, className); - } - - public static boolean isMemberInClass( - @Nullable PsiMember method, - @NonNull String className) { - if (method == null) { - return false; - } - PsiClass containingClass = method.getContainingClass(); - return containingClass != null && className.equals(containingClass.getQualifiedName()); - } - - public int getParameterCount(@NonNull PsiMethod method) { - return method.getParameterList().getParametersCount(); - } - - /** - * Returns true if the given method (which is typically looked up by resolving a method call) is - * either a method in the exact given class, or if {@code allowInherit} is true, a method in a - * class possibly extending the given class, and if the parameter types are the exact types - * specified. - * - * @param method the method in question - * @param className the class name the method should be defined in or inherit from (or - * if null, allow any class) - * @param allowInherit whether we allow checking for inheritance - * @param argumentTypes the names of the types of the parameters - * @return true if this method is defined in the given class and with the given parameters - */ - public boolean methodMatches( - @NonNull PsiMethod method, - @Nullable String className, - boolean allowInherit, - @NonNull String... argumentTypes) { - if (className != null && allowInherit) { - if (!isMemberInSubClassOf(method, className, false)) { - return false; - } - } - - return parametersMatch(method, argumentTypes); - } - - /** - * Returns true if the given method's parameters are the exact types specified. - * - * @param method the method in question - * @param argumentTypes the names of the types of the parameters - * @return true if this method is defined in the given class and with the given parameters - */ - public boolean parametersMatch( - @NonNull PsiMethod method, - @NonNull String... argumentTypes) { - PsiParameterList parameterList = method.getParameterList(); - if (parameterList.getParametersCount() != argumentTypes.length) { - return false; - } - PsiParameter[] parameters = parameterList.getParameters(); - for (int i = 0; i < parameters.length; i++) { - PsiType type = parameters[i].getType(); - if (!type.getCanonicalText().equals(argumentTypes[i])) { - return false; - } - } - - return true; - } - - /** Returns true if the given type matches the given fully qualified type name */ - public boolean parameterHasType( - @Nullable PsiMethod method, - int parameterIndex, - @NonNull String typeName) { - if (method == null) { - return false; - } - PsiParameterList parameterList = method.getParameterList(); - return parameterList.getParametersCount() > parameterIndex - && typeMatches(parameterList.getParameters()[parameterIndex].getType(), typeName); - } - - /** Returns true if the given type matches the given fully qualified type name */ - public boolean typeMatches( - @Nullable PsiType type, - @NonNull String typeName) { - return type != null && type.getCanonicalText().equals(typeName); - - } - - @Nullable - public PsiElement resolve(@NonNull PsiElement element) { - if (element instanceof PsiReference) { - return ((PsiReference)element).resolve(); - } else if (element instanceof PsiMethodCallExpression) { - PsiElement resolved = ((PsiMethodCallExpression) element).resolveMethod(); - if (resolved != null) { - return resolved; - } - } - return null; - } - - public boolean isPublic(@Nullable PsiModifierListOwner owner) { - if (owner != null) { - PsiModifierList modifierList = owner.getModifierList(); - return modifierList != null && modifierList.hasModifierProperty(PsiModifier.PUBLIC); - } - return false; - } - - public boolean isStatic(@Nullable PsiModifierListOwner owner) { - if (owner != null) { - PsiModifierList modifierList = owner.getModifierList(); - return modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC); - } - return false; - } - - public boolean isPrivate(@Nullable PsiModifierListOwner owner) { - if (owner != null) { - PsiModifierList modifierList = owner.getModifierList(); - return modifierList != null && modifierList.hasModifierProperty(PsiModifier.PRIVATE); - } - return false; - } - - public boolean isAbstract(@Nullable PsiModifierListOwner owner) { - if (owner != null) { - PsiModifierList modifierList = owner.getModifierList(); - return modifierList != null && modifierList.hasModifierProperty(PsiModifier.ABSTRACT); - } - return false; - } - - public boolean isFinal(@Nullable PsiModifierListOwner owner) { - if (owner != null) { - PsiModifierList modifierList = owner.getModifierList(); - return modifierList != null && modifierList.hasModifierProperty(PsiModifier.FINAL); - } - return false; - } - - @Nullable - public PsiMethod getSuperMethod(@Nullable PsiMethod method) { - if (method == null) { - return null; - } - final PsiMethod[] superMethods = method.findSuperMethods(); - if (superMethods.length > 0) { - return superMethods[0]; - } - return null; - } - - @NonNull - public String getInternalName(@NonNull PsiClass psiClass) { - String qualifiedName = psiClass.getQualifiedName(); - if (qualifiedName == null) { - qualifiedName = psiClass.getName(); - if (qualifiedName == null) { - assert psiClass instanceof PsiAnonymousClass; - //noinspection ConstantConditions - return getInternalName(psiClass.getContainingClass()); - } - } - return ClassContext.getInternalName(qualifiedName); - } - - @NonNull - public String getInternalName(@NonNull PsiClassType psiClassType) { - return ClassContext.getInternalName(psiClassType.getCanonicalText()); - } - - @Nullable - public abstract PsiClass findClass(@NonNull String qualifiedName); - - @Nullable - public abstract PsiClassType getClassType(@Nullable PsiClass psiClass); - - @NonNull - public abstract PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner); - - @Nullable - public abstract PsiAnnotation findAnnotationInHierarchy( - @NonNull PsiModifierListOwner listOwner, - @NonNull String... annotationNames); - - @Nullable - public abstract PsiAnnotation findAnnotation( - @Nullable PsiModifierListOwner listOwner, - @NonNull String... annotationNames); - - @Nullable - public abstract File getFile(@NonNull PsiFile file); -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaParser.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaParser.java deleted file mode 100644 index 9676385..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaParser.java +++ /dev/null
@@ -1,1078 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import static com.android.SdkConstants.ATTR_VALUE; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.ClassContext; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.DefaultPosition; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Position; -import com.google.common.annotations.Beta; -import com.google.common.base.Splitter; -import com.intellij.mock.MockProject; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiComment; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiJavaFile; -import com.intellij.psi.PsiType; - -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UFile; -import org.jetbrains.uast.UastContext; - -import java.io.File; -import java.lang.reflect.Modifier; -import java.util.Collections; -import java.util.List; - -import lombok.ast.Catch; -import lombok.ast.For; -import lombok.ast.Identifier; -import lombok.ast.If; -import lombok.ast.Node; -import lombok.ast.Return; -import lombok.ast.StrictListAccessor; -import lombok.ast.Switch; -import lombok.ast.Throw; -import lombok.ast.TypeReference; -import lombok.ast.TypeReferencePart; -import lombok.ast.While; - -/** - * A wrapper for a Java parser. This allows tools integrating lint to map directly - * to builtin services, such as already-parsed data structures in Java editors. - * <p> - * <b>NOTE: This is not public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -// Currently ships with deprecated API support -@SuppressWarnings({"deprecation", "UnusedParameters"}) -@Beta -public abstract class JavaParser { - public static final String TYPE_OBJECT = "java.lang.Object"; - public static final String TYPE_STRING = "java.lang.String"; - public static final String TYPE_INT = "int"; - public static final String TYPE_LONG = "long"; - public static final String TYPE_CHAR = "char"; - public static final String TYPE_FLOAT = "float"; - public static final String TYPE_DOUBLE = "double"; - public static final String TYPE_BOOLEAN = "boolean"; - public static final String TYPE_SHORT = "short"; - public static final String TYPE_BYTE = "byte"; - public static final String TYPE_NULL = "null"; - public static final String TYPE_INTEGER_WRAPPER = "java.lang.Integer"; - public static final String TYPE_BOOLEAN_WRAPPER = "java.lang.Boolean"; - public static final String TYPE_BYTE_WRAPPER = "java.lang.Byte"; - public static final String TYPE_SHORT_WRAPPER = "java.lang.Short"; - public static final String TYPE_LONG_WRAPPER = "java.lang.Long"; - public static final String TYPE_DOUBLE_WRAPPER = "java.lang.Double"; - public static final String TYPE_FLOAT_WRAPPER = "java.lang.Float"; - public static final String TYPE_CHARACTER_WRAPPER = "java.lang.Character"; - - /** - * Prepare to parse the given contexts. This method will be called before - * a series of {@link #parseJava(JavaContext)} calls, which allows some - * parsers to do up front global computation in case they want to more - * efficiently process multiple files at the same time. This allows a single - * type-attribution pass for example, which is a lot more efficient than - * performing global type analysis over and over again for each individual - * file - * - * @param contexts a list of contexts to be parsed - */ - public abstract void prepareJavaParse(@NonNull List<JavaContext> contexts); - - /** - * Parse the file pointed to by the given context. - * - * @param context the context pointing to the file to be parsed, typically - * via {@link Context#getContents()} but the file handle ( - * {@link Context#file} can also be used to map to an existing - * editor buffer in the surrounding tool, etc) - * @return the compilation unit node for the file - * @deprecated Use {@link #parseJavaToPsi(JavaContext)} instead - */ - @Deprecated - @Nullable - public Node parseJava(@NonNull JavaContext context) { - return null; - } - - /** - * Parse the file pointed to by the given context. - * - * @param context the context pointing to the file to be parsed, typically - * via {@link Context#getContents()} but the file handle ( - * {@link Context#file} can also be used to map to an existing - * editor buffer in the surrounding tool, etc) - * @return the compilation unit node for the file - */ - @Nullable - public abstract PsiJavaFile parseJavaToPsi(@NonNull JavaContext context); - - /** - * Returns an evaluator which can perform various resolution tasks, - * evaluate inheritance lookup etc. - * - * @return an evaluator - */ - @NonNull - public abstract JavaEvaluator getEvaluator(); - - public abstract Project getIdeaProject(); - - public abstract UastContext getUastContext(); - - /** - * Returns a {@link Location} for the given node - * - * @param context information about the file being parsed - * @param node the node to create a location for - * @return a location for the given node - * @deprecated Use {@link #getNameLocation(JavaContext, PsiElement)} instead - */ - @Deprecated - @NonNull - public Location getLocation(@NonNull JavaContext context, @NonNull Node node) { - // No longer mandatory to override for children; this is a deprecated API - return Location.NONE; - } - - /** - * Returns a {@link Location} for the given element - * - * @param context information about the file being parsed - * @param element the element to create a location for - * @return a location for the given node - */ - @SuppressWarnings("MethodMayBeStatic") // subclasses may want to override/optimize - @NonNull - public Location getLocation(@NonNull JavaContext context, @NonNull PsiElement element) { - TextRange range = element.getTextRange(); - UFile uFile = (UFile) getUastContext().convertElementWithParent(element.getContainingFile(), UFile.class); - if (uFile == null) { - return Location.NONE; - } - - PsiFile containingFile = uFile.getPsi(); - File file = context.file; - if (containingFile != context.getUFile().getPsi()) { - // Reporting an error in a different file. - if (context.getDriver().getScope().size() == 1) { - // Don't bother with this error if it's in a different file during single-file analysis - return Location.NONE; - } - VirtualFile virtualFile = containingFile.getVirtualFile(); - if (virtualFile == null) { - return Location.NONE; - } - file = VfsUtilCore.virtualToIoFile(virtualFile); - } - return Location.create(file, context.getContents(), range.getStartOffset(), - range.getEndOffset()); - } - - /** - * Returns a {@link Location} for the given node range (from the starting offset of the first - * node to the ending offset of the second node). - * - * @param context information about the file being parsed - * @param from the AST node to get a starting location from - * @param fromDelta Offset delta to apply to the starting offset - * @param to the AST node to get a ending location from - * @param toDelta Offset delta to apply to the ending offset - * @return a location for the given node - * @deprecated Use {@link #getRangeLocation(JavaContext, PsiElement, int, PsiElement, int)} - * instead - */ - @Deprecated - @NonNull - public abstract Location getRangeLocation( - @NonNull JavaContext context, - @NonNull Node from, - int fromDelta, - @NonNull Node to, - int toDelta); - - /** - * Returns a {@link Location} for the given node range (from the starting offset of the first - * node to the ending offset of the second node). - * - * @param context information about the file being parsed - * @param from the AST node to get a starting location from - * @param fromDelta Offset delta to apply to the starting offset - * @param to the AST node to get a ending location from - * @param toDelta Offset delta to apply to the ending offset - * @return a location for the given node - */ - @SuppressWarnings("MethodMayBeStatic") // subclasses may want to override/optimize - @NonNull - public Location getRangeLocation( - @NonNull JavaContext context, - @NonNull PsiElement from, - int fromDelta, - @NonNull PsiElement to, - int toDelta) { - String contents = context.getContents(); - int start = Math.max(0, from.getTextRange().getStartOffset() + fromDelta); - int end = Math.min(contents == null ? Integer.MAX_VALUE : contents.length(), - to.getTextRange().getEndOffset() + toDelta); - return Location.create(context.file, contents, start, end); - } - - /** - * Like {@link #getRangeLocation(JavaContext, PsiElement, int, PsiElement, int)} - * but both offsets are relative to the starting offset of the given node. This is - * sometimes more convenient than operating relative to the ending offset when you - * have a fixed range in mind. - * - * @param context information about the file being parsed - * @param from the AST node to get a starting location from - * @param fromDelta Offset delta to apply to the starting offset - * @param toDelta Offset delta to apply to the starting offset - * @return a location for the given node - */ - @SuppressWarnings("MethodMayBeStatic") // subclasses may want to override/optimize - @NonNull - public Location getRangeLocation( - @NonNull JavaContext context, - @NonNull PsiElement from, - int fromDelta, - int toDelta) { - return getRangeLocation(context, from, fromDelta, from, - -(from.getTextRange().getLength() - toDelta)); - } - - /** - * Returns a {@link Location} for the given node. This attempts to pick a shorter - * location range than the entire node; for a class or method for example, it picks - * the name node (if found). For statement constructs such as a {@code switch} statement - * it will highlight the keyword, etc. - * - * @param context information about the file being parsed - * @param node the node to create a location for - * @return a location for the given node - * @deprecated Use {@link #getNameLocation(JavaContext, PsiElement)} instead - */ - @Deprecated - @NonNull - public Location getNameLocation(@NonNull JavaContext context, @NonNull Node node) { - Node nameNode = JavaContext.findNameNode(node); - if (nameNode != null) { - node = nameNode; - } else { - if (node instanceof Switch - || node instanceof For - || node instanceof If - || node instanceof While - || node instanceof Throw - || node instanceof Return) { - // Lint doesn't want to highlight the entire statement/block associated - // with this node, it wants to just highlight the keyword. - Location location = getLocation(context, node); - Position start = location.getStart(); - if (start != null) { - // The Lombok classes happen to have the same length as the target keyword - int length = node.getClass().getSimpleName().length(); - return Location.create(location.getFile(), start, - new DefaultPosition(start.getLine(), start.getColumn() + length, - start.getOffset() + length)); - } - } - } - - return getLocation(context, node); - } - - /** - * Returns a {@link Location} for the given node. This attempts to pick a shorter - * location range than the entire node; for a class or method for example, it picks - * the name node (if found). For statement constructs such as a {@code switch} statement - * it will highlight the keyword, etc. - * - * @param context information about the file being parsed - * @param element the node to create a location for - * @return a location for the given node - */ - @NonNull - public Location getNameLocation(@NonNull JavaContext context, @NonNull PsiElement element) { - PsiElement nameNode = JavaContext.findNameElement(element); - if (nameNode != null) { - element = nameNode; - } - - return getLocation(context, element); - } - /** - * Creates a light-weight handle to a location for the given node. It can be - * turned into a full fledged location by - * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}. - * - * @param context the context providing the node - * @param node the node (element or attribute) to create a location handle - * for - * @return a location handle - * @deprecated Use PSI instead (where handles aren't necessary; use PsiElement directly) - */ - @Deprecated - @NonNull - public abstract Location.Handle createLocationHandle(@NonNull JavaContext context, - @NonNull Node node); - - /** - * Dispose any data structures held for the given context. - * @param context information about the file previously parsed - * @param compilationUnit the compilation unit being disposed - * @deprecated Use {@link #dispose(JavaContext, PsiJavaFile)} instead - */ - @Deprecated - public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) { - } - - /** - * Dispose any data structures held for the given context. - * @param context information about the file previously parsed - * @param compilationUnit the compilation unit being disposed - */ - public void dispose(@NonNull JavaContext context, @NonNull PsiFile compilationUnit) { - } - - /** - * Dispose any data structures held for the given context. - * @param context information about the file previously parsed - * @param compilationUnit the compilation unit being disposed - */ - public void dispose(@NonNull JavaContext context, @NonNull UFile compilationUnit) { - } - - /** - * Dispose any remaining data structures held for all contexts. - * Typically frees up any resources allocated by - * {@link #prepareJavaParse(List)} - */ - public void dispose() { - } - - /** - * Resolves the given expression node: computes the declaration for the given symbol - * - * @param context information about the file being parsed - * @param node the node to resolve - * @return a node representing the resolved fully type: class/interface/annotation, - * field, method or variable - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - @Nullable - public ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node) { - return null; - } - - /** - * Finds the given type, if possible (which should be reachable from the compilation - * patch of the given node. - * - * @param context information about the file being parsed - * @param fullyQualifiedName the fully qualified name of the class to look up - * @return the class, or null if not found - */ - @Nullable - public ResolvedClass findClass( - @NonNull JavaContext context, - @NonNull String fullyQualifiedName) { - return null; - } - - /** - * Returns the set of exception types handled by the given catch block. - * <p> - * This is a workaround for the fact that the Lombok AST API (and implementation) - * doesn't support multi-catch statements. - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - public List<TypeDescriptor> getCatchTypes(@NonNull JavaContext context, - @NonNull Catch catchBlock) { - TypeReference typeReference = catchBlock.astExceptionDeclaration().astTypeReference(); - return Collections.<TypeDescriptor>singletonList(new DefaultTypeDescriptor( - typeReference.getTypeName())); - } - - /** - * Gets the type of the given node - * - * @param context information about the file being parsed - * @param node the node to look up the type for - * @return the type of the node, if known - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - @Nullable - public TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node) { - return null; - } - - /** - * Runs the given runnable under a readlock such that it can access the PSI - * - * @param runnable the runnable to be run - */ - public abstract void runReadAction(@NonNull Runnable runnable); - - /** - * A description of a type, such as a primitive int or the android.app.Activity class - * @deprecated Use {@link PsiType} instead - */ - @SuppressWarnings("unused") - @Deprecated - public abstract static class TypeDescriptor { - /** - * Returns the fully qualified name of the type, such as "int" or "android.app.Activity" - * */ - @NonNull public abstract String getName(); - - /** Returns the simple name of this class */ - @NonNull - public String getSimpleName() { - // This doesn't handle inner classes properly, so subclasses with more - // accurate type information will override to handle it correctly. - String name = getName(); - int index = name.lastIndexOf('.'); - if (index != -1) { - return name.substring(index + 1); - } - return name; - } - - /** - * Returns the full signature of the type, which is normally the same as {@link #getName()} - * but for arrays can include []'s, for generic methods can include generics parameters - * etc - */ - @NonNull public abstract String getSignature(); - - /** - * Computes the internal class name of the given fully qualified class name. - * For example, it converts foo.bar.Foo.Bar into foo/bar/Foo$Bar. - * This should only be called for class types, not primitives. - * - * @return the internal class name - */ - @NonNull public String getInternalName() { - return ClassContext.getInternalName(getName()); - } - - public abstract boolean matchesName(@NonNull String name); - - /** - * Returns true if the given TypeDescriptor represents an array - * @return true if this type represents an array - */ - public abstract boolean isArray(); - - /** - * Returns true if the given TypeDescriptor represents a primitive - * @return true if this type represents a primitive - */ - public abstract boolean isPrimitive(); - - public abstract boolean matchesSignature(@NonNull String signature); - - @NonNull - public TypeReference getNode() { - TypeReference typeReference = new TypeReference(); - StrictListAccessor<TypeReferencePart, TypeReference> parts = typeReference.astParts(); - for (String part : Splitter.on('.').split(getName())) { - Identifier identifier = Identifier.of(part); - parts.addToEnd(new TypeReferencePart().astIdentifier(identifier)); - } - - return typeReference; - } - - /** If the type is not primitive, returns the class of the type if known */ - @Nullable - public abstract ResolvedClass getTypeClass(); - - @Override - public abstract boolean equals(Object o); - - @Override - public String toString() { - return getName(); - } - } - - /** - * Convenience implementation of {@link TypeDescriptor} - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - public static class DefaultTypeDescriptor extends TypeDescriptor { - - private String mName; - - public DefaultTypeDescriptor(String name) { - mName = name; - } - - @NonNull - @Override - public String getName() { - return mName; - } - - @NonNull - @Override - public String getSignature() { - return getName(); - } - - @Override - public boolean matchesName(@NonNull String name) { - return mName.equals(name); - } - - @Override - public boolean isArray() { - return mName.endsWith("[]"); - } - - @Override - public boolean isPrimitive() { - return mName.indexOf('.') != -1; - } - - @Override - public boolean matchesSignature(@NonNull String signature) { - return matchesName(signature); - } - - @Override - public String toString() { - return getSignature(); - } - - @Override - @Nullable - public ResolvedClass getTypeClass() { - return null; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - DefaultTypeDescriptor that = (DefaultTypeDescriptor) o; - - return !(mName != null ? !mName.equals(that.mName) : that.mName != null); - - } - - @Override - public int hashCode() { - return mName != null ? mName.hashCode() : 0; - } - } - - /** - * A resolved declaration from an AST Node reference - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @SuppressWarnings("unused") - @Deprecated - public abstract static class ResolvedNode { - @NonNull - public abstract String getName(); - - /** Returns the signature of the resolved node */ - public abstract String getSignature(); - - public abstract int getModifiers(); - - @Override - public String toString() { - return getSignature(); - } - - /** Returns any annotations defined on this node */ - @NonNull - public abstract Iterable<ResolvedAnnotation> getAnnotations(); - - /** - * Searches for the annotation of the given type on this node - * - * @param type the fully qualified name of the annotation to check - * @return the annotation, or null if not found - */ - @Nullable - public ResolvedAnnotation getAnnotation(@NonNull String type) { - for (ResolvedAnnotation annotation : getAnnotations()) { - if (annotation.getType().matchesSignature(type)) { - return annotation; - } - } - - return null; - } - - /** - * Returns true if this element is in the given package (or optionally, in one of its sub - * packages) - * - * @param pkg the package name - * @param includeSubPackages whether to include subpackages - * @return true if the element is in the given package - */ - public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { - return getSignature().startsWith(pkg); - } - - /** - * Attempts to find the corresponding AST node, if possible. This won't work if for example - * the resolved node is from a binary (such as a compiled class in a .jar) or if the - * underlying parser doesn't support it. - * <p> - * Note that looking up the AST node can result in different instances for each lookup. - * - * @return an AST node, if possible. - */ - @Nullable - public Node findAstNode() { - return null; - } - } - - /** - * A resolved class declaration (class, interface, enumeration or annotation) - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @SuppressWarnings("unused") - @Deprecated - public abstract static class ResolvedClass extends ResolvedNode { - /** Returns the fully qualified name of this class */ - @Override - @NonNull - public abstract String getName(); - - /** Returns the simple name of this class */ - @NonNull - public abstract String getSimpleName(); - - /** Returns the package name of this class */ - @NonNull - public String getPackageName() { - String name = getName(); - String simpleName = getSimpleName(); - if (name.length() > simpleName.length() + 1) { - return name.substring(0, name.length() - simpleName.length() - 1); - } - return name; - } - - /** Returns whether this class' fully qualified name matches the given name */ - public abstract boolean matches(@NonNull String name); - - @Nullable - public abstract ResolvedClass getSuperClass(); - - @NonNull - public abstract Iterable<ResolvedClass> getInterfaces(); - - @Nullable - public abstract ResolvedClass getContainingClass(); - - public abstract boolean isInterface(); - public abstract boolean isEnum(); - - public TypeDescriptor getType() { - return new DefaultTypeDescriptor(getName()); - } - - /** - * Determines whether this class extends the given name. If strict is true, - * it will not consider C extends C true. - * <p> - * The target must be a class; to check whether this class extends an interface, - * use {@link #isImplementing(String,boolean)} instead. If you're not sure, use - * {@link #isInheritingFrom(String, boolean)}. - * - * @param name the fully qualified class name - * @param strict if true, do not consider a class to be extending itself - * @return true if this class extends the given class - */ - public abstract boolean isSubclassOf(@NonNull String name, boolean strict); - - /** - * Determines whether this is implementing the given interface. - * <p> - * The target must be an interface; to check whether this class extends a class, - * use {@link #isSubclassOf(String, boolean)} instead. If you're not sure, use - * {@link #isInheritingFrom(String, boolean)}. - * - * @param name the fully qualified interface name - * @param strict if true, do not consider a class to be extending itself - * @return true if this class implements the given interface - */ - public abstract boolean isImplementing(@NonNull String name, boolean strict); - - /** - * Determines whether this class extends or implements the class of the given name. - * If strict is true, it will not consider C extends C true. - * <p> - * For performance reasons, if you know that the target is a class, consider using - * {@link #isSubclassOf(String, boolean)} instead, and if the target is an interface, - * consider using {@link #isImplementing(String,boolean)}. - * - * @param name the fully qualified class name - * @param strict if true, do not consider a class to be inheriting from itself - * @return true if this class extends or implements the given class - */ - public abstract boolean isInheritingFrom(@NonNull String name, boolean strict); - - @NonNull - public abstract Iterable<ResolvedMethod> getConstructors(); - - /** Returns the methods defined in this class, and optionally any methods inherited from any superclasses as well */ - @NonNull - public abstract Iterable<ResolvedMethod> getMethods(boolean includeInherited); - - /** Returns the methods of a given name defined in this class, and optionally any methods inherited from any superclasses as well */ - @NonNull - public abstract Iterable<ResolvedMethod> getMethods(@NonNull String name, boolean includeInherited); - - /** Returns the fields defined in this class, and optionally any fields declared in any superclasses as well */ - @NonNull - public abstract Iterable<ResolvedField> getFields(boolean includeInherited); - - /** Returns the named field defined in this class, or optionally inherited from a superclass */ - @Nullable - public abstract ResolvedField getField(@NonNull String name, boolean includeInherited); - - /** Returns the package containing this class */ - @Nullable - public abstract ResolvedPackage getPackage(); - - @Override - public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { - String packageName = getPackageName(); - - //noinspection SimplifiableIfStatement - if (pkg.equals(packageName)) { - return true; - } - - return includeSubPackages && packageName.length() > pkg.length() && - packageName.charAt(pkg.length()) == '.' && - packageName.startsWith(pkg); - } - } - - /** - * A method or constructor declaration - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @SuppressWarnings("unused") - @Deprecated - public abstract static class ResolvedMethod extends ResolvedNode { - @Override - @NonNull - public abstract String getName(); - - /** Returns whether this method name matches the given name */ - public abstract boolean matches(@NonNull String name); - - @NonNull - public abstract ResolvedClass getContainingClass(); - - public abstract int getArgumentCount(); - - @NonNull - public abstract TypeDescriptor getArgumentType(int index); - - /** Returns true if the parameter at the given index matches the given type signature */ - public boolean argumentMatchesType(int index, @NonNull String signature) { - return getArgumentType(index).matchesSignature(signature); - } - - @Nullable - public abstract TypeDescriptor getReturnType(); - - public boolean isConstructor() { - return getReturnType() == null; - } - - /** Returns any annotations defined on the given parameter of this method */ - @NonNull - public abstract Iterable<ResolvedAnnotation> getParameterAnnotations(int index); - - /** - * Searches for the annotation of the given type on the method - * - * @param type the fully qualified name of the annotation to check - * @param parameterIndex the index of the parameter to look up - * @return the annotation, or null if not found - */ - @Nullable - public ResolvedAnnotation getParameterAnnotation(@NonNull String type, - int parameterIndex) { - for (ResolvedAnnotation annotation : getParameterAnnotations(parameterIndex)) { - if (annotation.getType().matchesSignature(type)) { - return annotation; - } - } - - return null; - } - - /** Returns the super implementation of the given method, if any */ - @Nullable - public ResolvedMethod getSuperMethod() { - if ((getModifiers() & Modifier.PRIVATE) != 0) { - // Private methods aren't overriding anything - return null; - } - ResolvedClass cls = getContainingClass().getSuperClass(); - if (cls != null) { - String methodName = getName(); - int argCount = getArgumentCount(); - for (ResolvedMethod method : cls.getMethods(methodName, true)) { - if (argCount != method.getArgumentCount()) { - continue; - } - boolean sameTypes = true; - for (int arg = 0; arg < argCount; arg++) { - if (!method.getArgumentType(arg).equals(getArgumentType(arg))) { - sameTypes = false; - break; - } - } - if (sameTypes) { - if ((method.getModifiers() & Modifier.PRIVATE) != 0) { - // Normally can't override private methods - unless they're - // in the same compilation unit where the compiler will create - // an accessor method to trampoline over to it. - // - // Compare compilation units: - if (haveSameCompilationUnit(getContainingClass(), - method.getContainingClass())) { - return method; - } else { - // We can stop the search; this is invalid (you can't have a - // private method in the middle of a chain; the compiler would - // complain about weaker access) - return null; - } - } - return method; - } - } - } - - return null; - } - - @Override - public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { - String packageName = getContainingClass().getPackageName(); - - //noinspection SimplifiableIfStatement - if (pkg.equals(packageName)) { - return true; - } - - return includeSubPackages && packageName.length() > pkg.length() && - packageName.charAt(pkg.length()) == '.' && - packageName.startsWith(pkg); - } - } - - /** - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - private static boolean haveSameCompilationUnit(@Nullable ResolvedClass cls1, - @Nullable ResolvedClass cls2) { - if (cls1 == null || cls2 == null) { - return false; - } - //noinspection ConstantConditions - while (cls1.getContainingClass() != null) { - cls1 = cls1.getContainingClass(); - } - //noinspection ConstantConditions - while (cls2.getContainingClass() != null) { - cls2 = cls2.getContainingClass(); - } - return cls1.equals(cls2); - } - - /** - * A field declaration - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - public abstract static class ResolvedField extends ResolvedNode { - @Override - @NonNull - public abstract String getName(); - - /** Returns whether this field name matches the given name */ - public abstract boolean matches(@NonNull String name); - - @NonNull - public abstract TypeDescriptor getType(); - - @Nullable - public abstract ResolvedClass getContainingClass(); - - @Nullable - public abstract Object getValue(); - - @Nullable - public String getContainingClassName() { - ResolvedClass containingClass = getContainingClass(); - return containingClass != null ? containingClass.getName() : null; - } - - @Override - public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { - ResolvedClass containingClass = getContainingClass(); - if (containingClass == null) { - return false; - } - - String packageName = containingClass.getPackageName(); - - //noinspection SimplifiableIfStatement - if (pkg.equals(packageName)) { - return true; - } - - return includeSubPackages && packageName.length() > pkg.length() && - packageName.charAt(pkg.length()) == '.' && - packageName.startsWith(pkg); - } - } - - /** - * An annotation <b>reference</b>. Note that this refers to a usage of an annotation, - * not a declaraton of an annotation. You can call {@link #getClassType()} to - * find the declaration for the annotation. - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - public abstract static class ResolvedAnnotation extends ResolvedNode { - @Override - @NonNull - public abstract String getName(); - - /** Returns whether this field name matches the given name */ - public abstract boolean matches(@NonNull String name); - - @NonNull - public abstract TypeDescriptor getType(); - - /** Returns the {@link ResolvedClass} which defines the annotation */ - @Nullable - public abstract ResolvedClass getClassType(); - - public static class Value { - @NonNull public final String name; - @Nullable public final Object value; - - public Value(@NonNull String name, @Nullable Object value) { - this.name = name; - this.value = value; - } - } - - @NonNull - public abstract List<Value> getValues(); - - @Nullable - public Object getValue(@NonNull String name) { - for (Value value : getValues()) { - if (name.equals(value.name)) { - return value.value; - } - } - return null; - } - - @Nullable - public Object getValue() { - return getValue(ATTR_VALUE); - } - - @NonNull - @Override - public Iterable<ResolvedAnnotation> getAnnotations() { - return Collections.emptyList(); - } - } - - /** - * A package declaration - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @SuppressWarnings("unused") - @Deprecated - public abstract static class ResolvedPackage extends ResolvedNode { - /** Returns the parent package of this package, if any. */ - @Nullable - public abstract ResolvedPackage getParentPackage(); - - @NonNull - @Override - public Iterable<ResolvedAnnotation> getAnnotations() { - return Collections.emptyList(); - } - } - - /** - * A local variable or parameter declaration - * @deprecated Use {@link JavaPsiScanner} APIs instead - */ - @Deprecated - public abstract static class ResolvedVariable extends ResolvedNode { - @Override - @NonNull - public abstract String getName(); - - /** Returns whether this variable name matches the given name */ - public abstract boolean matches(@NonNull String name); - - @NonNull - public abstract TypeDescriptor getType(); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaPsiVisitor.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaPsiVisitor.java deleted file mode 100644 index 772ca31..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaPsiVisitor.java +++ /dev/null
@@ -1,1716 +0,0 @@ -/* - * 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.klint.client.api; - -import static com.android.SdkConstants.ANDROID_PKG; -import static com.android.SdkConstants.R_CLASS; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.resources.ResourceType; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.JavaContext; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.psi.ImplicitVariable; -import com.intellij.psi.JavaElementVisitor; -import com.intellij.psi.JavaRecursiveElementVisitor; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAnnotationMethod; -import com.intellij.psi.PsiAnnotationParameterList; -import com.intellij.psi.PsiAnonymousClass; -import com.intellij.psi.PsiArrayAccessExpression; -import com.intellij.psi.PsiArrayInitializerExpression; -import com.intellij.psi.PsiArrayInitializerMemberValue; -import com.intellij.psi.PsiAssertStatement; -import com.intellij.psi.PsiAssignmentExpression; -import com.intellij.psi.PsiBinaryExpression; -import com.intellij.psi.PsiBlockStatement; -import com.intellij.psi.PsiBreakStatement; -import com.intellij.psi.PsiCallExpression; -import com.intellij.psi.PsiCatchSection; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassInitializer; -import com.intellij.psi.PsiClassObjectAccessExpression; -import com.intellij.psi.PsiCodeBlock; -import com.intellij.psi.PsiConditionalExpression; -import com.intellij.psi.PsiContinueStatement; -import com.intellij.psi.PsiDeclarationStatement; -import com.intellij.psi.PsiDoWhileStatement; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiEmptyStatement; -import com.intellij.psi.PsiEnumConstant; -import com.intellij.psi.PsiEnumConstantInitializer; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiExpressionList; -import com.intellij.psi.PsiExpressionListStatement; -import com.intellij.psi.PsiExpressionStatement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiForStatement; -import com.intellij.psi.PsiForeachStatement; -import com.intellij.psi.PsiIdentifier; -import com.intellij.psi.PsiIfStatement; -import com.intellij.psi.PsiImportList; -import com.intellij.psi.PsiImportStatement; -import com.intellij.psi.PsiImportStaticReferenceElement; -import com.intellij.psi.PsiImportStaticStatement; -import com.intellij.psi.PsiInstanceOfExpression; -import com.intellij.psi.PsiJavaCodeReferenceElement; -import com.intellij.psi.PsiJavaFile; -import com.intellij.psi.PsiJavaToken; -import com.intellij.psi.PsiKeyword; -import com.intellij.psi.PsiLabeledStatement; -import com.intellij.psi.PsiLambdaExpression; -import com.intellij.psi.PsiLiteralExpression; -import com.intellij.psi.PsiLocalVariable; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiMethodReferenceExpression; -import com.intellij.psi.PsiModifierList; -import com.intellij.psi.PsiNameValuePair; -import com.intellij.psi.PsiNewExpression; -import com.intellij.psi.PsiPackage; -import com.intellij.psi.PsiPackageStatement; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiParameterList; -import com.intellij.psi.PsiParenthesizedExpression; -import com.intellij.psi.PsiPolyadicExpression; -import com.intellij.psi.PsiPostfixExpression; -import com.intellij.psi.PsiPrefixExpression; -import com.intellij.psi.PsiReceiverParameter; -import com.intellij.psi.PsiReferenceExpression; -import com.intellij.psi.PsiReferenceList; -import com.intellij.psi.PsiReferenceParameterList; -import com.intellij.psi.PsiResourceList; -import com.intellij.psi.PsiResourceVariable; -import com.intellij.psi.PsiReturnStatement; -import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiSuperExpression; -import com.intellij.psi.PsiSwitchLabelStatement; -import com.intellij.psi.PsiSwitchStatement; -import com.intellij.psi.PsiSynchronizedStatement; -import com.intellij.psi.PsiThisExpression; -import com.intellij.psi.PsiThrowStatement; -import com.intellij.psi.PsiTryStatement; -import com.intellij.psi.PsiTypeCastExpression; -import com.intellij.psi.PsiTypeElement; -import com.intellij.psi.PsiTypeParameter; -import com.intellij.psi.PsiTypeParameterList; -import com.intellij.psi.PsiVariable; -import com.intellij.psi.PsiWhileStatement; -import com.intellij.psi.javadoc.PsiDocComment; -import com.intellij.psi.javadoc.PsiDocTag; -import com.intellij.psi.javadoc.PsiDocTagValue; -import com.intellij.psi.javadoc.PsiDocToken; -import com.intellij.psi.javadoc.PsiInlineDocTag; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Specialized visitor for running detectors on a Java AST. - * It operates in three phases: - * <ol> - * <li> First, it computes a set of maps where it generates a map from each - * significant AST attribute (such as method call names) to a list - * of detectors to consult whenever that attribute is encountered. - * Examples of "attributes" are method names, Android resource identifiers, - * and general AST node types such as "cast" nodes etc. These are - * defined on the {@link JavaPsiScanner} interface. - * <li> Second, it iterates over the document a single time, delegating to - * the detectors found at each relevant AST attribute. - * <li> Finally, it calls the remaining visitors (those that need to process a - * whole document on their own). - * </ol> - * It also notifies all the detectors before and after the document is processed - * such that they can do pre- and post-processing. - */ -public class JavaPsiVisitor { - /** Default size of lists holding detectors of the same type for a given node type */ - private static final int SAME_TYPE_COUNT = 8; - - private final Map<String, List<VisitingDetector>> mMethodDetectors = - Maps.newHashMapWithExpectedSize(80); - private final Map<String, List<VisitingDetector>> mConstructorDetectors = - Maps.newHashMapWithExpectedSize(12); - private final Map<String, List<VisitingDetector>> mReferenceDetectors = - Maps.newHashMapWithExpectedSize(10); - private Set<String> mConstructorSimpleNames; - private final List<VisitingDetector> mResourceFieldDetectors = - new ArrayList<VisitingDetector>(); - private final List<VisitingDetector> mAllDetectors; - private final List<VisitingDetector> mFullTreeDetectors; - private final Map<Class<? extends PsiElement>, List<VisitingDetector>> mNodePsiTypeDetectors = - new HashMap<Class<? extends PsiElement>, List<VisitingDetector>>(16); - private final JavaParser mParser; - private final Map<String, List<VisitingDetector>> mSuperClassDetectors = - new HashMap<String, List<VisitingDetector>>(); - - /** - * Number of fatal exceptions (internal errors, usually from ECJ) we've - * encountered; we don't log each and every one to avoid massive log spam - * in code which triggers this condition - */ - private static int sExceptionCount; - /** Max number of logs to include */ - private static final int MAX_REPORTED_CRASHES = 20; - - JavaPsiVisitor(@NonNull JavaParser parser, @NonNull List<Detector> detectors) { - mParser = parser; - mAllDetectors = new ArrayList<VisitingDetector>(detectors.size()); - mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size()); - - for (Detector detector : detectors) { - JavaPsiScanner javaPsiScanner = (JavaPsiScanner) detector; - VisitingDetector v = new VisitingDetector(detector, javaPsiScanner); - mAllDetectors.add(v); - - List<String> applicableSuperClasses = detector.applicableSuperClasses(); - if (applicableSuperClasses != null) { - for (String fqn : applicableSuperClasses) { - List<VisitingDetector> list = mSuperClassDetectors.get(fqn); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mSuperClassDetectors.put(fqn, list); - } - list.add(v); - } - continue; - } - - List<Class<? extends PsiElement>> nodePsiTypes = detector.getApplicablePsiTypes(); - if (nodePsiTypes != null) { - for (Class<? extends PsiElement> type : nodePsiTypes) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(type); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mNodePsiTypeDetectors.put(type, list); - } - list.add(v); - } - } - - List<String> names = detector.getApplicableMethodNames(); - if (names != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert names != XmlScanner.ALL; - - for (String name : names) { - List<VisitingDetector> list = mMethodDetectors.get(name); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mMethodDetectors.put(name, list); - } - list.add(v); - } - } - - List<String> types = detector.getApplicableConstructorTypes(); - if (types != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert types != XmlScanner.ALL; - if (mConstructorSimpleNames == null) { - mConstructorSimpleNames = Sets.newHashSet(); - } - for (String type : types) { - List<VisitingDetector> list = mConstructorDetectors.get(type); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mConstructorDetectors.put(type, list); - mConstructorSimpleNames.add(type.substring(type.lastIndexOf('.')+1)); - } - list.add(v); - } - } - - List<String> referenceNames = detector.getApplicableReferenceNames(); - if (referenceNames != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert referenceNames != XmlScanner.ALL; - - for (String name : referenceNames) { - List<VisitingDetector> list = mReferenceDetectors.get(name); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mReferenceDetectors.put(name, list); - } - list.add(v); - } - } - - if (detector.appliesToResourceRefs()) { - mResourceFieldDetectors.add(v); - } else if ((referenceNames == null || referenceNames.isEmpty()) - && (nodePsiTypes == null || nodePsiTypes.isEmpty()) - && (types == null || types.isEmpty())) { - mFullTreeDetectors.add(v); - } - } - } - - void visitFile(@NonNull final JavaContext context) { - try { - final PsiJavaFile javaFile = mParser.parseJavaToPsi(context); - if (javaFile == null) { - // No need to log this; the parser should be reporting - // a full warning (such as IssueRegistry#PARSER_ERROR) - // with details, location, etc. - return; - } - try { - context.setJavaFile(javaFile); - - mParser.runReadAction(new Runnable() { - @Override - public void run() { - for (VisitingDetector v : mAllDetectors) { - v.setContext(context); - v.getDetector().beforeCheckFile(context); - } - } - }); - - if (!mSuperClassDetectors.isEmpty()) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - SuperclassPsiVisitor visitor = new SuperclassPsiVisitor(context); - javaFile.accept(visitor); - } - }); - } - - for (final VisitingDetector v : mFullTreeDetectors) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - JavaElementVisitor visitor = v.getVisitor(); - javaFile.accept(visitor); - } - }); - } - - if (!mMethodDetectors.isEmpty() - || !mResourceFieldDetectors.isEmpty() - || !mConstructorDetectors.isEmpty() - || !mReferenceDetectors.isEmpty()) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - // TODO: Do we need to break this one up into finer grain - // locking units - JavaElementVisitor visitor = new DelegatingPsiVisitor(context); - javaFile.accept(visitor); - } - }); - } else { - if (!mNodePsiTypeDetectors.isEmpty()) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - // TODO: Do we need to break this one up into finer grain - // locking units - JavaElementVisitor visitor = new DispatchPsiVisitor(); - javaFile.accept(visitor); - } - }); - } - } - - mParser.runReadAction(new Runnable() { - @Override - public void run() { - for (VisitingDetector v : mAllDetectors) { - v.getDetector().afterCheckFile(context); - } - } - }); - } finally { - mParser.dispose(context, javaFile); - context.setJavaFile(null); - } - } catch (ProcessCanceledException ignore) { - // Cancelling inspections in the IDE - } catch (RuntimeException e) { - if (sExceptionCount++ > MAX_REPORTED_CRASHES) { - // No need to keep spamming the user that a lot of the files - // are tripping up ECJ, they get the picture. - return; - } - - if (e.getClass().getSimpleName().equals("IndexNotReadyException")) { - // Attempting to access PSI during startup before indices are ready; ignore these. - // See http://b.android.com/176644 for an example. - return; - } - - // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268 - // Don't allow lint bugs to take down the whole build. TRY to log this as a - // lint error instead! - StringBuilder sb = new StringBuilder(100); - sb.append("Unexpected failure during lint analysis of "); - sb.append(context.file.getName()); - sb.append(" (this is a bug in lint or one of the libraries it depends on)\n"); - - sb.append(e.getClass().getSimpleName()); - sb.append(':'); - StackTraceElement[] stackTrace = e.getStackTrace(); - int count = 0; - for (StackTraceElement frame : stackTrace) { - if (count > 0) { - sb.append("<-"); - } - - String className = frame.getClassName(); - sb.append(className.substring(className.lastIndexOf('.') + 1)); - sb.append('.').append(frame.getMethodName()); - sb.append('('); - sb.append(frame.getFileName()).append(':').append(frame.getLineNumber()); - sb.append(')'); - count++; - // Only print the top 3-4 frames such that we can identify the bug - if (count == 4) { - break; - } - } - Throwable throwable = null; // NOT e: this makes for very noisy logs - //noinspection ConstantConditions - context.log(throwable, sb.toString()); - } - } - - /** - * For testing only: returns the number of exceptions thrown during Java AST analysis - * - * @return the number of internal errors found - */ - @VisibleForTesting - public static int getCrashCount() { - return sExceptionCount; - } - - /** - * For testing only: clears the crash counter - */ - @VisibleForTesting - public static void clearCrashCount() { - sExceptionCount = 0; - } - - public void prepare(@NonNull List<JavaContext> contexts) { - mParser.prepareJavaParse(contexts); - } - - public void dispose() { - mParser.dispose(); - } - - @Nullable - private static Set<String> getInterfaceNames( - @Nullable Set<String> addTo, - @NonNull PsiClass cls) { - for (PsiClass resolvedInterface : cls.getInterfaces()) { - String name = resolvedInterface.getQualifiedName(); - if (addTo == null) { - addTo = Sets.newHashSet(); - } else if (addTo.contains(name)) { - // Superclasses can explicitly implement the same interface, - // so keep track of visited interfaces as we traverse up the - // super class chain to avoid checking the same interface - // more than once. - continue; - } - addTo.add(name); - getInterfaceNames(addTo, resolvedInterface); - } - - return addTo; - } - - private static class VisitingDetector { - private JavaElementVisitor mVisitor; - private JavaContext mContext; - public final Detector mDetector; - public final JavaPsiScanner mJavaScanner; - - public VisitingDetector(@NonNull Detector detector, @NonNull JavaPsiScanner javaScanner) { - mDetector = detector; - mJavaScanner = javaScanner; - } - - @NonNull - public Detector getDetector() { - return mDetector; - } - - @Nullable - public JavaPsiScanner getJavaScanner() { - return mJavaScanner; - } - - public void setContext(@NonNull JavaContext context) { - mContext = context; - - // The visitors are one-per-context, so clear them out here and construct - // lazily only if needed - mVisitor = null; - } - - @NonNull - JavaElementVisitor getVisitor() { - if (mVisitor == null) { - mVisitor = mDetector.createPsiVisitor(mContext); - assert !(mVisitor instanceof JavaRecursiveElementVisitor) : - "Your visitor (returned by " + mDetector.getClass().getSimpleName() - + "#createPsiVisitor(...) should *not* extend " - + " JavaRecursiveElementVisitor; use a plain " - + "JavaElementVisitor instead. The lint infrastructure does its own " - + "recursion calling *just* your visit methods specified in " - + "getApplicablePsiTypes"; - if (mVisitor == null) { - mVisitor = new JavaElementVisitor() { - @Override - public void visitElement(PsiElement element) { - // No-op. Workaround for super currently calling - // ProgressIndicatorProvider.checkCanceled(); - } - }; - } - } - return mVisitor; - } - } - - private class SuperclassPsiVisitor extends JavaRecursiveElementVisitor { - private JavaContext mContext; - - public SuperclassPsiVisitor(@NonNull JavaContext context) { - mContext = context; - } - - @Override - public void visitClass(@NonNull PsiClass node) { - super.visitClass(node); - checkClass(node); - } - - private void checkClass(@NonNull PsiClass node) { - if (node instanceof PsiTypeParameter) { - // Not included: explained in javadoc for JavaPsiScanner#checkClass - return; - } - - PsiClass cls = node; - int depth = 0; - while (cls != null) { - List<VisitingDetector> list = mSuperClassDetectors.get(cls.getQualifiedName()); - if (list != null) { - for (VisitingDetector v : list) { - JavaPsiScanner javaPsiScanner = v.getJavaScanner(); - if (javaPsiScanner != null) { - javaPsiScanner.checkClass(mContext, node); - } - } - } - - // Check interfaces too - Set<String> interfaceNames = getInterfaceNames(null, cls); - if (interfaceNames != null) { - for (String name : interfaceNames) { - list = mSuperClassDetectors.get(name); - if (list != null) { - for (VisitingDetector v : list) { - JavaPsiScanner javaPsiScanner = v.getJavaScanner(); - if (javaPsiScanner != null) { - javaPsiScanner.checkClass(mContext, node); - } - } - } - } - } - - cls = cls.getSuperClass(); - depth++; - if (depth == 500) { - // Shouldn't happen in practice; this prevents the IDE from - // hanging if the user has accidentally typed in an incorrect - // super class which creates a cycle. - break; - } - } - } - } - - private class DispatchPsiVisitor extends JavaRecursiveElementVisitor { - - @Override - public void visitAnonymousClass(PsiAnonymousClass node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiAnonymousClass.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnonymousClass(node); - } - } - super.visitAnonymousClass(node); - } - - @Override - public void visitArrayAccessExpression(PsiArrayAccessExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiArrayAccessExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitArrayAccessExpression(node); - } - } - super.visitArrayAccessExpression(node); - } - - @Override - public void visitArrayInitializerExpression(PsiArrayInitializerExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiArrayInitializerExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitArrayInitializerExpression(node); - } - } - super.visitArrayInitializerExpression(node); - } - - @Override - public void visitAssertStatement(PsiAssertStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiAssertStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAssertStatement(node); - } - } - super.visitAssertStatement(node); - } - - @Override - public void visitAssignmentExpression(PsiAssignmentExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiAssignmentExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAssignmentExpression(node); - } - } - super.visitAssignmentExpression(node); - } - - @Override - public void visitBinaryExpression(PsiBinaryExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiBinaryExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBinaryExpression(node); - } - } - super.visitBinaryExpression(node); - } - - @Override - public void visitBlockStatement(PsiBlockStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiBlockStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBlockStatement(node); - } - } - super.visitBlockStatement(node); - } - - @Override - public void visitBreakStatement(PsiBreakStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiBreakStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBreakStatement(node); - } - } - super.visitBreakStatement(node); - } - - @Override - public void visitClass(PsiClass node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiClass.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitClass(node); - } - } - super.visitClass(node); - } - - @Override - public void visitClassInitializer(PsiClassInitializer node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiClassInitializer.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitClassInitializer(node); - } - } - super.visitClassInitializer(node); - } - - @Override - public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiClassObjectAccessExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitClassObjectAccessExpression(node); - } - } - super.visitClassObjectAccessExpression(node); - } - - @Override - public void visitCodeBlock(PsiCodeBlock node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiCodeBlock.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCodeBlock(node); - } - } - super.visitCodeBlock(node); - } - - @Override - public void visitConditionalExpression(PsiConditionalExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiConditionalExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitConditionalExpression(node); - } - } - super.visitConditionalExpression(node); - } - - @Override - public void visitContinueStatement(PsiContinueStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiContinueStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitContinueStatement(node); - } - } - super.visitContinueStatement(node); - } - - @Override - public void visitDeclarationStatement(PsiDeclarationStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiDeclarationStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDeclarationStatement(node); - } - } - super.visitDeclarationStatement(node); - } - - @Override - public void visitDocComment(PsiDocComment node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiDocComment.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDocComment(node); - } - } - super.visitDocComment(node); - } - - @Override - public void visitDocTag(PsiDocTag node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiDocTag.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDocTag(node); - } - } - super.visitDocTag(node); - } - - @Override - public void visitDocTagValue(PsiDocTagValue node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiDocTagValue.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDocTagValue(node); - } - } - super.visitDocTagValue(node); - } - - @Override - public void visitDoWhileStatement(PsiDoWhileStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiDoWhileStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDoWhileStatement(node); - } - } - super.visitDoWhileStatement(node); - } - - @Override - public void visitEmptyStatement(PsiEmptyStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiEmptyStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEmptyStatement(node); - } - } - super.visitEmptyStatement(node); - } - - @Override - public void visitExpression(PsiExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitExpression(node); - } - } - super.visitExpression(node); - } - - @Override - public void visitExpressionList(PsiExpressionList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiExpressionList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitExpressionList(node); - } - } - super.visitExpressionList(node); - } - - @Override - public void visitExpressionListStatement(PsiExpressionListStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiExpressionListStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitExpressionListStatement(node); - } - } - super.visitExpressionListStatement(node); - } - - @Override - public void visitExpressionStatement(PsiExpressionStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiExpressionStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitExpressionStatement(node); - } - } - super.visitExpressionStatement(node); - } - - @Override - public void visitField(PsiField node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiField.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitField(node); - } - } - super.visitField(node); - } - - @Override - public void visitForStatement(PsiForStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiForStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitForStatement(node); - } - } - super.visitForStatement(node); - } - - @Override - public void visitForeachStatement(PsiForeachStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiForeachStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitForeachStatement(node); - } - } - super.visitForeachStatement(node); - } - - @Override - public void visitIdentifier(PsiIdentifier node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiIdentifier.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitIdentifier(node); - } - } - super.visitIdentifier(node); - } - - @Override - public void visitIfStatement(PsiIfStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiIfStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitIfStatement(node); - } - } - super.visitIfStatement(node); - } - - @Override - public void visitImportList(PsiImportList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiImportList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitImportList(node); - } - } - super.visitImportList(node); - } - - @Override - public void visitImportStatement(PsiImportStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiImportStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitImportStatement(node); - } - } - super.visitImportStatement(node); - } - - @Override - public void visitImportStaticStatement(PsiImportStaticStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiImportStaticStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitImportStaticStatement(node); - } - } - super.visitImportStaticStatement(node); - } - - @Override - public void visitInlineDocTag(PsiInlineDocTag node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiInlineDocTag.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitInlineDocTag(node); - } - } - super.visitInlineDocTag(node); - } - - @Override - public void visitInstanceOfExpression(PsiInstanceOfExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiInstanceOfExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitInstanceOfExpression(node); - } - } - super.visitInstanceOfExpression(node); - } - - @Override - public void visitJavaToken(PsiJavaToken node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiJavaToken.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitJavaToken(node); - } - } - super.visitJavaToken(node); - } - - @Override - public void visitKeyword(PsiKeyword node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiKeyword.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitKeyword(node); - } - } - super.visitKeyword(node); - } - - @Override - public void visitLabeledStatement(PsiLabeledStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiLabeledStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLabeledStatement(node); - } - } - super.visitLabeledStatement(node); - } - - @Override - public void visitLiteralExpression(PsiLiteralExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiLiteralExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLiteralExpression(node); - } - } - super.visitLiteralExpression(node); - } - - @Override - public void visitLocalVariable(PsiLocalVariable node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiLocalVariable.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLocalVariable(node); - } - } - super.visitLocalVariable(node); - } - - @Override - public void visitMethod(PsiMethod node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiMethod.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitMethod(node); - } - } - super.visitMethod(node); - } - - @Override - public void visitMethodCallExpression(PsiMethodCallExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiMethodCallExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitMethodCallExpression(node); - } - } - super.visitMethodCallExpression(node); - } - - @Override - public void visitCallExpression(PsiCallExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiCallExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCallExpression(node); - } - } - super.visitCallExpression(node); - } - - @Override - public void visitModifierList(PsiModifierList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiModifierList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitModifierList(node); - } - } - super.visitModifierList(node); - } - - @Override - public void visitNewExpression(PsiNewExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiNewExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitNewExpression(node); - } - } - super.visitNewExpression(node); - } - - @Override - public void visitPackage(PsiPackage node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiPackage.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPackage(node); - } - } - super.visitPackage(node); - } - - @Override - public void visitPackageStatement(PsiPackageStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiPackageStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPackageStatement(node); - } - } - super.visitPackageStatement(node); - } - - @Override - public void visitParameter(PsiParameter node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiParameter.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitParameter(node); - } - } - super.visitParameter(node); - } - - @Override - public void visitReceiverParameter(PsiReceiverParameter node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiReceiverParameter.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReceiverParameter(node); - } - } - super.visitReceiverParameter(node); - } - - @Override - public void visitParameterList(PsiParameterList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiParameterList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitParameterList(node); - } - } - super.visitParameterList(node); - } - - @Override - public void visitParenthesizedExpression(PsiParenthesizedExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiParenthesizedExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitParenthesizedExpression(node); - } - } - super.visitParenthesizedExpression(node); - } - - @Override - public void visitPostfixExpression(PsiPostfixExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiPostfixExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPostfixExpression(node); - } - } - super.visitPostfixExpression(node); - } - - @Override - public void visitPrefixExpression(PsiPrefixExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiPrefixExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPrefixExpression(node); - } - } - super.visitPrefixExpression(node); - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiJavaCodeReferenceElement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReferenceElement(node); - } - } - super.visitReferenceElement(node); - } - - @Override - public void visitImportStaticReferenceElement(PsiImportStaticReferenceElement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiImportStaticReferenceElement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitImportStaticReferenceElement(node); - } - } - super.visitImportStaticReferenceElement(node); - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiReferenceExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReferenceExpression(node); - } - } - super.visitReferenceExpression(node); - } - - @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiMethodReferenceExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitMethodReferenceExpression(node); - } - } - super.visitMethodReferenceExpression(node); - } - - @Override - public void visitReferenceList(PsiReferenceList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiReferenceList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReferenceList(node); - } - } - super.visitReferenceList(node); - } - - @Override - public void visitReferenceParameterList(PsiReferenceParameterList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiReferenceParameterList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReferenceParameterList(node); - } - } - super.visitReferenceParameterList(node); - } - - @Override - public void visitTypeParameterList(PsiTypeParameterList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiTypeParameterList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeParameterList(node); - } - } - super.visitTypeParameterList(node); - } - - @Override - public void visitReturnStatement(PsiReturnStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiReturnStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReturnStatement(node); - } - } - super.visitReturnStatement(node); - } - - @Override - public void visitStatement(PsiStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitStatement(node); - } - } - super.visitStatement(node); - } - - @Override - public void visitSuperExpression(PsiSuperExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiSuperExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSuperExpression(node); - } - } - super.visitSuperExpression(node); - } - - @Override - public void visitSwitchLabelStatement(PsiSwitchLabelStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiSwitchLabelStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSwitchLabelStatement(node); - } - } - super.visitSwitchLabelStatement(node); - } - - @Override - public void visitSwitchStatement(PsiSwitchStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiSwitchStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSwitchStatement(node); - } - } - super.visitSwitchStatement(node); - } - - @Override - public void visitSynchronizedStatement(PsiSynchronizedStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiSynchronizedStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSynchronizedStatement(node); - } - } - super.visitSynchronizedStatement(node); - } - - @Override - public void visitThisExpression(PsiThisExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiThisExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitThisExpression(node); - } - } - super.visitThisExpression(node); - } - - @Override - public void visitThrowStatement(PsiThrowStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiThrowStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitThrowStatement(node); - } - } - super.visitThrowStatement(node); - } - - @Override - public void visitTryStatement(PsiTryStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiTryStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTryStatement(node); - } - } - super.visitTryStatement(node); - } - - @Override - public void visitCatchSection(PsiCatchSection node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiCatchSection.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCatchSection(node); - } - } - super.visitCatchSection(node); - } - - @Override - public void visitResourceList(PsiResourceList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiResourceList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitResourceList(node); - } - } - super.visitResourceList(node); - } - - @Override - public void visitResourceVariable(PsiResourceVariable node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiResourceVariable.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitResourceVariable(node); - } - } - super.visitResourceVariable(node); - } - - @Override - public void visitTypeElement(PsiTypeElement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiTypeElement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeElement(node); - } - } - super.visitTypeElement(node); - } - - @Override - public void visitTypeCastExpression(PsiTypeCastExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiTypeCastExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeCastExpression(node); - } - } - super.visitTypeCastExpression(node); - } - - @Override - public void visitVariable(PsiVariable node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiVariable.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitVariable(node); - } - } - super.visitVariable(node); - } - - @Override - public void visitWhileStatement(PsiWhileStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiWhileStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitWhileStatement(node); - } - } - super.visitWhileStatement(node); - } - - @Override - public void visitJavaFile(PsiJavaFile node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiJavaFile.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitJavaFile(node); - } - } - super.visitJavaFile(node); - } - - @Override - public void visitImplicitVariable(ImplicitVariable node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(ImplicitVariable.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitImplicitVariable(node); - } - } - super.visitImplicitVariable(node); - } - - @Override - public void visitDocToken(PsiDocToken node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiDocToken.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDocToken(node); - } - } - super.visitDocToken(node); - } - - @Override - public void visitTypeParameter(PsiTypeParameter node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiTypeParameter.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeParameter(node); - } - } - super.visitTypeParameter(node); - } - - @Override - public void visitAnnotation(PsiAnnotation node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiAnnotation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotation(node); - } - } - super.visitAnnotation(node); - } - - @Override - public void visitAnnotationParameterList(PsiAnnotationParameterList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiAnnotationParameterList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotationParameterList(node); - } - } - super.visitAnnotationParameterList(node); - } - - @Override - public void visitAnnotationArrayInitializer(PsiArrayInitializerMemberValue node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiArrayInitializerMemberValue.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotationArrayInitializer(node); - } - } - super.visitAnnotationArrayInitializer(node); - } - - @Override - public void visitNameValuePair(PsiNameValuePair node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiNameValuePair.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitNameValuePair(node); - } - } - super.visitNameValuePair(node); - } - - @Override - public void visitAnnotationMethod(PsiAnnotationMethod node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiAnnotationMethod.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotationMethod(node); - } - } - super.visitAnnotationMethod(node); - } - - @Override - public void visitEnumConstant(PsiEnumConstant node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiEnumConstant.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEnumConstant(node); - } - } - super.visitEnumConstant(node); - } - - @Override - public void visitEnumConstantInitializer(PsiEnumConstantInitializer node) { - List<VisitingDetector> list = mNodePsiTypeDetectors - .get(PsiEnumConstantInitializer.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEnumConstantInitializer(node); - } - } - super.visitEnumConstantInitializer(node); - } - - @Override - public void visitPolyadicExpression(PsiPolyadicExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiPolyadicExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPolyadicExpression(node); - } - } - super.visitPolyadicExpression(node); - } - - @Override - public void visitLambdaExpression(PsiLambdaExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(PsiLambdaExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLambdaExpression(node); - } - } - super.visitLambdaExpression(node); - } - } - - /** Performs common AST searches for method calls and R-type-field references. - * Note that this is a specialized form of the {@link DispatchPsiVisitor}. */ - private class DelegatingPsiVisitor extends DispatchPsiVisitor { - private final JavaContext mContext; - private final boolean mVisitResources; - private final boolean mVisitMethods; - private final boolean mVisitConstructors; - private final boolean mVisitReferences; - - public DelegatingPsiVisitor(JavaContext context) { - mContext = context; - - mVisitMethods = !mMethodDetectors.isEmpty(); - mVisitConstructors = !mConstructorDetectors.isEmpty(); - mVisitResources = !mResourceFieldDetectors.isEmpty(); - mVisitReferences = !mReferenceDetectors.isEmpty(); - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement element) { - if (mVisitReferences) { - String name = element.getReferenceName(); - if (name != null) { - List<VisitingDetector> list = mReferenceDetectors.get(name); - if (list != null) { - PsiElement referenced = element.resolve(); - if (referenced != null) { - for (VisitingDetector v : list) { - JavaPsiScanner javaPsiScanner = v.getJavaScanner(); - if (javaPsiScanner != null) { - javaPsiScanner.visitReference(mContext, v.getVisitor(), - element, referenced); - } - } - } - } - } - } - - super.visitReferenceElement(element); - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression node) { - if (mVisitResources) { - // R.type.name - if (node.getQualifier() instanceof PsiReferenceExpression) { - PsiReferenceExpression select = (PsiReferenceExpression) node.getQualifier(); - if (select.getQualifier() instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) select.getQualifier(); - if (R_CLASS.equals(reference.getReferenceName())) { - String typeName = select.getReferenceName(); - String name = node.getReferenceName(); - - ResourceType type = ResourceType.getEnum(typeName); - if (type != null) { - boolean isFramework = - reference.getQualifier() instanceof PsiReferenceExpression - && ANDROID_PKG.equals(((PsiReferenceExpression)reference. - getQualifier()).getReferenceName()); - - for (VisitingDetector v : mResourceFieldDetectors) { - JavaPsiScanner detector = v.getJavaScanner(); - if (detector != null) { - //noinspection ConstantConditions - detector.visitResourceReference(mContext, v.getVisitor(), - node, type, name, isFramework); - } - } - } - - return; - } - } - } - - // Arbitrary packages -- android.R.type.name, foo.bar.R.type.name - if (R_CLASS.equals(node.getReferenceName())) { - PsiElement parent = node.getParent(); - if (parent instanceof PsiReferenceExpression) { - PsiElement grandParent = parent.getParent(); - if (grandParent instanceof PsiReferenceExpression) { - PsiReferenceExpression select = (PsiReferenceExpression) grandParent; - String name = select.getReferenceName(); - PsiElement typeOperand = select.getQualifier(); - if (name != null && typeOperand instanceof PsiReferenceExpression) { - PsiReferenceExpression typeSelect = - (PsiReferenceExpression) typeOperand; - String typeName = typeSelect.getReferenceName(); - ResourceType type = typeName != null - ? ResourceType.getEnum(typeName) - : null; - if (type != null) { - boolean isFramework = node.getQualifier().getText().equals( - ANDROID_PKG); - for (VisitingDetector v : mResourceFieldDetectors) { - JavaPsiScanner detector = v.getJavaScanner(); - if (detector != null) { - detector.visitResourceReference(mContext, - v.getVisitor(), - node, type, name, isFramework); - } - } - } - - return; - } - } - } - } - } - - super.visitReferenceExpression(node); - } - - @Override - public void visitMethodCallExpression(PsiMethodCallExpression node) { - super.visitMethodCallExpression(node); - - if (mVisitMethods) { - String methodName = node.getMethodExpression().getReferenceName(); - if (methodName != null) { - List<VisitingDetector> list = mMethodDetectors.get(methodName); - if (list != null) { - PsiMethod method = node.resolveMethod(); - if (method != null) { - for (VisitingDetector v : list) { - JavaPsiScanner javaPsiScanner = v.getJavaScanner(); - if (javaPsiScanner != null) { - javaPsiScanner.visitMethod(mContext, v.getVisitor(), node, - method); - } - } - } - } - } - } - } - - @Override - public void visitNewExpression(PsiNewExpression node) { - super.visitNewExpression(node); - - if (mVisitConstructors) { - PsiJavaCodeReferenceElement typeReference = node.getClassReference(); - if (typeReference != null) { - String type = typeReference.getQualifiedName(); - if (type != null) { - List<VisitingDetector> list = mConstructorDetectors.get(type); - if (list != null) { - PsiMethod method = node.resolveMethod(); - if (method != null) { - for (VisitingDetector v : list) { - JavaPsiScanner javaPsiScanner = v.getJavaScanner(); - if (javaPsiScanner != null) { - javaPsiScanner.visitConstructor(mContext, - v.getVisitor(), node, method); - } - } - } - } - } - } - } - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaVisitor.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaVisitor.java deleted file mode 100644 index e683aea..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaVisitor.java +++ /dev/null
@@ -1,1400 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.tools.klint.client.api.JavaParser.ResolvedClass; -import com.android.tools.klint.client.api.JavaParser.ResolvedMethod; -import com.android.tools.klint.client.api.JavaParser.ResolvedNode; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaScanner; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.JavaContext; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import lombok.ast.*; -import org.jetbrains.kotlin.utils.ExceptionUtilsKt; - -import java.util.*; - -import static com.android.SdkConstants.ANDROID_PKG; -import static com.android.SdkConstants.R_CLASS; - -/** - * Specialized visitor for running detectors on a Java AST. - * It operates in three phases: - * <ol> - * <li> First, it computes a set of maps where it generates a map from each - * significant AST attribute (such as method call names) to a list - * of detectors to consult whenever that attribute is encountered. - * Examples of "attributes" are method names, Android resource identifiers, - * and general AST node types such as "cast" nodes etc. These are - * defined on the {@link JavaScanner} interface. - * <li> Second, it iterates over the document a single time, delegating to - * the detectors found at each relevant AST attribute. - * <li> Finally, it calls the remaining visitors (those that need to process a - * whole document on their own). - * </ol> - * It also notifies all the detectors before and after the document is processed - * such that they can do pre- and post-processing. - */ -public class JavaVisitor { - /** Default size of lists holding detectors of the same type for a given node type */ - private static final int SAME_TYPE_COUNT = 8; - - private final Map<String, List<VisitingDetector>> mMethodDetectors = - Maps.newHashMapWithExpectedSize(40); - private final Map<String, List<VisitingDetector>> mConstructorDetectors = - Maps.newHashMapWithExpectedSize(12); - private Set<String> mConstructorSimpleNames; - private final List<VisitingDetector> mResourceFieldDetectors = - new ArrayList<VisitingDetector>(); - private final List<VisitingDetector> mAllDetectors; - private final List<VisitingDetector> mFullTreeDetectors; - private final Map<Class<? extends Node>, List<VisitingDetector>> mNodeTypeDetectors = - new HashMap<Class<? extends Node>, List<VisitingDetector>>(16); - private final JavaParser mParser; - private final Map<String, List<VisitingDetector>> mSuperClassDetectors = - new HashMap<String, List<VisitingDetector>>(); - - /** - * Number of fatal exceptions (internal errors, usually from ECJ) we've - * encountered; we don't log each and every one to avoid massive log spam - * in code which triggers this condition - */ - private static int sExceptionCount; - /** Max number of logs to include */ - private static final int MAX_REPORTED_CRASHES = 20; - - JavaVisitor(@NonNull JavaParser parser, @NonNull List<Detector> detectors) { - mParser = parser; - mAllDetectors = new ArrayList<VisitingDetector>(detectors.size()); - mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size()); - - for (Detector detector : detectors) { - VisitingDetector v = new VisitingDetector(detector, (JavaScanner) detector); - mAllDetectors.add(v); - - List<String> applicableSuperClasses = detector.applicableSuperClasses(); - if (applicableSuperClasses != null) { - for (String fqn : applicableSuperClasses) { - List<VisitingDetector> list = mSuperClassDetectors.get(fqn); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mSuperClassDetectors.put(fqn, list); - } - list.add(v); - } - continue; - } - - List<Class<? extends Node>> nodeTypes = detector.getApplicableNodeTypes(); - if (nodeTypes != null) { - for (Class<? extends Node> type : nodeTypes) { - List<VisitingDetector> list = mNodeTypeDetectors.get(type); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mNodeTypeDetectors.put(type, list); - } - list.add(v); - } - } - - List<String> names = detector.getApplicableMethodNames(); - if (names != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert names != XmlScanner.ALL; - - for (String name : names) { - List<VisitingDetector> list = mMethodDetectors.get(name); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mMethodDetectors.put(name, list); - } - list.add(v); - } - } - - List<String> types = detector.getApplicableConstructorTypes(); - if (types != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert types != XmlScanner.ALL; - if (mConstructorSimpleNames == null) { - mConstructorSimpleNames = Sets.newHashSet(); - } - for (String type : types) { - List<VisitingDetector> list = mConstructorDetectors.get(type); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mConstructorDetectors.put(type, list); - mConstructorSimpleNames.add(type.substring(type.lastIndexOf('.')+1)); - } - list.add(v); - } - } - - if (detector.appliesToResourceRefs()) { - mResourceFieldDetectors.add(v); - } else if ((names == null || names.isEmpty()) - && (nodeTypes == null || nodeTypes.isEmpty()) - && (types == null || types.isEmpty())) { - mFullTreeDetectors.add(v); - } - } - } - - void visitFile(@NonNull JavaContext context) { - Node compilationUnit = null; - try { - compilationUnit = mParser.parseJava(context); - if (compilationUnit == null) { - // No need to log this; the parser should be reporting - // a full warning (such as IssueRegistry#PARSER_ERROR) - // with details, location, etc. - return; - } - context.setCompilationUnit(compilationUnit); - - for (VisitingDetector v : mAllDetectors) { - v.setContext(context); - v.getDetector().beforeCheckFile(context); - } - - if (!mSuperClassDetectors.isEmpty()) { - SuperclassVisitor visitor = new SuperclassVisitor(context); - compilationUnit.accept(visitor); - } - - for (VisitingDetector v : mFullTreeDetectors) { - AstVisitor visitor = v.getVisitor(); - compilationUnit.accept(visitor); - } - - if (!mMethodDetectors.isEmpty() || !mResourceFieldDetectors.isEmpty() || - !mConstructorDetectors.isEmpty()) { - AstVisitor visitor = new DelegatingJavaVisitor(context); - compilationUnit.accept(visitor); - } else if (!mNodeTypeDetectors.isEmpty()) { - AstVisitor visitor = new DispatchVisitor(); - compilationUnit.accept(visitor); - } - - for (VisitingDetector v : mAllDetectors) { - v.getDetector().afterCheckFile(context); - } - } catch (RuntimeException e) { - if (sExceptionCount++ > MAX_REPORTED_CRASHES) { - // No need to keep spamming the user that a lot of the files - // are tripping up ECJ, they get the picture. - return; - } - - if (e.getClass().getSimpleName().equals("IndexNotReadyException")) { - // Attempting to access PSI during startup before indices are ready; ignore these. - // See http://b.android.com/176644 for an example. - return; - } else if (ExceptionUtilsKt.isProcessCanceledException(e)) { - // Cancelling inspections in the IDE - context.getDriver().cancel(); - return; - } - - // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268 - // Don't allow lint bugs to take down the whole build. TRY to log this as a - // lint error instead! - StringBuilder sb = new StringBuilder(100); - sb.append("Unexpected failure during lint analysis of "); - sb.append(context.file.getName()); - sb.append(" (this is a bug in lint or one of the libraries it depends on)\n"); - - sb.append(e.getClass().getSimpleName()); - sb.append(':'); - StackTraceElement[] stackTrace = e.getStackTrace(); - int count = 0; - for (StackTraceElement frame : stackTrace) { - if (count > 0) { - sb.append("<-"); - } - - String className = frame.getClassName(); - sb.append(className.substring(className.lastIndexOf('.') + 1)); - sb.append('.').append(frame.getMethodName()); - sb.append('('); - sb.append(frame.getFileName()).append(':').append(frame.getLineNumber()); - sb.append(')'); - count++; - // Only print the top 3-4 frames such that we can identify the bug - if (count == 4) { - break; - } - } - Throwable throwable = null; // NOT e: this makes for very noisy logs - //noinspection ConstantConditions - context.log(throwable, sb.toString()); - } finally { - if (compilationUnit != null) { - mParser.dispose(context, compilationUnit); - } - } - } - - /** - * For testing only: returns the number of exceptions thrown during Java AST analysis - * - * @return the number of internal errors found - */ - @VisibleForTesting - public static int getCrashCount() { - return sExceptionCount; - } - - /** - * For testing only: clears the crash counter - */ - @VisibleForTesting - public static void clearCrashCount() { - sExceptionCount = 0; - } - - public void prepare(@NonNull List<JavaContext> contexts) { - mParser.prepareJavaParse(contexts); - } - - public void dispose() { - mParser.dispose(); - } - - @Nullable - private static Set<String> getInterfaceNames( - @Nullable Set<String> addTo, - @NonNull ResolvedClass cls) { - Iterable<ResolvedClass> interfaces = cls.getInterfaces(); - for (ResolvedClass resolvedInterface : interfaces) { - String name = resolvedInterface.getName(); - if (addTo == null) { - addTo = Sets.newHashSet(); - } else if (addTo.contains(name)) { - // Superclasses can explicitly implement the same interface, - // so keep track of visited interfaces as we traverse up the - // super class chain to avoid checking the same interface - // more than once. - continue; - } - addTo.add(name); - getInterfaceNames(addTo, resolvedInterface); - } - - return addTo; - } - - private static class VisitingDetector { - private AstVisitor mVisitor; // construct lazily, and clear out on context switch! - private JavaContext mContext; - public final Detector mDetector; - public final JavaScanner mJavaScanner; - - public VisitingDetector(@NonNull Detector detector, @NonNull JavaScanner javaScanner) { - mDetector = detector; - mJavaScanner = javaScanner; - } - - @NonNull - public Detector getDetector() { - return mDetector; - } - - @NonNull - public JavaScanner getJavaScanner() { - return mJavaScanner; - } - - public void setContext(@NonNull JavaContext context) { - mContext = context; - - // The visitors are one-per-context, so clear them out here and construct - // lazily only if needed - mVisitor = null; - } - - @NonNull - AstVisitor getVisitor() { - if (mVisitor == null) { - mVisitor = mDetector.createJavaVisitor(mContext); - if (mVisitor == null) { - mVisitor = new ForwardingAstVisitor() { - }; - } - } - return mVisitor; - } - } - - private class SuperclassVisitor extends ForwardingAstVisitor { - private JavaContext mContext; - - public SuperclassVisitor(@NonNull JavaContext context) { - mContext = context; - } - - @Override - public boolean visitClassDeclaration(ClassDeclaration node) { - ResolvedNode resolved = mContext.resolve(node); - if (!(resolved instanceof ResolvedClass)) { - return true; - } - - ResolvedClass resolvedClass = (ResolvedClass) resolved; - ResolvedClass cls = resolvedClass; - int depth = 0; - while (cls != null) { - List<VisitingDetector> list = mSuperClassDetectors.get(cls.getName()); - if (list != null) { - for (VisitingDetector v : list) { - v.getJavaScanner().checkClass(mContext, node, node, resolvedClass); - } - } - - // Check interfaces too - Set<String> interfaceNames = getInterfaceNames(null, cls); - if (interfaceNames != null) { - for (String name : interfaceNames) { - list = mSuperClassDetectors.get(name); - if (list != null) { - for (VisitingDetector v : list) { - v.getJavaScanner().checkClass(mContext, node, node, - resolvedClass); - } - } - } - } - - cls = cls.getSuperClass(); - depth++; - if (depth == 500) { - // Shouldn't happen in practice; this prevents the IDE from - // hanging if the user has accidentally typed in an incorrect - // super class which creates a cycle. - break; - } - } - - return false; - } - - @Override - public boolean visitConstructorInvocation(ConstructorInvocation node) { - NormalTypeBody anonymous = node.astAnonymousClassBody(); - if (anonymous != null) { - ResolvedNode resolved = mContext.resolve(node.astTypeReference()); - if (!(resolved instanceof ResolvedClass)) { - return true; - } - - ResolvedClass resolvedClass = (ResolvedClass) resolved; - ResolvedClass cls = resolvedClass; - while (cls != null) { - List<VisitingDetector> list = mSuperClassDetectors.get(cls.getName()); - if (list != null) { - for (VisitingDetector v : list) { - v.getJavaScanner().checkClass(mContext, null, anonymous, - resolvedClass); - } - } - - // Check interfaces too - Set<String> interfaceNames = getInterfaceNames(null, cls); - if (interfaceNames != null) { - for (String name : interfaceNames) { - list = mSuperClassDetectors.get(name); - if (list != null) { - for (VisitingDetector v : list) { - v.getJavaScanner().checkClass(mContext, null, anonymous, - resolvedClass); - } - } - } - } - - cls = cls.getSuperClass(); - } - } - - return true; - } - - @Override - public boolean visitImportDeclaration(ImportDeclaration node) { - return true; - } - } - - /** - * Generic dispatcher which visits all nodes (once) and dispatches to - * specific visitors for each node. Each visitor typically only wants to - * look at a small part of a tree, such as a method call or a class - * declaration, so this means we avoid visiting all "uninteresting" nodes in - * the tree repeatedly. - */ - private class DispatchVisitor extends ForwardingAstVisitor { - @Override - public void endVisit(Node node) { - for (VisitingDetector v : mAllDetectors) { - v.getVisitor().endVisit(node); - } - } - - @Override - public boolean visitAlternateConstructorInvocation(AlternateConstructorInvocation node) { - List<VisitingDetector> list = - mNodeTypeDetectors.get(AlternateConstructorInvocation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAlternateConstructorInvocation(node); - } - } - return false; - } - - @Override - public boolean visitAnnotation(Annotation node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Annotation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotation(node); - } - } - return false; - } - - @Override - public boolean visitAnnotationDeclaration(AnnotationDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotationDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitAnnotationElement(AnnotationElement node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationElement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotationElement(node); - } - } - return false; - } - - @Override - public boolean visitAnnotationMethodDeclaration(AnnotationMethodDeclaration node) { - List<VisitingDetector> list = - mNodeTypeDetectors.get(AnnotationMethodDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotationMethodDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitAnnotationValueArray(AnnotationValueArray node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationValueArray.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotationValueArray(node); - } - } - return false; - } - - @Override - public boolean visitArrayAccess(ArrayAccess node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayAccess.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitArrayAccess(node); - } - } - return false; - } - - @Override - public boolean visitArrayCreation(ArrayCreation node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayCreation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitArrayCreation(node); - } - } - return false; - } - - @Override - public boolean visitArrayDimension(ArrayDimension node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayDimension.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitArrayDimension(node); - } - } - return false; - } - - @Override - public boolean visitArrayInitializer(ArrayInitializer node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayInitializer.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitArrayInitializer(node); - } - } - return false; - } - - @Override - public boolean visitAssert(Assert node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Assert.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAssert(node); - } - } - return false; - } - - @Override - public boolean visitBinaryExpression(BinaryExpression node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(BinaryExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBinaryExpression(node); - } - } - return false; - } - - @Override - public boolean visitBlock(Block node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Block.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBlock(node); - } - } - return false; - } - - @Override - public boolean visitBooleanLiteral(BooleanLiteral node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(BooleanLiteral.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBooleanLiteral(node); - } - } - return false; - } - - @Override - public boolean visitBreak(Break node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Break.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBreak(node); - } - } - return false; - } - - @Override - public boolean visitCase(Case node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Case.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCase(node); - } - } - return false; - } - - @Override - public boolean visitCast(Cast node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Cast.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCast(node); - } - } - return false; - } - - @Override - public boolean visitCatch(Catch node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Catch.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCatch(node); - } - } - return false; - } - - @Override - public boolean visitCharLiteral(CharLiteral node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(CharLiteral.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCharLiteral(node); - } - } - return false; - } - - @Override - public boolean visitClassDeclaration(ClassDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ClassDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitClassDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitClassLiteral(ClassLiteral node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ClassLiteral.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitClassLiteral(node); - } - } - return false; - } - - @Override - public boolean visitComment(Comment node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Comment.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitComment(node); - } - } - return false; - } - - @Override - public boolean visitCompilationUnit(CompilationUnit node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(CompilationUnit.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCompilationUnit(node); - } - } - return false; - } - - @Override - public boolean visitConstructorDeclaration(ConstructorDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitConstructorDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitConstructorInvocation(ConstructorInvocation node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorInvocation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitConstructorInvocation(node); - } - } - return false; - } - - @Override - public boolean visitContinue(Continue node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Continue.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitContinue(node); - } - } - return false; - } - - @Override - public boolean visitDefault(Default node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Default.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDefault(node); - } - } - return false; - } - - @Override - public boolean visitDoWhile(DoWhile node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(DoWhile.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDoWhile(node); - } - } - return false; - } - - @Override - public boolean visitEmptyDeclaration(EmptyDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEmptyDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitEmptyStatement(EmptyStatement node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEmptyStatement(node); - } - } - return false; - } - - @Override - public boolean visitEnumConstant(EnumConstant node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(EnumConstant.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEnumConstant(node); - } - } - return false; - } - - @Override - public boolean visitEnumDeclaration(EnumDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(EnumDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEnumDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitEnumTypeBody(EnumTypeBody node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(EnumTypeBody.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitEnumTypeBody(node); - } - } - return false; - } - - @Override - public boolean visitExpressionStatement(ExpressionStatement node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ExpressionStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitExpressionStatement(node); - } - } - return false; - } - - @Override - public boolean visitFloatingPointLiteral(FloatingPointLiteral node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(FloatingPointLiteral.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitFloatingPointLiteral(node); - } - } - return false; - } - - @Override - public boolean visitFor(For node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(For.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitFor(node); - } - } - return false; - } - - @Override - public boolean visitForEach(ForEach node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ForEach.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitForEach(node); - } - } - return false; - } - - @Override - public boolean visitIdentifier(Identifier node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Identifier.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitIdentifier(node); - } - } - return false; - } - - @Override - public boolean visitIf(If node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(If.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitIf(node); - } - } - return false; - } - - @Override - public boolean visitImportDeclaration(ImportDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(ImportDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitImportDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitInlineIfExpression(InlineIfExpression node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(InlineIfExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitInlineIfExpression(node); - } - } - return false; - } - - @Override - public boolean visitInstanceInitializer(InstanceInitializer node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceInitializer.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitInstanceInitializer(node); - } - } - return false; - } - - @Override - public boolean visitInstanceOf(InstanceOf node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceOf.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitInstanceOf(node); - } - } - return false; - } - - @Override - public boolean visitIntegralLiteral(IntegralLiteral node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(IntegralLiteral.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitIntegralLiteral(node); - } - } - return false; - } - - @Override - public boolean visitInterfaceDeclaration(InterfaceDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(InterfaceDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitInterfaceDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitKeywordModifier(KeywordModifier node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(KeywordModifier.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitKeywordModifier(node); - } - } - return false; - } - - @Override - public boolean visitLabelledStatement(LabelledStatement node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(LabelledStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLabelledStatement(node); - } - } - return false; - } - - @Override - public boolean visitMethodDeclaration(MethodDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(MethodDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitMethodDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitMethodInvocation(MethodInvocation node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(MethodInvocation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitMethodInvocation(node); - } - } - return false; - } - - @Override - public boolean visitModifiers(Modifiers node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Modifiers.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitModifiers(node); - } - } - return false; - } - - @Override - public boolean visitNormalTypeBody(NormalTypeBody node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(NormalTypeBody.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitNormalTypeBody(node); - } - } - return false; - } - - @Override - public boolean visitNullLiteral(NullLiteral node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(NullLiteral.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitNullLiteral(node); - } - } - return false; - } - - @Override - public boolean visitPackageDeclaration(PackageDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(PackageDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPackageDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitParseArtefact(Node node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Node.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitParseArtefact(node); - } - } - return false; - } - - @Override - public boolean visitReturn(Return node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Return.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReturn(node); - } - } - return false; - } - - @Override - public boolean visitSelect(Select node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Select.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSelect(node); - } - } - return false; - } - - @Override - public boolean visitStaticInitializer(StaticInitializer node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(StaticInitializer.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitStaticInitializer(node); - } - } - return false; - } - - @Override - public boolean visitStringLiteral(StringLiteral node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(StringLiteral.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitStringLiteral(node); - } - } - return false; - } - - @Override - public boolean visitSuper(Super node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Super.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSuper(node); - } - } - return false; - } - - @Override - public boolean visitSuperConstructorInvocation(SuperConstructorInvocation node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(SuperConstructorInvocation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSuperConstructorInvocation(node); - } - } - return false; - } - - @Override - public boolean visitSwitch(Switch node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Switch.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSwitch(node); - } - } - return false; - } - - @Override - public boolean visitSynchronized(Synchronized node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Synchronized.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSynchronized(node); - } - } - return false; - } - - @Override - public boolean visitThis(This node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(This.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitThis(node); - } - } - return false; - } - - @Override - public boolean visitThrow(Throw node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Throw.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitThrow(node); - } - } - return false; - } - - @Override - public boolean visitTry(Try node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(Try.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTry(node); - } - } - return false; - } - - @Override - public boolean visitTypeReference(TypeReference node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReference.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeReference(node); - } - } - return false; - } - - @Override - public boolean visitTypeReferencePart(TypeReferencePart node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReferencePart.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeReferencePart(node); - } - } - return false; - } - - @Override - public boolean visitTypeVariable(TypeVariable node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(TypeVariable.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeVariable(node); - } - } - return false; - } - - @Override - public boolean visitUnaryExpression(UnaryExpression node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(UnaryExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitUnaryExpression(node); - } - } - return false; - } - - @Override - public boolean visitVariableDeclaration(VariableDeclaration node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDeclaration.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitVariableDeclaration(node); - } - } - return false; - } - - @Override - public boolean visitVariableDefinition(VariableDefinition node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinition.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitVariableDefinition(node); - } - } - return false; - } - - @Override - public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinitionEntry.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitVariableDefinitionEntry(node); - } - } - return false; - } - - @Override - public boolean visitVariableReference(VariableReference node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(VariableReference.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitVariableReference(node); - } - } - return false; - } - - @Override - public boolean visitWhile(While node) { - List<VisitingDetector> list = mNodeTypeDetectors.get(While.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitWhile(node); - } - } - return false; - } - } - - /** Performs common AST searches for method calls and R-type-field references. - * Note that this is a specialized form of the {@link DispatchVisitor}. */ - private class DelegatingJavaVisitor extends DispatchVisitor { - private final JavaContext mContext; - private final boolean mVisitResources; - private final boolean mVisitMethods; - private final boolean mVisitConstructors; - - public DelegatingJavaVisitor(JavaContext context) { - mContext = context; - - mVisitMethods = !mMethodDetectors.isEmpty(); - mVisitConstructors = !mConstructorDetectors.isEmpty(); - mVisitResources = !mResourceFieldDetectors.isEmpty(); - } - - @Override - public boolean visitSelect(Select node) { - if (mVisitResources) { - // R.type.name - if (node.astOperand() instanceof Select) { - Select select = (Select) node.astOperand(); - if (select.astOperand() instanceof VariableReference) { - VariableReference reference = (VariableReference) select.astOperand(); - if (reference.astIdentifier().astValue().equals(R_CLASS)) { - String type = select.astIdentifier().astValue(); - String name = node.astIdentifier().astValue(); - - // R -could- be referenced locally and really have been - // imported as "import android.R;" in the import statements, - // but this is not recommended (and in fact there's a specific - // lint rule warning against it) - boolean isFramework = false; - - for (VisitingDetector v : mResourceFieldDetectors) { - JavaScanner detector = v.getJavaScanner(); - //noinspection ConstantConditions - detector.visitResourceReference(mContext, v.getVisitor(), - node, type, name, isFramework); - } - - return super.visitSelect(node); - } - } - } - - // Arbitrary packages -- android.R.type.name, foo.bar.R.type.name - if (node.astIdentifier().astValue().equals(R_CLASS)) { - Node parent = node.getParent(); - if (parent instanceof Select) { - Node grandParent = parent.getParent(); - if (grandParent instanceof Select) { - Select select = (Select) grandParent; - String name = select.astIdentifier().astValue(); - Expression typeOperand = select.astOperand(); - if (typeOperand instanceof Select) { - Select typeSelect = (Select) typeOperand; - String type = typeSelect.astIdentifier().astValue(); - boolean isFramework = node.astOperand().toString().equals( - ANDROID_PKG); - for (VisitingDetector v : mResourceFieldDetectors) { - JavaScanner detector = v.getJavaScanner(); - detector.visitResourceReference(mContext, v.getVisitor(), - node, type, name, isFramework); - } - } - } - } - } - } - - return super.visitSelect(node); - } - - @Override - public boolean visitMethodInvocation(MethodInvocation node) { - if (mVisitMethods) { - String methodName = node.astName().astValue(); - List<VisitingDetector> list = mMethodDetectors.get(methodName); - if (list != null) { - for (VisitingDetector v : list) { - v.getJavaScanner().visitMethod(mContext, v.getVisitor(), node); - } - } - } - - return super.visitMethodInvocation(node); - } - - @Override - public boolean visitConstructorInvocation(ConstructorInvocation node) { - if (mVisitConstructors) { - TypeReference typeReference = node.astTypeReference(); - if (typeReference != null) { - TypeReferencePart last = typeReference.astParts().last(); - if (last != null) { - String name = last.astIdentifier().astValue(); - if (mConstructorSimpleNames.contains(name)) { - ResolvedNode resolved = mContext.resolve(node); - if (resolved instanceof ResolvedMethod) { - ResolvedMethod method = (ResolvedMethod) resolved; - String type = method.getContainingClass().getName(); - List<VisitingDetector> list = mConstructorDetectors.get(type); - if (list != null) { - for (VisitingDetector v : list) { - v.getJavaScanner().visitConstructor(mContext, - v.getVisitor(), node, method); - } - } - - } - } - } - } - } - - return super.visitConstructorInvocation(node); - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintClient.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintClient.java deleted file mode 100644 index 0b3597f..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintClient.java +++ /dev/null
@@ -1,1255 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import static com.android.SdkConstants.CLASS_FOLDER; -import static com.android.SdkConstants.DOT_AAR; -import static com.android.SdkConstants.DOT_JAR; -import static com.android.SdkConstants.FD_ASSETS; -import static com.android.SdkConstants.GEN_FOLDER; -import static com.android.SdkConstants.LIBS_FOLDER; -import static com.android.SdkConstants.RES_FOLDER; -import static com.android.SdkConstants.SRC_FOLDER; -import static com.android.tools.klint.detector.api.LintUtils.endsWith; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.builder.model.AndroidArtifact; -import com.android.builder.model.AndroidLibrary; -import com.android.builder.model.Dependencies; -import com.android.builder.model.Variant; -import com.android.ide.common.repository.ResourceVisibilityLookup; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceItem; -import com.android.prefs.AndroidLocation; -import com.android.repository.api.ProgressIndicator; -import com.android.repository.api.ProgressIndicatorAdapter; -import com.android.sdklib.BuildToolInfo; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkVersionInfo; -import com.android.sdklib.repository.AndroidSdkHandler; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TextFormat; -import com.android.utils.XmlUtils; -import com.google.common.annotations.Beta; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.common.io.Files; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Information about the tool embedding the lint analyzer. IDEs and other tools - * implementing lint support will extend this to integrate logging, displaying errors, - * etc. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class LintClient { - private static final String PROP_BIN_DIR = "com.android.tools.lint.bindir"; //$NON-NLS-1$ - - protected LintClient(@NonNull String clientName) { - //noinspection AssignmentToStaticFieldFromInstanceMethod - sClientName = clientName; - } - - protected LintClient() { - //noinspection AssignmentToStaticFieldFromInstanceMethod - sClientName = "unknown"; - } - - /** - * Returns a configuration for use by the given project. The configuration - * provides information about which issues are enabled, any customizations - * to the severity of an issue, etc. - * <p> - * By default this method returns a {@link DefaultConfiguration}. - * - * @param project the project to obtain a configuration for - * @param driver the current driver, if any - * @return a configuration, never null. - */ - @NonNull - public Configuration getConfiguration(@NonNull Project project, @Nullable LintDriver driver) { - return DefaultConfiguration.create(this, project, null); - } - - /** - * Report the given issue. This method will only be called if the configuration - * provided by {@link #getConfiguration(Project,LintDriver)} has reported the corresponding - * issue as enabled and has not filtered out the issue with its - * {@link Configuration#ignore(Context,Issue,Location,String)} method. - * <p> - * @param context the context used by the detector when the issue was found - * @param issue the issue that was found - * @param severity the severity of the issue - * @param location the location of the issue - * @param message the associated user message - * @param format the format of the description and location descriptions - */ - public abstract void report( - @NonNull Context context, - @NonNull Issue issue, - @NonNull Severity severity, - @NonNull Location location, - @NonNull String message, - @NonNull TextFormat format); - - /** - * Send an exception or error message (with warning severity) to the log - * - * @param exception the exception, possibly null - * @param format the error message using {@link String#format} syntax, possibly null - * (though in that case the exception should not be null) - * @param args any arguments for the format string - */ - public void log( - @Nullable Throwable exception, - @Nullable String format, - @Nullable Object... args) { - log(Severity.WARNING, exception, format, args); - } - - /** - * Send an exception or error message to the log - * - * @param severity the severity of the warning - * @param exception the exception, possibly null - * @param format the error message using {@link String#format} syntax, possibly null - * (though in that case the exception should not be null) - * @param args any arguments for the format string - */ - public abstract void log( - @NonNull Severity severity, - @Nullable Throwable exception, - @Nullable String format, - @Nullable Object... args); - - /** - * Returns a {@link XmlParser} to use to parse XML - * - * @return a new {@link XmlParser}, or null if this client does not support - * XML analysis - */ - @Nullable - public abstract XmlParser getXmlParser(); - - /** - * Returns a {@link JavaParser} to use to parse Java - * - * @param project the project to parse, if known (this can be used to look up - * the class path for type attribution etc, and it can also be used - * to more efficiently process a set of files, for example to - * perform type attribution for multiple units in a single pass) - * @return a new {@link JavaParser}, or null if this client does not - * support Java analysis - */ - @Nullable - public abstract JavaParser getJavaParser(@Nullable Project project); - - /** - * Returns an optimal detector, if applicable. By default, just returns the - * original detector, but tools can replace detectors using this hook with a version - * that takes advantage of native capabilities of the tool. - * - * @param detectorClass the class of the detector to be replaced - * @return the new detector class, or just the original detector (not null) - */ - @NonNull - public Class<? extends Detector> replaceDetector( - @NonNull Class<? extends Detector> detectorClass) { - return detectorClass; - } - - /** - * Reads the given text file and returns the content as a string - * - * @param file the file to read - * @return the string to return, never null (will be empty if there is an - * I/O error) - */ - @NonNull - public abstract String readFile(@NonNull File file); - - /** - * Reads the given binary file and returns the content as a byte array. - * By default this method will read the bytes from the file directly, - * but this can be customized by a client if for example I/O could be - * held in memory and not flushed to disk yet. - * - * @param file the file to read - * @return the bytes in the file, never null - * @throws IOException if the file does not exist, or if the file cannot be - * read for some reason - */ - @NonNull - public byte[] readBytes(@NonNull File file) throws IOException { - return Files.toByteArray(file); - } - - /** - * Returns the list of source folders for Java source files - * - * @param project the project to look up Java source file locations for - * @return a list of source folders to search for .java files - */ - @NonNull - public List<File> getJavaSourceFolders(@NonNull Project project) { - return getClassPath(project).getSourceFolders(); - } - - /** - * Returns the list of output folders for class files - * - * @param project the project to look up class file locations for - * @return a list of output folders to search for .class files - */ - @NonNull - public List<File> getJavaClassFolders(@NonNull Project project) { - return getClassPath(project).getClassFolders(); - - } - - /** - * Returns the list of Java libraries - * - * @param project the project to look up jar dependencies for - * @param includeProvided If true, included provided libraries too (libraries that are not - * packaged with the app, but are provided for compilation purposes and - * are assumed to be present in the running environment) - * @return a list of jar dependencies containing .class files - */ - @NonNull - public List<File> getJavaLibraries(@NonNull Project project, boolean includeProvided) { - return getClassPath(project).getLibraries(includeProvided); - } - - /** - * Returns the list of source folders for test source files - * - * @param project the project to look up test source file locations for - * @return a list of source folders to search for .java files - */ - @NonNull - public List<File> getTestSourceFolders(@NonNull Project project) { - return getClassPath(project).getTestSourceFolders(); - } - - /** - * Returns the resource folders. - * - * @param project the project to look up the resource folder for - * @return a list of files pointing to the resource folders, possibly empty - */ - @NonNull - public List<File> getResourceFolders(@NonNull Project project) { - File res = new File(project.getDir(), RES_FOLDER); - if (res.exists()) { - return Collections.singletonList(res); - } - - return Collections.emptyList(); - } - - /** - * Returns the asset folders. - * - * @param project the project to look up the asset folder for - * @return a list of files pointing to the asset folders, possibly empty - */ - @NonNull - public List<File> getAssetFolders(@NonNull Project project) { - File assets = new File(project.getDir(), FD_ASSETS); - if (assets.exists()) { - return Collections.singletonList(assets); - } - - return Collections.emptyList(); - } - - /** - * Returns the {@link SdkInfo} to use for the given project. - * - * @param project the project to look up an {@link SdkInfo} for - * @return an {@link SdkInfo} for the project - */ - @NonNull - public SdkInfo getSdkInfo(@NonNull Project project) { - // By default no per-platform SDK info - return new DefaultSdkInfo(); - } - - /** - * Returns a suitable location for storing cache files. Note that the - * directory may not exist. - * - * @param create if true, attempt to create the cache dir if it does not - * exist - * @return a suitable location for storing cache files, which may be null if - * the create flag was false, or if for some reason the directory - * could not be created - */ - @Nullable - public File getCacheDir(boolean create) { - String home = System.getProperty("user.home"); - String relative = ".android" + File.separator + "cache"; //$NON-NLS-1$ //$NON-NLS-2$ - File dir = new File(home, relative); - if (create && !dir.exists()) { - if (!dir.mkdirs()) { - return null; - } - } - return dir; - } - - /** - * Returns the File corresponding to the system property or the environment variable - * for {@link #PROP_BIN_DIR}. - * This property is typically set by the SDK/tools/lint[.bat] wrapper. - * It denotes the path of the wrapper on disk. - * - * @return A new File corresponding to {@link LintClient#PROP_BIN_DIR} or null. - */ - @Nullable - private static File getLintBinDir() { - // First check the Java properties (e.g. set using "java -jar ... -Dname=value") - String path = System.getProperty(PROP_BIN_DIR); - if (path == null || path.isEmpty()) { - // If not found, check environment variables. - path = System.getenv(PROP_BIN_DIR); - } - if (path != null && !path.isEmpty()) { - File file = new File(path); - if (file.exists()) { - return file; - } - } - return null; - } - - /** - * Returns the File pointing to the user's SDK install area. This is generally - * the root directory containing the lint tool (but also platforms/ etc). - * - * @return a file pointing to the user's install area - */ - @Nullable - public File getSdkHome() { - File binDir = getLintBinDir(); - if (binDir != null) { - assert binDir.getName().equals("tools"); - - File root = binDir.getParentFile(); - if (root != null && root.isDirectory()) { - return root; - } - } - - String home = System.getenv("ANDROID_HOME"); //$NON-NLS-1$ - if (home != null) { - return new File(home); - } - - return null; - } - - /** - * Locates an SDK resource (relative to the SDK root directory). - * <p> - * TODO: Consider switching to a {@link URL} return type instead. - * - * @param relativePath A relative path (using {@link File#separator} to - * separate path components) to the given resource - * @return a {@link File} pointing to the resource, or null if it does not - * exist - */ - @Nullable - public File findResource(@NonNull String relativePath) { - File top = getSdkHome(); - if (top == null) { - throw new IllegalArgumentException("Lint must be invoked with the System property " - + PROP_BIN_DIR + " pointing to the ANDROID_SDK tools directory"); - } - - File file = new File(top, relativePath); - if (file.exists()) { - return file; - } else { - return null; - } - } - - private Map<Project, ClassPathInfo> mProjectInfo; - - /** - * Returns true if this project is a Gradle-based Android project - * - * @param project the project to check - * @return true if this is a Gradle-based project - */ - public boolean isGradleProject(Project project) { - // This is not an accurate test; specific LintClient implementations (e.g. - // IDEs or a gradle-integration of lint) have more context and can perform a more accurate - // check - if (new File(project.getDir(), SdkConstants.FN_BUILD_GRADLE).exists()) { - return true; - } - - File parent = project.getDir().getParentFile(); - if (parent != null && parent.getName().equals(SdkConstants.FD_SOURCES)) { - File root = parent.getParentFile(); - if (root != null && new File(root, SdkConstants.FN_BUILD_GRADLE).exists()) { - return true; - } - } - - return false; - } - - /** - * Information about class paths (sources, class files and libraries) - * usually associated with a project. - */ - protected static class ClassPathInfo { - private final List<File> mClassFolders; - private final List<File> mSourceFolders; - private final List<File> mLibraries; - private final List<File> mNonProvidedLibraries; - private final List<File> mTestFolders; - - public ClassPathInfo( - @NonNull List<File> sourceFolders, - @NonNull List<File> classFolders, - @NonNull List<File> libraries, - @NonNull List<File> nonProvidedLibraries, - @NonNull List<File> testFolders) { - mSourceFolders = sourceFolders; - mClassFolders = classFolders; - mLibraries = libraries; - mNonProvidedLibraries = nonProvidedLibraries; - mTestFolders = testFolders; - } - - @NonNull - public List<File> getSourceFolders() { - return mSourceFolders; - } - - @NonNull - public List<File> getClassFolders() { - return mClassFolders; - } - - @NonNull - public List<File> getLibraries(boolean includeProvided) { - return includeProvided ? mLibraries : mNonProvidedLibraries; - } - - public List<File> getTestSourceFolders() { - return mTestFolders; - } - } - - /** - * Considers the given project as an Eclipse project and returns class path - * information for the project - the source folder(s), the output folder and - * any libraries. - * <p> - * Callers will not cache calls to this method, so if it's expensive to compute - * the classpath info, this method should perform its own caching. - * - * @param project the project to look up class path info for - * @return a class path info object, never null - */ - @NonNull - protected ClassPathInfo getClassPath(@NonNull Project project) { - ClassPathInfo info; - if (mProjectInfo == null) { - mProjectInfo = Maps.newHashMap(); - info = null; - } else { - info = mProjectInfo.get(project); - } - - if (info == null) { - List<File> sources = new ArrayList<File>(2); - List<File> classes = new ArrayList<File>(1); - List<File> libraries = new ArrayList<File>(); - // No test folders in Eclipse: - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=224708 - List<File> tests = Collections.emptyList(); - - File projectDir = project.getDir(); - File classpathFile = new File(projectDir, ".classpath"); //$NON-NLS-1$ - if (classpathFile.exists()) { - String classpathXml = readFile(classpathFile); - try { - Document document = XmlUtils.parseDocument(classpathXml, false); - NodeList tags = document.getElementsByTagName("classpathentry"); //$NON-NLS-1$ - for (int i = 0, n = tags.getLength(); i < n; i++) { - Element element = (Element) tags.item(i); - String kind = element.getAttribute("kind"); //$NON-NLS-1$ - List<File> addTo = null; - if (kind.equals("src")) { //$NON-NLS-1$ - addTo = sources; - } else if (kind.equals("output")) { //$NON-NLS-1$ - addTo = classes; - } else if (kind.equals("lib")) { //$NON-NLS-1$ - addTo = libraries; - } - if (addTo != null) { - String path = element.getAttribute("path"); //$NON-NLS-1$ - File folder = new File(projectDir, path); - if (folder.exists()) { - addTo.add(folder); - } - } - } - } catch (Exception e) { - log(null, null); - } - } - - // Add in libraries that aren't specified in the .classpath file - File libs = new File(project.getDir(), LIBS_FOLDER); - if (libs.isDirectory()) { - File[] jars = libs.listFiles(); - if (jars != null) { - for (File jar : jars) { - if (endsWith(jar.getPath(), DOT_JAR) - && !libraries.contains(jar)) { - libraries.add(jar); - } - } - } - } - - if (classes.isEmpty()) { - File folder = new File(projectDir, CLASS_FOLDER); - if (folder.exists()) { - classes.add(folder); - } else { - // Maven checks - folder = new File(projectDir, - "target" + File.separator + "classes"); //$NON-NLS-1$ //$NON-NLS-2$ - if (folder.exists()) { - classes.add(folder); - - // If it's maven, also correct the source path, "src" works but - // it's in a more specific subfolder - if (sources.isEmpty()) { - File src = new File(projectDir, - "src" + File.separator //$NON-NLS-1$ - + "main" + File.separator //$NON-NLS-1$ - + "java"); //$NON-NLS-1$ - if (src.exists()) { - sources.add(src); - } else { - src = new File(projectDir, SRC_FOLDER); - if (src.exists()) { - sources.add(src); - } - } - - File gen = new File(projectDir, - "target" + File.separator //$NON-NLS-1$ - + "generated-sources" + File.separator //$NON-NLS-1$ - + "r"); //$NON-NLS-1$ - if (gen.exists()) { - sources.add(gen); - } - } - } - } - } - - // Fallback, in case there is no Eclipse project metadata here - if (sources.isEmpty()) { - File src = new File(projectDir, SRC_FOLDER); - if (src.exists()) { - sources.add(src); - } - File gen = new File(projectDir, GEN_FOLDER); - if (gen.exists()) { - sources.add(gen); - } - } - - info = new ClassPathInfo(sources, classes, libraries, libraries, tests); - mProjectInfo.put(project, info); - } - - return info; - } - - /** - * A map from directory to existing projects, or null. Used to ensure that - * projects are unique for a directory (in case we process a library project - * before its including project for example) - */ - protected Map<File, Project> mDirToProject; - - /** - * Returns a project for the given directory. This should return the same - * project for the same directory if called repeatedly. - * - * @param dir the directory containing the project - * @param referenceDir See {@link Project#getReferenceDir()}. - * @return a project, never null - */ - @NonNull - public Project getProject(@NonNull File dir, @NonNull File referenceDir) { - if (mDirToProject == null) { - mDirToProject = new HashMap<File, Project>(); - } - - File canonicalDir = dir; - try { - // Attempt to use the canonical handle for the file, in case there - // are symlinks etc present (since when handling library projects, - // we also call getCanonicalFile to compute the result of appending - // relative paths, which can then resolve symlinks and end up with - // a different prefix) - canonicalDir = dir.getCanonicalFile(); - } catch (IOException ioe) { - // pass - } - - Project project = mDirToProject.get(canonicalDir); - if (project != null) { - return project; - } - - project = createProject(dir, referenceDir); - mDirToProject.put(canonicalDir, project); - return project; - } - - /** - * Returns the list of known projects (projects registered via - * {@link #getProject(File, File)} - * - * @return a collection of projects in any order - */ - public Collection<Project> getKnownProjects() { - return mDirToProject != null ? mDirToProject.values() : Collections.<Project>emptyList(); - } - - /** - * Registers the given project for the given directory. This can - * be used when projects are initialized outside of the client itself. - * - * @param dir the directory of the project, which must be unique - * @param project the project - */ - public void registerProject(@NonNull File dir, @NonNull Project project) { - File canonicalDir = dir; - try { - // Attempt to use the canonical handle for the file, in case there - // are symlinks etc present (since when handling library projects, - // we also call getCanonicalFile to compute the result of appending - // relative paths, which can then resolve symlinks and end up with - // a different prefix) - canonicalDir = dir.getCanonicalFile(); - } catch (IOException ioe) { - // pass - } - - - if (mDirToProject == null) { - mDirToProject = new HashMap<File, Project>(); - } else { - assert !mDirToProject.containsKey(dir) : dir; - } - mDirToProject.put(canonicalDir, project); - } - - protected Set<File> mProjectDirs = Sets.newHashSet(); - - /** - * Create a project for the given directory - * @param dir the root directory of the project - * @param referenceDir See {@link Project#getReferenceDir()}. - * @return a new project - */ - @NonNull - protected Project createProject(@NonNull File dir, @NonNull File referenceDir) { - if (mProjectDirs.contains(dir)) { - throw new CircularDependencyException( - "Circular library dependencies; check your project.properties files carefully"); - } - mProjectDirs.add(dir); - return Project.create(this, dir, referenceDir); - } - - /** - * Returns the name of the given project - * - * @param project the project to look up - * @return the name of the project - */ - @NonNull - public String getProjectName(@NonNull Project project) { - return project.getDir().getName(); - } - - protected IAndroidTarget[] mTargets; - - /** - * Returns all the {@link IAndroidTarget} versions installed in the user's SDK install - * area. - * - * @return all the installed targets - */ - @NonNull - public IAndroidTarget[] getTargets() { - if (mTargets == null) { - AndroidSdkHandler sdkHandler = getSdk(); - if (sdkHandler != null) { - ProgressIndicator logger = getRepositoryLogger(); - Collection<IAndroidTarget> targets = sdkHandler.getAndroidTargetManager(logger) - .getTargets(logger); - mTargets = targets.toArray(new IAndroidTarget[targets.size()]); - } else { - mTargets = new IAndroidTarget[0]; - } - } - - return mTargets; - } - - protected AndroidSdkHandler mSdk; - - /** - * Returns the SDK installation (used to look up platforms etc) - * - * @return the SDK if known - */ - @Nullable - public AndroidSdkHandler getSdk() { - if (mSdk == null) { - File sdkHome = getSdkHome(); - if (sdkHome != null) { - mSdk = AndroidSdkHandler.getInstance(sdkHome); - } - } - - return mSdk; - } - - /** - * Returns the compile target to use for the given project - * - * @param project the project in question - * - * @return the compile target to use to build the given project - */ - @Nullable - public IAndroidTarget getCompileTarget(@NonNull Project project) { - int buildSdk = project.getBuildSdk(); - IAndroidTarget[] targets = getTargets(); - for (int i = targets.length - 1; i >= 0; i--) { - IAndroidTarget target = targets[i]; - if (target.isPlatform() && target.getVersion().getApiLevel() == buildSdk) { - return target; - } - } - - return null; - } - - /** - * Returns the highest known API level. - * - * @return the highest known API level - */ - public int getHighestKnownApiLevel() { - int max = SdkVersionInfo.HIGHEST_KNOWN_STABLE_API; - - for (IAndroidTarget target : getTargets()) { - if (target.isPlatform()) { - int api = target.getVersion().getApiLevel(); - if (api > max && !target.getVersion().isPreview()) { - max = api; - } - } - } - - return max; - } - - /** - * Returns the specific version of the build tools being used for the given project, if known - * - * @param project the project in question - * - * @return the build tools version in use by the project, or null if not known - */ - @Nullable - public BuildToolInfo getBuildTools(@NonNull Project project) { - //TODO - //AndroidSdkHandler sdk = getSdk(); - //// Build systems like Eclipse and ant just use the latest available - //// build tools, regardless of project metadata. In Gradle, this - //// method is overridden to use the actual build tools specified in the - //// project. - //if (sdk != null) { - // IAndroidTarget compileTarget = getCompileTarget(project); - // if (compileTarget != null) { - // return compileTarget.getBuildToolInfo(); - // } - // return sdk.getLatestBuildTool(getRepositoryLogger(), false); - //} - - return null; - } - - /** - * Returns the super class for the given class name, which should be in VM - * format (e.g. java/lang/Integer, not java.lang.Integer, and using $ rather - * than . for inner classes). If the super class is not known, returns null. - * <p> - * This is typically not necessary, since lint analyzes all the available - * classes. However, if this lint client is invoking lint in an incremental - * context (for example, an IDE offering incremental analysis of a single - * source file), then lint may not see all the classes, and the client can - * provide its own super class lookup. - * - * @param project the project containing the class - * @param name the fully qualified class name - * @return the corresponding super class name (in VM format), or null if not - * known - */ - @Nullable - public String getSuperClass(@NonNull Project project, @NonNull String name) { - assert name.indexOf('.') == -1 : "Use VM signatures, e.g. java/lang/Integer"; - - if ("java/lang/Object".equals(name)) { //$NON-NLS-1$ - return null; - } - - String superClass = project.getSuperClassMap().get(name); - if (superClass != null) { - return superClass; - } - - for (Project library : project.getAllLibraries()) { - superClass = library.getSuperClassMap().get(name); - if (superClass != null) { - return superClass; - } - } - - return null; - } - - /** - * Creates a super class map for the given project. The map maps from - * internal class name (e.g. java/lang/Integer, not java.lang.Integer) to its - * corresponding super class name. The root class, java/lang/Object, is not in the map. - * - * @param project the project to initialize the super class with; this will include - * local classes as well as any local .jar libraries; not transitive - * dependencies - * @return a map from class to its corresponding super class; never null - */ - @NonNull - public Map<String, String> createSuperClassMap(@NonNull Project project) { - List<File> libraries = project.getJavaLibraries(true); - List<File> classFolders = project.getJavaClassFolders(); - List<ClassEntry> classEntries = ClassEntry.fromClassPath(this, classFolders, true); - if (libraries.isEmpty()) { - return ClassEntry.createSuperClassMap(this, classEntries); - } - List<ClassEntry> libraryEntries = ClassEntry.fromClassPath(this, libraries, true); - return ClassEntry.createSuperClassMap(this, libraryEntries, classEntries); - } - - /** - * Checks whether the given name is a subclass of the given super class. If - * the method does not know, it should return null, and otherwise return - * {@link Boolean#TRUE} or {@link Boolean#FALSE}. - * <p> - * Note that the class names are in internal VM format (java/lang/Integer, - * not java.lang.Integer, and using $ rather than . for inner classes). - * - * @param project the project context to look up the class in - * @param name the name of the class to be checked - * @param superClassName the name of the super class to compare to - * @return true if the class of the given name extends the given super class - */ - @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion") - @Nullable - public Boolean isSubclassOf( - @NonNull Project project, - @NonNull String name, - @NonNull String superClassName) { - return null; - } - - /** - * Finds any custom lint rule jars that should be included for analysis, - * regardless of project. - * <p> - * The default implementation locates custom lint jars in ~/.android/lint/ and - * in $ANDROID_LINT_JARS - * - * @return a list of rule jars (possibly empty). - */ - @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden - @NonNull - public List<File> findGlobalRuleJars() { - // Look for additional detectors registered by the user, via - // (1) an environment variable (useful for build servers etc), and - // (2) via jar files in the .android/lint directory - List<File> files = null; - try { - String androidHome = AndroidLocation.getFolder(); - File lint = new File(androidHome + File.separator + "lint"); //$NON-NLS-1$ - if (lint.exists()) { - File[] list = lint.listFiles(); - if (list != null) { - for (File jarFile : list) { - if (endsWith(jarFile.getName(), DOT_JAR)) { - if (files == null) { - files = new ArrayList<File>(); - } - files.add(jarFile); - } - } - } - } - } catch (AndroidLocation.AndroidLocationException e) { - // Ignore -- no android dir, so no rules to load. - } - - String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$ - if (lintClassPath != null && !lintClassPath.isEmpty()) { - String[] paths = lintClassPath.split(File.pathSeparator); - for (String path : paths) { - File jarFile = new File(path); - if (jarFile.exists()) { - if (files == null) { - files = new ArrayList<File>(); - } else if (files.contains(jarFile)) { - continue; - } - files.add(jarFile); - } - } - } - - return files != null ? files : Collections.<File>emptyList(); - } - - /** - * Finds any custom lint rule jars that should be included for analysis - * in the given project - * - * @param project the project to look up rule jars from - * @return a list of rule jars (possibly empty). - */ - @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden - @NonNull - public List<File> findRuleJars(@NonNull Project project) { - if (project.isGradleProject()) { - if (project.isLibrary()) { - AndroidLibrary model = project.getGradleLibraryModel(); - if (model != null) { - File lintJar = model.getLintJar(); - if (lintJar.exists()) { - return Collections.singletonList(lintJar); - } - } - } else if (project.getSubset() != null) { - // Probably just analyzing a single file: we still want to look for custom - // rules applicable to the file - List<File> rules = null; - final Variant variant = project.getCurrentVariant(); - if (variant != null) { - Collection<AndroidLibrary> libraries = variant.getMainArtifact() - .getDependencies().getLibraries(); - for (AndroidLibrary library : libraries) { - File lintJar = library.getLintJar(); - if (lintJar.exists()) { - if (rules == null) { - rules = Lists.newArrayListWithExpectedSize(4); - } - rules.add(lintJar); - } - } - if (rules != null) { - return rules; - } - } - } else if (project.getDir().getPath().endsWith(DOT_AAR)) { - File lintJar = new File(project.getDir(), "lint.jar"); //$NON-NLS-1$ - if (lintJar.exists()) { - return Collections.singletonList(lintJar); - } - } - } - - return Collections.emptyList(); - } - - /** - * Opens a URL connection. - * - * Clients such as IDEs can override this to for example consider the user's IDE proxy - * settings. - * - * @param url the URL to read - * @return a {@link URLConnection} or null - * @throws IOException if any kind of IO exception occurs - */ - @Nullable - public URLConnection openConnection(@NonNull URL url) throws IOException { - return url.openConnection(); - } - - /** Closes a connection previously returned by {@link #openConnection(URL)} */ - public void closeConnection(@NonNull URLConnection connection) throws IOException { - if (connection instanceof HttpURLConnection) { - ((HttpURLConnection)connection).disconnect(); - } - } - - /** - * Returns true if the given directory is a lint project directory. - * By default, a project directory is the directory containing a manifest file, - * but in Gradle projects for example it's the root gradle directory. - * - * @param dir the directory to check - * @return true if the directory represents a lint project - */ - @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden - public boolean isProjectDirectory(@NonNull File dir) { - return LintUtils.isManifestFolder(dir) || Project.isAospFrameworksRelatedProject(dir); - } - - /** - * Returns whether lint should look for suppress comments. Tools that already do - * this on their own can return false here to avoid doing unnecessary work. - */ - public boolean checkForSuppressComments() { - return true; - } - - /** - * Adds in any custom lint rules and returns the result as a new issue registry, - * or the same one if no custom rules were found - * - * @param registry the main registry to add rules to - * @return a new registry containing the passed in rules plus any custom rules, - * or the original registry if no custom rules were found - */ - public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) { - List<File> jarFiles = findGlobalRuleJars(); - - if (!jarFiles.isEmpty()) { - List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size()); - registries.add(registry); - for (File jarFile : jarFiles) { - try { - registries.add(JarFileIssueRegistry.get(this, jarFile)); - } catch (Throwable e) { - log(e, "Could not load custom rule jar file %1$s", jarFile); - } - } - if (registries.size() > 1) { // the first item is the passed in registry itself - return new CompositeIssueRegistry(registries); - } - } - - return registry; - } - - /** - * Creates a {@link ClassLoader} which can load in a set of Jar files. - * - * @param urls the URLs - * @param parent the parent class loader - * @return a new class loader - */ - public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) { - return new URLClassLoader(urls, parent); - } - - /** - * Returns true if this client supports project resource repository lookup via - * {@link #getProjectResources(Project,boolean)} - * - * @return true if the client can provide project resources - */ - public boolean supportsProjectResources() { - return false; - } - - /** - * Returns the project resources, if available - * - * @param includeDependencies if true, include merged view of all dependencies - * @return the project resources, or null if not available - */ - @Nullable - public AbstractResourceRepository getProjectResources(Project project, - boolean includeDependencies) { - return null; - } - - /** - * For a lint client which supports resource items (via {@link #supportsProjectResources()}) - * return a handle for a resource item - * - * @param item the resource item to look up a location handle for - * @return a corresponding handle - */ - @NonNull - public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) { - return new Location.ResourceItemHandle(item); - } - - private ResourceVisibilityLookup.Provider mResourceVisibility; - - /** - * Returns a shared {@link ResourceVisibilityLookup.Provider} - * - * @return a shared provider for looking up resource visibility - */ - @NonNull - public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() { - if (mResourceVisibility == null) { - mResourceVisibility = new ResourceVisibilityLookup.Provider(); - } - return mResourceVisibility; - } - - /** - * The client name returned by {@link #getClientName()} when running in - * Android Studio/IntelliJ IDEA - */ - public static final String CLIENT_STUDIO = "studio"; - - /** - * The client name returned by {@link #getClientName()} when running in - * Gradle - */ - public static final String CLIENT_GRADLE = "gradle"; - - /** - * The client name returned by {@link #getClientName()} when running in - * the CLI (command line interface) version of lint, {@code lint} - */ - public static final String CLIENT_CLI = "cli"; - - /** - * The client name returned by {@link #getClientName()} when running in - * some unknown client - */ - public static final String CLIENT_UNKNOWN = "unknown"; - - /** The client name. */ - @NonNull - private static String sClientName = CLIENT_UNKNOWN; - - /** - * Returns the name of the embedding client. It could be not just - * {@link #CLIENT_STUDIO}, {@link #CLIENT_GRADLE}, {@link #CLIENT_CLI} - * etc but other values too as lint is integrated in other embedding contexts. - * - * @return the name of the embedding client - */ - @NonNull - public static String getClientName() { - return sClientName; - } - - /** - * Returns true if the embedding client currently running lint is Android Studio - * (or IntelliJ IDEA) - * - * @return true if running in Android Studio / IntelliJ IDEA - */ - public static boolean isStudio() { - return CLIENT_STUDIO.equals(sClientName); - } - - /** - * Returns true if the embedding client currently running lint is Gradle - * - * @return true if running in Gradle - */ - public static boolean isGradle() { - return CLIENT_GRADLE.equals(sClientName); - } - - @NonNull - public ProgressIndicator getRepositoryLogger() { - return new LintClient.RepoLogger(); - } - - private static final class RepoLogger extends ProgressIndicatorAdapter { - // Intentionally not logging these: the SDK manager is - // logging events such as package.xml parsing - // Parsing /path/to/sdk//build-tools/19.1.0/package.xml - // Parsing /path/to/sdk//build-tools/20.0.0/package.xml - // Parsing /path/to/sdk//build-tools/21.0.0/package.xml - // which we don't want to spam on the console. - // It's also warning about packages that it's encountering - // multiple times etc; that's not something we should include - // in lint command line output. - - @Override - public void logError(@NonNull String s, @Nullable Throwable e) { - } - - @Override - public void logInfo(@NonNull String s) { - } - - @Override - public void logWarning(@NonNull String s, @Nullable Throwable e) { - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintDriver.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintDriver.java deleted file mode 100644 index aafa8ab..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintDriver.java +++ /dev/null
@@ -1,2906 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import static com.android.SdkConstants.ATTR_IGNORE; -import static com.android.SdkConstants.CLASS_CONSTRUCTOR; -import static com.android.SdkConstants.CONSTRUCTOR_NAME; -import static com.android.SdkConstants.DOT_CLASS; -import static com.android.SdkConstants.DOT_JAR; -import static com.android.SdkConstants.DOT_JAVA; -import static com.android.SdkConstants.FD_GRADLE_WRAPPER; -import static com.android.SdkConstants.FN_GRADLE_WRAPPER_PROPERTIES; -import static com.android.SdkConstants.FN_LOCAL_PROPERTIES; -import static com.android.SdkConstants.FQCN_SUPPRESS_LINT; -import static com.android.SdkConstants.RES_FOLDER; -import static com.android.SdkConstants.SUPPRESS_ALL; -import static com.android.SdkConstants.SUPPRESS_LINT; -import static com.android.SdkConstants.TOOLS_URI; -import static com.android.ide.common.resources.configuration.FolderConfiguration.QUALIFIER_SPLITTER; -import static com.android.tools.klint.detector.api.LintUtils.isAnonymousClass; -import static java.io.File.separator; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.repository.ResourceVisibilityLookup; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceItem; -import com.android.resources.ResourceFolderType; -import com.android.sdklib.BuildToolInfo; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.repository.AndroidSdkHandler; -import com.android.tools.klint.client.api.LintListener.EventType; -import com.android.tools.klint.detector.api.ClassContext; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.ResourceContext; -import com.android.tools.klint.detector.api.ResourceXmlDetector; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TextFormat; -import com.android.tools.klint.detector.api.XmlContext; -import com.google.common.annotations.Beta; -import com.google.common.base.Joiner; -import com.google.common.base.Objects; -import com.google.common.base.Splitter; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAnnotationMemberValue; -import com.intellij.psi.PsiAnnotationParameterList; -import com.intellij.psi.PsiArrayInitializerExpression; -import com.intellij.psi.PsiArrayInitializerMemberValue; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiLiteral; -import com.intellij.psi.PsiModifierList; -import com.intellij.psi.PsiModifierListOwner; -import com.intellij.psi.PsiNameValuePair; - -import org.jetbrains.uast.*; -import org.jetbrains.org.objectweb.asm.ClassReader; -import org.jetbrains.org.objectweb.asm.Opcodes; -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; -import org.jetbrains.org.objectweb.asm.tree.AnnotationNode; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode; -import org.jetbrains.org.objectweb.asm.tree.FieldNode; -import org.jetbrains.org.objectweb.asm.tree.InsnList; -import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; -import org.jetbrains.org.objectweb.asm.tree.MethodNode; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import lombok.ast.Annotation; -import lombok.ast.AnnotationElement; -import lombok.ast.AnnotationMethodDeclaration; -import lombok.ast.AnnotationValue; -import lombok.ast.ArrayInitializer; -import lombok.ast.ConstructorDeclaration; -import lombok.ast.Expression; -import lombok.ast.MethodDeclaration; -import lombok.ast.Modifiers; -import lombok.ast.Node; -import lombok.ast.StrictListAccessor; -import lombok.ast.StringLiteral; -import lombok.ast.TypeDeclaration; -import lombok.ast.TypeReference; -import lombok.ast.VariableDefinition; - -/** - * Analyzes Android projects and files - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class LintDriver { - /** - * Max number of passes to run through the lint runner if requested by - * {@link #requestRepeat} - */ - private static final int MAX_PHASES = 3; - private static final String SUPPRESS_LINT_VMSIG = '/' + SUPPRESS_LINT + ';'; - /** Prefix used by the comment suppress mechanism in Studio/IntelliJ */ - private static final String STUDIO_ID_PREFIX = "AndroidLint"; - - private final LintClient mClient; - private LintRequest mRequest; - private IssueRegistry mRegistry; - private volatile boolean mCanceled; - private EnumSet<Scope> mScope; - private List<? extends Detector> mApplicableDetectors; - private Map<Scope, List<Detector>> mScopeDetectors; - private List<LintListener> mListeners; - private int mPhase; - private List<Detector> mRepeatingDetectors; - private EnumSet<Scope> mRepeatScope; - private Project[] mCurrentProjects; - private Project mCurrentProject; - private boolean mAbbreviating = true; - private boolean mParserErrors; - private Map<Object,Object> mProperties; - /** Whether we need to look for legacy (old Lombok-based Java API) detectors */ - private boolean mRunCompatChecks = true; - - /** - * Creates a new {@link LintDriver} - * - * @param registry The registry containing issues to be checked - * @param client the tool wrapping the analyzer, such as an IDE or a CLI - */ - public LintDriver(@NonNull IssueRegistry registry, @NonNull LintClient client) { - mRegistry = registry; - mClient = new LintClientWrapper(client); - } - - /** Cancels the current lint run as soon as possible */ - public void cancel() { - mCanceled = true; - } - - /** - * Returns the scope for the lint job - * - * @return the scope, never null - */ - @NonNull - public EnumSet<Scope> getScope() { - return mScope; - } - - /** - * Sets the scope for the lint job - * - * @param scope the scope to use - */ - public void setScope(@NonNull EnumSet<Scope> scope) { - mScope = scope; - } - - /** - * Returns the lint client requesting the lint check. This may not be the same - * instance as the one passed in to this driver; lint uses a wrapper which performs - * additional validation to ensure that for example badly behaved detectors which report - * issues that have been disabled will get muted without the real lint client getting - * notified. Thus, this {@link LintClient} is suitable for use by detectors to look - * up a client to for example get location handles from, but tool handling code should - * never try to cast this client back to their original lint client. For the original - * lint client, use {@link LintRequest} instead. - * - * @return the client, never null - */ - @NonNull - public LintClient getClient() { - return mClient; - } - - /** - * Returns the current request, which points to the original files to be checked, - * the original scope, the original {@link LintClient}, as well as the release mode. - * - * @return the request - */ - @NonNull - public LintRequest getRequest() { - return mRequest; - } - - /** - * Records a property for later retrieval by {@link #getProperty(Object)} - * - * @param key the key to associate the value with - * @param value the value, or null to remove a previous binding - */ - public void putProperty(@NonNull Object key, @Nullable Object value) { - if (mProperties == null) { - mProperties = Maps.newHashMap(); - } - if (value == null) { - mProperties.remove(key); - } else { - mProperties.put(key, value); - } - } - - /** - * Returns the property previously stored with the given key, or null - * - * @param key the key - * @return the value or null if not found - */ - @Nullable - public Object getProperty(@NonNull Object key) { - if (mProperties != null) { - return mProperties.get(key); - } - - return null; - } - - /** - * Returns the current phase number. The first pass is numbered 1. Only one pass - * will be performed, unless a {@link Detector} calls {@link #requestRepeat}. - * - * @return the current phase, usually 1 - */ - public int getPhase() { - return mPhase; - } - - /** - * Returns the current {@link IssueRegistry}. - * - * @return the current {@link IssueRegistry} - */ - @NonNull - public IssueRegistry getRegistry() { - return mRegistry; - } - - /** - * Returns the project containing a given file, or null if not found. This searches - * only among the currently checked project and its library projects, not among all - * possible projects being scanned sequentially. - * - * @param file the file to be checked - * @return the corresponding project, or null if not found - */ - @Nullable - public Project findProjectFor(@NonNull File file) { - if (mCurrentProjects != null) { - if (mCurrentProjects.length == 1) { - return mCurrentProjects[0]; - } - String path = file.getPath(); - for (Project project : mCurrentProjects) { - if (path.startsWith(project.getDir().getPath())) { - return project; - } - } - } - - return null; - } - - /** - * Sets whether lint should abbreviate output when appropriate. - * - * @param abbreviating true to abbreviate output, false to include everything - */ - public void setAbbreviating(boolean abbreviating) { - mAbbreviating = abbreviating; - } - - /** - * Returns whether lint should abbreviate output when appropriate. - * - * @return true if lint should abbreviate output, false when including everything - */ - public boolean isAbbreviating() { - return mAbbreviating; - } - - /** - * Returns whether lint has encountered any files with fatal parser errors - * (e.g. broken source code, or even broken parsers) - * <p> - * This is useful for checks that need to make sure they've seen all data in - * order to be conclusive (such as an unused resource check). - * - * @return true if any files were not properly processed because they - * contained parser errors - */ - public boolean hasParserErrors() { - return mParserErrors; - } - - /** - * Sets whether lint has encountered files with fatal parser errors. - * - * @see #hasParserErrors() - * @param hasErrors whether parser errors have been encountered - */ - public void setHasParserErrors(boolean hasErrors) { - mParserErrors = hasErrors; - } - - /** - * Returns the projects being analyzed - * - * @return the projects being analyzed - */ - @NonNull - public List<Project> getProjects() { - if (mCurrentProjects != null) { - return Arrays.asList(mCurrentProjects); - } - return Collections.emptyList(); - } - - /** - * Analyze the given file (which can point to an Android project). Issues found - * are reported to the associated {@link LintClient}. - * - * @param files the files and directories to be analyzed - * @param scope the scope of the analysis; detectors with a wider scope will - * not be run. If null, the scope will be inferred from the files. - * @deprecated use {@link #analyze(LintRequest) instead} - */ - @Deprecated - public void analyze(@NonNull List<File> files, @Nullable EnumSet<Scope> scope) { - analyze(new LintRequest(mClient, files).setScope(scope)); - } - - /** - * Analyze the given files (which can point to Android projects or directories - * containing Android projects). Issues found are reported to the associated - * {@link LintClient}. - * <p> - * Note that the {@link LintDriver} is not multi thread safe or re-entrant; - * if you want to run potentially overlapping lint jobs, create a separate driver - * for each job. - * - * @param request the files and directories to be analyzed - */ - public void analyze(@NonNull LintRequest request) { - try { - mRequest = request; - analyze(); - } finally { - mRequest = null; - } - } - - /** Runs the driver to analyze the requested files */ - private void analyze() { - mCanceled = false; - mScope = mRequest.getScope(); - assert mScope == null || !mScope.contains(Scope.ALL_RESOURCE_FILES) || - mScope.contains(Scope.RESOURCE_FILE); - - Collection<Project> projects; - try { - projects = mRequest.getProjects(); - if (projects == null) { - projects = computeProjects(mRequest.getFiles()); - } - } catch (CircularDependencyException e) { - mCurrentProject = e.getProject(); - if (mCurrentProject != null) { - Location location = e.getLocation(); - File file = location != null ? location.getFile() : mCurrentProject.getDir(); - Context context = new Context(this, mCurrentProject, null, file); - context.report(IssueRegistry.LINT_ERROR, e.getLocation(), e.getMessage()); - mCurrentProject = null; - } - return; - } - if (projects.isEmpty()) { - mClient.log(null, "No projects found for %1$s", mRequest.getFiles().toString()); - return; - } - if (mCanceled) { - return; - } - - if (mScope == null) { - mScope = Scope.infer(projects); - } - - fireEvent(EventType.STARTING, null); - - for (Project project : projects) { - mPhase = 1; - - Project main = mRequest.getMainProject(project); - - // The set of available detectors varies between projects - computeDetectors(project); - - if (mApplicableDetectors.isEmpty()) { - // No detectors enabled in this project: skip it - continue; - } - - checkProject(project, main); - if (mCanceled) { - break; - } - - runExtraPhases(project, main); - } - - fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null); - } - - @Nullable - private Set<Issue> myCustomIssues; - - /** - * Returns true if the given issue is an issue that was loaded as a custom rule - * (e.g. a 3rd-party library provided the detector, it's not built in) - * - * @param issue the issue to be looked up - * @return true if this is a custom (non-builtin) check - */ - public boolean isCustomIssue(@NonNull Issue issue) { - return myCustomIssues != null && myCustomIssues.contains(issue); - } - - private void registerCustomDetectors(Collection<Project> projects) { - // Look at the various projects, and if any of them provide a custom - // lint jar, "add" them (this will replace the issue registry with - // a CompositeIssueRegistry containing the original issue registry - // plus JarFileIssueRegistry instances for each lint jar - Set<File> jarFiles = Sets.newHashSet(); - for (Project project : projects) { - jarFiles.addAll(mClient.findRuleJars(project)); - for (Project library : project.getAllLibraries()) { - jarFiles.addAll(mClient.findRuleJars(library)); - } - } - - jarFiles.addAll(mClient.findGlobalRuleJars()); - - if (!jarFiles.isEmpty()) { - List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size()); - registries.add(mRegistry); - for (File jarFile : jarFiles) { - try { - JarFileIssueRegistry registry = JarFileIssueRegistry.get(mClient, jarFile); - if (registry.hasLegacyDetectors()) { - mRunCompatChecks = true; - } - if (myCustomIssues == null) { - myCustomIssues = Sets.newHashSet(); - } - myCustomIssues.addAll(registry.getIssues()); - registries.add(registry); - } catch (Throwable e) { - mClient.log(e, "Could not load custom rule jar file %1$s", jarFile); - } - } - if (registries.size() > 1) { // the first item is mRegistry itself - mRegistry = new CompositeIssueRegistry(registries); - } - } - } - - private void runExtraPhases(@NonNull Project project, @NonNull Project main) { - // Did any detectors request another phase? - if (mRepeatingDetectors != null) { - // Yes. Iterate up to MAX_PHASES times. - - // During the extra phases, we might be narrowing the scope, and setting it in the - // scope field such that detectors asking about the available scope will get the - // correct result. However, we need to restore it to the original scope when this - // is done in case there are other projects that will be checked after this, since - // the repeated phases is done *per project*, not after all projects have been - // processed. - EnumSet<Scope> oldScope = mScope; - - do { - mPhase++; - fireEvent(EventType.NEW_PHASE, - new Context(this, project, null, project.getDir())); - - // Narrow the scope down to the set of scopes requested by - // the rules. - if (mRepeatScope == null) { - mRepeatScope = Scope.ALL; - } - mScope = Scope.intersect(mScope, mRepeatScope); - if (mScope.isEmpty()) { - break; - } - - // Compute the detectors to use for this pass. - // Unlike the normal computeDetectors(project) call, - // this is going to use the existing instances, and include - // those that apply for the configuration. - computeRepeatingDetectors(mRepeatingDetectors, project); - - if (mApplicableDetectors.isEmpty()) { - // No detectors enabled in this project: skip it - continue; - } - - checkProject(project, main); - if (mCanceled) { - break; - } - } while (mPhase < MAX_PHASES && mRepeatingDetectors != null); - - mScope = oldScope; - } - } - - private void computeRepeatingDetectors(List<Detector> detectors, Project project) { - // Ensure that the current visitor is recomputed - mCurrentFolderType = null; - mCurrentVisitor = null; - mCurrentXmlDetectors = null; - mCurrentBinaryDetectors = null; - - // Create map from detector class to issue such that we can - // compute applicable issues for each detector in the list of detectors - // to be repeated - List<Issue> issues = mRegistry.getIssues(); - Multimap<Class<? extends Detector>, Issue> issueMap = - ArrayListMultimap.create(issues.size(), 3); - for (Issue issue : issues) { - issueMap.put(issue.getImplementation().getDetectorClass(), issue); - } - - Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope = - new HashMap<Class<? extends Detector>, EnumSet<Scope>>(); - Map<Scope, List<Detector>> scopeToDetectors = - new EnumMap<Scope, List<Detector>>(Scope.class); - - List<Detector> detectorList = new ArrayList<Detector>(); - // Compute the list of detectors (narrowed down from mRepeatingDetectors), - // and simultaneously build up the detectorToScope map which tracks - // the scopes each detector is affected by (this is used to populate - // the mScopeDetectors map which is used during iteration). - Configuration configuration = project.getConfiguration(this); - for (Detector detector : detectors) { - Class<? extends Detector> detectorClass = detector.getClass(); - Collection<Issue> detectorIssues = issueMap.get(detectorClass); - if (detectorIssues != null) { - boolean add = false; - for (Issue issue : detectorIssues) { - // The reason we have to check whether the detector is enabled - // is that this is a per-project property, so when running lint in multiple - // projects, a detector enabled only in a different project could have - // requested another phase, and we end up in this project checking whether - // the detector is enabled here. - if (!configuration.isEnabled(issue)) { - continue; - } - - add = true; // Include detector if any of its issues are enabled - - EnumSet<Scope> s = detectorToScope.get(detectorClass); - EnumSet<Scope> issueScope = issue.getImplementation().getScope(); - if (s == null) { - detectorToScope.put(detectorClass, issueScope); - } else if (!s.containsAll(issueScope)) { - EnumSet<Scope> union = EnumSet.copyOf(s); - union.addAll(issueScope); - detectorToScope.put(detectorClass, union); - } - } - - if (add) { - detectorList.add(detector); - EnumSet<Scope> union = detectorToScope.get(detector.getClass()); - for (Scope s : union) { - List<Detector> list = scopeToDetectors.get(s); - if (list == null) { - list = new ArrayList<Detector>(); - scopeToDetectors.put(s, list); - } - list.add(detector); - } - } - } - } - - mApplicableDetectors = detectorList; - mScopeDetectors = scopeToDetectors; - mRepeatingDetectors = null; - mRepeatScope = null; - - validateScopeList(); - } - - private void computeDetectors(@NonNull Project project) { - // Ensure that the current visitor is recomputed - mCurrentFolderType = null; - mCurrentVisitor = null; - - Configuration configuration = project.getConfiguration(this); - mScopeDetectors = new EnumMap<Scope, List<Detector>>(Scope.class); - mApplicableDetectors = mRegistry.createDetectors(mClient, configuration, - mScope, mScopeDetectors); - - validateScopeList(); - } - - /** Development diagnostics only, run with assertions on */ - @SuppressWarnings("all") // Turn off warnings for the intentional assertion side effect below - private void validateScopeList() { - boolean assertionsEnabled = false; - assert assertionsEnabled = true; // Intentional side-effect - if (assertionsEnabled) { - List<Detector> resourceFileDetectors = mScopeDetectors.get(Scope.RESOURCE_FILE); - if (resourceFileDetectors != null) { - for (Detector detector : resourceFileDetectors) { - // This is wrong; it should allow XmlScanner instead of ResourceXmlScanner! - assert detector instanceof ResourceXmlDetector : detector; - } - } - - List<Detector> manifestDetectors = mScopeDetectors.get(Scope.MANIFEST); - if (manifestDetectors != null) { - for (Detector detector : manifestDetectors) { - assert detector instanceof Detector.XmlScanner : detector; - } - } - List<Detector> javaCodeDetectors = mScopeDetectors.get(Scope.ALL_JAVA_FILES); - if (javaCodeDetectors != null) { - for (Detector detector : javaCodeDetectors) { - assert detector instanceof Detector.JavaScanner || - // TODO: Migrate all - detector instanceof Detector.UastScanner || - detector instanceof Detector.JavaPsiScanner : detector; - } - } - List<Detector> javaFileDetectors = mScopeDetectors.get(Scope.JAVA_FILE); - if (javaFileDetectors != null) { - for (Detector detector : javaFileDetectors) { - assert detector instanceof Detector.JavaScanner || - // TODO: Migrate all - detector instanceof Detector.UastScanner || - detector instanceof Detector.JavaPsiScanner : detector; - } - } - - List<Detector> classDetectors = mScopeDetectors.get(Scope.CLASS_FILE); - if (classDetectors != null) { - for (Detector detector : classDetectors) { - assert detector instanceof Detector.ClassScanner : detector; - } - } - - List<Detector> classCodeDetectors = mScopeDetectors.get(Scope.ALL_CLASS_FILES); - if (classCodeDetectors != null) { - for (Detector detector : classCodeDetectors) { - assert detector instanceof Detector.ClassScanner : detector; - } - } - - List<Detector> gradleDetectors = mScopeDetectors.get(Scope.GRADLE_FILE); - if (gradleDetectors != null) { - for (Detector detector : gradleDetectors) { - assert detector instanceof Detector.GradleScanner : detector; - } - } - - List<Detector> otherDetectors = mScopeDetectors.get(Scope.OTHER); - if (otherDetectors != null) { - for (Detector detector : otherDetectors) { - assert detector instanceof Detector.OtherFileScanner : detector; - } - } - - List<Detector> dirDetectors = mScopeDetectors.get(Scope.RESOURCE_FOLDER); - if (dirDetectors != null) { - for (Detector detector : dirDetectors) { - assert detector instanceof Detector.ResourceFolderScanner : detector; - } - } - - List<Detector> binaryDetectors = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE); - if (binaryDetectors != null) { - for (Detector detector : binaryDetectors) { - assert detector instanceof Detector.BinaryResourceScanner : detector; - } - } - } - } - - private void registerProjectFile( - @NonNull Map<File, Project> fileToProject, - @NonNull File file, - @NonNull File projectDir, - @NonNull File rootDir) { - fileToProject.put(file, mClient.getProject(projectDir, rootDir)); - } - - private Collection<Project> computeProjects(@NonNull List<File> files) { - // Compute list of projects - Map<File, Project> fileToProject = new LinkedHashMap<File, Project>(); - - File sharedRoot = null; - - // Ensure that we have absolute paths such that if you lint - // "foo bar" in "baz" we can show baz/ as the root - List<File> absolute = new ArrayList<File>(files.size()); - for (File file : files) { - absolute.add(file.getAbsoluteFile()); - } - // Always use absoluteFiles so that we can check the file's getParentFile() - // which is null if the file is not absolute. - files = absolute; - - if (files.size() > 1) { - sharedRoot = LintUtils.getCommonParent(files); - if (sharedRoot != null && sharedRoot.getParentFile() == null) { // "/" ? - sharedRoot = null; - } - } - - for (File file : files) { - if (file.isDirectory()) { - File rootDir = sharedRoot; - if (rootDir == null) { - rootDir = file; - if (files.size() > 1) { - rootDir = file.getParentFile(); - if (rootDir == null) { - rootDir = file; - } - } - } - - // Figure out what to do with a directory. Note that the meaning of the - // directory can be ambiguous: - // If you pass a directory which is unknown, we don't know if we should - // search upwards (in case you're pointing at a deep java package folder - // within the project), or if you're pointing at some top level directory - // containing lots of projects you want to scan. We attempt to do the - // right thing, which is to see if you're pointing right at a project or - // right within it (say at the src/ or res/) folder, and if not, you're - // hopefully pointing at a project tree that you want to scan recursively. - if (mClient.isProjectDirectory(file)) { - registerProjectFile(fileToProject, file, file, rootDir); - continue; - } else { - File parent = file.getParentFile(); - if (parent != null) { - if (mClient.isProjectDirectory(parent)) { - registerProjectFile(fileToProject, file, parent, parent); - continue; - } else { - parent = parent.getParentFile(); - if (parent != null && mClient.isProjectDirectory(parent)) { - registerProjectFile(fileToProject, file, parent, parent); - continue; - } - } - } - - // Search downwards for nested projects - addProjects(file, fileToProject, rootDir); - } - } else { - // Pointed at a file: Search upwards for the containing project - File parent = file.getParentFile(); - while (parent != null) { - if (mClient.isProjectDirectory(parent)) { - registerProjectFile(fileToProject, file, parent, parent); - break; - } - parent = parent.getParentFile(); - } - } - - if (mCanceled) { - return Collections.emptySet(); - } - } - - for (Map.Entry<File, Project> entry : fileToProject.entrySet()) { - File file = entry.getKey(); - Project project = entry.getValue(); - if (!file.equals(project.getDir())) { - if (file.isDirectory()) { - try { - File dir = file.getCanonicalFile(); - if (dir.equals(project.getDir())) { - continue; - } - } catch (IOException ioe) { - // pass - } - } - - project.addFile(file); - } - } - - // Partition the projects up such that we only return projects that aren't - // included by other projects (e.g. because they are library projects) - - Collection<Project> allProjects = fileToProject.values(); - Set<Project> roots = new HashSet<Project>(allProjects); - for (Project project : allProjects) { - roots.removeAll(project.getAllLibraries()); - } - - // Report issues for all projects that are explicitly referenced. We need to - // do this here, since the project initialization will mark all library - // projects as no-report projects by default. - for (Project project : allProjects) { - // Report issues for all projects explicitly listed or found via a directory - // traversal -- including library projects. - project.setReportIssues(true); - } - - if (LintUtils.assertionsEnabled()) { - // Make sure that all the project directories are unique. This ensures - // that we didn't accidentally end up with different project instances - // for a library project discovered as a directory as well as one - // initialized from the library project dependency list - IdentityHashMap<Project, Project> projects = - new IdentityHashMap<Project, Project>(); - for (Project project : roots) { - projects.put(project, project); - for (Project library : project.getAllLibraries()) { - projects.put(library, library); - } - } - Set<File> dirs = new HashSet<File>(); - for (Project project : projects.keySet()) { - assert !dirs.contains(project.getDir()); - dirs.add(project.getDir()); - } - } - - return roots; - } - - private void addProjects( - @NonNull File dir, - @NonNull Map<File, Project> fileToProject, - @NonNull File rootDir) { - if (mCanceled) { - return; - } - - if (mClient.isProjectDirectory(dir)) { - registerProjectFile(fileToProject, dir, dir, rootDir); - } else { - File[] files = dir.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - addProjects(file, fileToProject, rootDir); - } - } - } - } - } - - private void checkProject(@NonNull Project project, @NonNull Project main) { - File projectDir = project.getDir(); - - Context projectContext = new Context(this, project, null, projectDir); - fireEvent(EventType.SCANNING_PROJECT, projectContext); - - List<Project> allLibraries = project.getAllLibraries(); - Set<Project> allProjects = new HashSet<Project>(allLibraries.size() + 1); - allProjects.add(project); - allProjects.addAll(allLibraries); - mCurrentProjects = allProjects.toArray(new Project[allProjects.size()]); - - mCurrentProject = project; - - for (Detector check : mApplicableDetectors) { - check.beforeCheckProject(projectContext); - if (mCanceled) { - return; - } - } - - assert mCurrentProject == project; - runFileDetectors(project, main); - - if (!Scope.checkSingleFile(mScope)) { - List<Project> libraries = project.getAllLibraries(); - for (Project library : libraries) { - Context libraryContext = new Context(this, library, project, projectDir); - fireEvent(EventType.SCANNING_LIBRARY_PROJECT, libraryContext); - mCurrentProject = library; - - for (Detector check : mApplicableDetectors) { - check.beforeCheckLibraryProject(libraryContext); - if (mCanceled) { - return; - } - } - assert mCurrentProject == library; - - runFileDetectors(library, main); - if (mCanceled) { - return; - } - - assert mCurrentProject == library; - - for (Detector check : mApplicableDetectors) { - check.afterCheckLibraryProject(libraryContext); - if (mCanceled) { - return; - } - } - } - } - - mCurrentProject = project; - - for (Detector check : mApplicableDetectors) { - check.afterCheckProject(projectContext); - if (mCanceled) { - return; - } - } - - if (mCanceled) { - mClient.report( - projectContext, - // Must provide an issue since API guarantees that the issue parameter - IssueRegistry.CANCELLED, - Severity.INFORMATIONAL, - Location.create(project.getDir()), - "Lint canceled by user", TextFormat.RAW); - } - - mCurrentProjects = null; - } - - private void runFileDetectors(@NonNull Project project, @Nullable Project main) { - // Look up manifest information (but not for library projects) - if (project.isAndroidProject()) { - for (File manifestFile : project.getManifestFiles()) { - XmlParser parser = mClient.getXmlParser(); - if (parser != null) { - XmlContext context = new XmlContext(this, project, main, manifestFile, null, - parser); - context.document = parser.parseXml(context); - if (context.document != null) { - try { - project.readManifest(context.document); - - if ((!project.isLibrary() || (main != null - && main.isMergingManifests())) - && mScope.contains(Scope.MANIFEST)) { - List<Detector> detectors = mScopeDetectors.get(Scope.MANIFEST); - if (detectors != null) { - ResourceVisitor v = new ResourceVisitor(parser, detectors, - null); - fireEvent(EventType.SCANNING_FILE, context); - v.visitFile(context, manifestFile); - } - } - } finally { - if (context.document != null) { // else: freed by XmlVisitor above - parser.dispose(context, context.document); - } - } - } - } - } - - // Process both Scope.RESOURCE_FILE and Scope.ALL_RESOURCE_FILES detectors together - // in a single pass through the resource directories. - if (mScope.contains(Scope.ALL_RESOURCE_FILES) - || mScope.contains(Scope.RESOURCE_FILE) - || mScope.contains(Scope.RESOURCE_FOLDER) - || mScope.contains(Scope.BINARY_RESOURCE_FILE)) { - List<Detector> dirChecks = mScopeDetectors.get(Scope.RESOURCE_FOLDER); - List<Detector> binaryChecks = mScopeDetectors.get(Scope.BINARY_RESOURCE_FILE); - List<Detector> checks = union(mScopeDetectors.get(Scope.RESOURCE_FILE), - mScopeDetectors.get(Scope.ALL_RESOURCE_FILES)); - boolean haveXmlChecks = checks != null && !checks.isEmpty(); - List<ResourceXmlDetector> xmlDetectors; - if (haveXmlChecks) { - xmlDetectors = new ArrayList<ResourceXmlDetector>(checks.size()); - for (Detector detector : checks) { - if (detector instanceof ResourceXmlDetector) { - xmlDetectors.add((ResourceXmlDetector) detector); - } - } - haveXmlChecks = !xmlDetectors.isEmpty(); - } else { - xmlDetectors = Collections.emptyList(); - } - if (haveXmlChecks - || dirChecks != null && !dirChecks.isEmpty() - || binaryChecks != null && !binaryChecks.isEmpty()) { - List<File> files = project.getSubset(); - if (files != null) { - checkIndividualResources(project, main, xmlDetectors, dirChecks, - binaryChecks, files); - } else { - List<File> resourceFolders = project.getResourceFolders(); - if (!resourceFolders.isEmpty()) { - for (File res : resourceFolders) { - checkResFolder(project, main, res, xmlDetectors, dirChecks, - binaryChecks); - } - } - } - } - } - - if (mCanceled) { - return; - } - } - - if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) { - List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE), - mScopeDetectors.get(Scope.ALL_JAVA_FILES)); - if (checks != null && !checks.isEmpty()) { - List<File> files = project.getSubset(); - if (files != null) { - checkIndividualJavaFiles(project, main, checks, files); - } else { - List<File> sourceFolders = project.getJavaSourceFolders(); - if (mScope.contains(Scope.TEST_SOURCES)) { - List<File> testFolders = project.getTestSourceFolders(); - if (!testFolders.isEmpty()) { - List<File> combined = Lists.newArrayListWithExpectedSize( - sourceFolders.size() + testFolders.size()); - combined.addAll(sourceFolders); - combined.addAll(testFolders); - sourceFolders = combined; - } - } - - checkJava(project, main, sourceFolders, checks); - - } - } - } - - if (mCanceled) { - return; - } - - if (mScope.contains(Scope.CLASS_FILE) - || mScope.contains(Scope.ALL_CLASS_FILES) - || mScope.contains(Scope.JAVA_LIBRARIES)) { - checkClasses(project, main); - } - - if (mCanceled) { - return; - } - - if (mScope.contains(Scope.GRADLE_FILE)) { - checkBuildScripts(project, main); - } - - if (mCanceled) { - return; - } - - if (mScope.contains(Scope.OTHER)) { - List<Detector> checks = mScopeDetectors.get(Scope.OTHER); - if (checks != null) { - OtherFileVisitor visitor = new OtherFileVisitor(checks); - visitor.scan(this, project, main); - } - } - - if (mCanceled) { - return; - } - - if (project == main && mScope.contains(Scope.PROGUARD_FILE) && - project.isAndroidProject()) { - checkProGuard(project, main); - } - - if (project == main && mScope.contains(Scope.PROPERTY_FILE)) { - checkProperties(project, main); - } - } - - private void checkBuildScripts(Project project, Project main) { - List<Detector> detectors = mScopeDetectors.get(Scope.GRADLE_FILE); - if (detectors != null) { - List<File> files = project.getSubset(); - if (files == null) { - files = project.getGradleBuildScripts(); - } - for (File file : files) { - Context context = new Context(this, project, main, file); - fireEvent(EventType.SCANNING_FILE, context); - for (Detector detector : detectors) { - if (detector.appliesTo(context, file)) { - detector.beforeCheckFile(context); - detector.visitBuildScript(context, Maps.<String, Object>newHashMap()); - detector.afterCheckFile(context); - } - } - } - } - } - - private void checkProGuard(Project project, Project main) { - List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE); - if (detectors != null) { - List<File> files = project.getProguardFiles(); - for (File file : files) { - Context context = new Context(this, project, main, file); - fireEvent(EventType.SCANNING_FILE, context); - for (Detector detector : detectors) { - if (detector.appliesTo(context, file)) { - detector.beforeCheckFile(context); - detector.run(context); - detector.afterCheckFile(context); - } - } - } - } - } - - private void checkProperties(Project project, Project main) { - List<Detector> detectors = mScopeDetectors.get(Scope.PROPERTY_FILE); - if (detectors != null) { - checkPropertyFile(project, main, detectors, FN_LOCAL_PROPERTIES); - checkPropertyFile(project, main, detectors, FD_GRADLE_WRAPPER + separator + - FN_GRADLE_WRAPPER_PROPERTIES); - } - } - - private void checkPropertyFile(Project project, Project main, List<Detector> detectors, - String relativePath) { - File file = new File(project.getDir(), relativePath); - if (file.exists()) { - Context context = new Context(this, project, main, file); - fireEvent(EventType.SCANNING_FILE, context); - for (Detector detector : detectors) { - if (detector.appliesTo(context, file)) { - detector.beforeCheckFile(context); - detector.run(context); - detector.afterCheckFile(context); - } - } - } - } - - /** True if execution has been canceled */ - boolean isCanceled() { - return mCanceled; - } - - /** - * Returns the super class for the given class name, - * which should be in VM format (e.g. java/lang/Integer, not java.lang.Integer). - * If the super class is not known, returns null. This can happen if - * the given class is not a known class according to the project or its - * libraries, for example because it refers to one of the core libraries which - * are not analyzed by lint. - * - * @param name the fully qualified class name - * @return the corresponding super class name (in VM format), or null if not known - */ - @Nullable - public String getSuperClass(@NonNull String name) { - return mClient.getSuperClass(mCurrentProject, name); - } - - /** - * Returns true if the given class is a subclass of the given super class. - * - * @param classNode the class to check whether it is a subclass of the given - * super class name - * @param superClassName the fully qualified super class name (in VM format, - * e.g. java/lang/Integer, not java.lang.Integer. - * @return true if the given class is a subclass of the given super class - */ - public boolean isSubclassOf(@NonNull ClassNode classNode, @NonNull String superClassName) { - if (superClassName.equals(classNode.superName)) { - return true; - } - - if (mCurrentProject != null) { - Boolean isSub = mClient.isSubclassOf(mCurrentProject, classNode.name, superClassName); - if (isSub != null) { - return isSub; - } - } - - String className = classNode.name; - while (className != null) { - if (className.equals(superClassName)) { - return true; - } - className = getSuperClass(className); - } - - return false; - } - @Nullable - private static List<Detector> union( - @Nullable List<Detector> list1, - @Nullable List<Detector> list2) { - if (list1 == null) { - return list2; - } else if (list2 == null) { - return list1; - } else { - // Use set to pick out unique detectors, since it's possible for there to be overlap, - // e.g. the DuplicateIdDetector registers both a cross-resource issue and a - // single-file issue, so it shows up on both scope lists: - Set<Detector> set = new HashSet<Detector>(list1.size() + list2.size()); - set.addAll(list1); - set.addAll(list2); - - return new ArrayList<Detector>(set); - } - } - - /** Check the classes in this project (and if applicable, in any library projects */ - private void checkClasses(Project project, Project main) { - List<File> files = project.getSubset(); - if (files != null) { - checkIndividualClassFiles(project, main, files); - return; - } - - // We need to read in all the classes up front such that we can initialize - // the parent chains (such that for example for a virtual dispatch, we can - // also check the super classes). - - List<File> libraries = project.getJavaLibraries(false); - List<ClassEntry> libraryEntries = ClassEntry.fromClassPath(mClient, libraries, true); - - List<File> classFolders = project.getJavaClassFolders(); - List<ClassEntry> classEntries; - if (classFolders.isEmpty()) { - String message = String.format("No `.class` files were found in project \"%1$s\", " - + "so none of the classfile based checks could be run. " - + "Does the project need to be built first?", project.getName()); - Location location = Location.create(project.getDir()); - mClient.report(new Context(this, project, main, project.getDir()), - IssueRegistry.LINT_ERROR, - project.getConfiguration(this).getSeverity(IssueRegistry.LINT_ERROR), - location, message, TextFormat.RAW); - classEntries = Collections.emptyList(); - } else { - classEntries = ClassEntry.fromClassPath(mClient, classFolders, true); - } - - // Actually run the detectors. Libraries should be called before the - // main classes. - runClassDetectors(Scope.JAVA_LIBRARIES, libraryEntries, project, main); - - if (mCanceled) { - return; - } - - runClassDetectors(Scope.CLASS_FILE, classEntries, project, main); - runClassDetectors(Scope.ALL_CLASS_FILES, classEntries, project, main); - } - - private void checkIndividualClassFiles( - @NonNull Project project, - @Nullable Project main, - @NonNull List<File> files) { - List<File> classFiles = Lists.newArrayListWithExpectedSize(files.size()); - List<File> classFolders = project.getJavaClassFolders(); - if (!classFolders.isEmpty()) { - for (File file : files) { - String path = file.getPath(); - if (file.isFile() && path.endsWith(DOT_CLASS)) { - classFiles.add(file); - } - } - } - - List<ClassEntry> entries = ClassEntry.fromClassFiles(mClient, classFiles, classFolders, - true); - if (!entries.isEmpty()) { - Collections.sort(entries); - runClassDetectors(Scope.CLASS_FILE, entries, project, main); - } - } - - /** - * Stack of {@link ClassNode} nodes for outer classes of the currently - * processed class, including that class itself. Populated by - * {@link #runClassDetectors(Scope, List, Project, Project)} and used by - * {@link #getOuterClassNode(ClassNode)} - */ - private Deque<ClassNode> mOuterClasses; - - private void runClassDetectors(Scope scope, List<ClassEntry> entries, - Project project, Project main) { - if (mScope.contains(scope)) { - List<Detector> classDetectors = mScopeDetectors.get(scope); - if (classDetectors != null && !classDetectors.isEmpty() && !entries.isEmpty()) { - AsmVisitor visitor = new AsmVisitor(mClient, classDetectors); - - String sourceContents = null; - String sourceName = ""; - mOuterClasses = new ArrayDeque<ClassNode>(); - ClassEntry prev = null; - for (ClassEntry entry : entries) { - if (prev != null && prev.compareTo(entry) == 0) { - // Duplicate entries for some reason: ignore - continue; - } - prev = entry; - - ClassReader reader; - ClassNode classNode; - try { - reader = new ClassReader(entry.bytes); - classNode = new ClassNode(); - reader.accept(classNode, 0 /* flags */); - } catch (Throwable t) { - mClient.log(null, "Error processing %1$s: broken class file?", - entry.path()); - continue; - } - - ClassNode peek; - while ((peek = mOuterClasses.peek()) != null) { - if (classNode.name.startsWith(peek.name)) { - break; - } else { - mOuterClasses.pop(); - } - } - mOuterClasses.push(classNode); - - if (isSuppressed(null, classNode)) { - // Class was annotated with suppress all -- no need to look any further - continue; - } - - if (sourceContents != null) { - // Attempt to reuse the source buffer if initialized - // This means making sure that the source files - // foo/bar/MyClass and foo/bar/MyClass$Bar - // and foo/bar/MyClass$3 and foo/bar/MyClass$3$1 have the same prefix. - String newName = classNode.name; - int newRootLength = newName.indexOf('$'); - if (newRootLength == -1) { - newRootLength = newName.length(); - } - int oldRootLength = sourceName.indexOf('$'); - if (oldRootLength == -1) { - oldRootLength = sourceName.length(); - } - if (newRootLength != oldRootLength || - !sourceName.regionMatches(0, newName, 0, newRootLength)) { - sourceContents = null; - } - } - - ClassContext context = new ClassContext(this, project, main, - entry.file, entry.jarFile, entry.binDir, entry.bytes, - classNode, scope == Scope.JAVA_LIBRARIES /*fromLibrary*/, - sourceContents); - - try { - visitor.runClassDetectors(context); - } catch (Exception e) { - mClient.log(e, null); - } - - if (mCanceled) { - return; - } - - sourceContents = context.getSourceContents(false/*read*/); - sourceName = classNode.name; - } - - mOuterClasses = null; - } - } - } - - /** Returns the outer class node of the given class node - * @param classNode the inner class node - * @return the outer class node */ - public ClassNode getOuterClassNode(@NonNull ClassNode classNode) { - String outerName = classNode.outerClass; - - Iterator<ClassNode> iterator = mOuterClasses.iterator(); - while (iterator.hasNext()) { - ClassNode node = iterator.next(); - if (outerName != null) { - if (node.name.equals(outerName)) { - return node; - } - } else if (node == classNode) { - return iterator.hasNext() ? iterator.next() : null; - } - } - - return null; - } - - /** - * Returns the {@link ClassNode} corresponding to the given type, if possible, or null - * - * @param type the fully qualified type, using JVM signatures (/ and $, not . as path - * separators) - * @param flags the ASM flags to pass to the {@link ClassReader}, normally 0 but can - * for example be {@link ClassReader#SKIP_CODE} and/oor - * {@link ClassReader#SKIP_DEBUG} - * @return the class node for the type, or null - */ - @Nullable - public ClassNode findClass(@NonNull ClassContext context, @NonNull String type, int flags) { - String relative = type.replace('/', File.separatorChar) + DOT_CLASS; - File classFile = findClassFile(context.getProject(), relative); - if (classFile != null) { - if (classFile.getPath().endsWith(DOT_JAR)) { - // TODO: Handle .jar files - return null; - } - - try { - byte[] bytes = mClient.readBytes(classFile); - ClassReader reader = new ClassReader(bytes); - ClassNode classNode = new ClassNode(); - reader.accept(classNode, flags); - - return classNode; - } catch (Throwable t) { - mClient.log(null, "Error processing %1$s: broken class file?", - classFile.getPath()); - } - } - - return null; - } - - @Nullable - private File findClassFile(@NonNull Project project, String relativePath) { - for (File root : mClient.getJavaClassFolders(project)) { - File path = new File(root, relativePath); - if (path.exists()) { - return path; - } - } - // Search in the libraries - for (File root : mClient.getJavaLibraries(project, true)) { - // TODO: Handle .jar files! - //if (root.getPath().endsWith(DOT_JAR)) { - //} - - File path = new File(root, relativePath); - if (path.exists()) { - return path; - } - } - - // Search dependent projects - for (Project library : project.getDirectLibraries()) { - File path = findClassFile(library, relativePath); - if (path != null) { - return path; - } - } - - return null; - } - - private void checkJava( - @NonNull Project project, - @Nullable Project main, - @NonNull List<File> sourceFolders, - @NonNull List<Detector> checks) { - JavaParser javaParser = mClient.getJavaParser(project); - if (javaParser == null) { - mClient.log(null, "No java parser provided to lint: not running Java checks"); - return; - } - - assert !checks.isEmpty(); - - // Gather all Java source files in a single pass; more efficient. - List<File> sources = new ArrayList<File>(100); - for (File folder : sourceFolders) { - gatherJavaFiles(folder, sources); - } - if (!sources.isEmpty()) { - List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(sources.size()); - for (File file : sources) { - JavaContext context = new JavaContext(this, project, main, file, javaParser); - contexts.add(context); - } - visitJavaFiles(checks, javaParser, contexts); - } - } - - private void visitJavaFiles(@NonNull List<Detector> checks, JavaParser javaParser, - List<JavaContext> contexts) { - // Temporary: we still have some builtin checks that aren't migrated to - // PSI. Until that's complete, remove them from the list here - //List<Detector> scanners = checks; - List<Detector> scanners = Lists.newArrayListWithCapacity(0); - List<Detector> uastScanners = Lists.newArrayListWithCapacity(checks.size()); - for (Detector detector : checks) { - if (detector instanceof Detector.JavaPsiScanner) { - scanners.add(detector); - } else if (detector instanceof Detector.UastScanner) { - uastScanners.add(detector); - } - } - - if (!scanners.isEmpty()) { - JavaPsiVisitor visitor = new JavaPsiVisitor(javaParser, scanners); - visitor.prepare(contexts); - for (JavaContext context : contexts) { - fireEvent(EventType.SCANNING_FILE, context); - visitor.visitFile(context); - if (mCanceled) { - return; - } - } - - visitor.dispose(); - } - - if (!uastScanners.isEmpty()) { - final UElementVisitor uElementVisitor = new UElementVisitor(javaParser, uastScanners); - uElementVisitor.prepare(contexts); - for (final JavaContext context : contexts) { - fireEvent(EventType.SCANNING_FILE, context); - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - uElementVisitor.visitFile(context); - } - }); - if (mCanceled) { - return; - } - } - - uElementVisitor.dispose(); - } - - // Only if the user is using some custom lint rules that haven't been updated - // yet - //noinspection ConstantConditions - if (mRunCompatChecks) { - // Filter the checks to only those that implement JavaScanner - List<Detector> filtered = Lists.newArrayListWithCapacity(checks.size()); - for (Detector detector : checks) { - if (detector instanceof Detector.JavaScanner) { - filtered.add(detector); - } - } - - if (!filtered.isEmpty()) { - List<String> detectorNames = Lists.newArrayListWithCapacity(filtered.size()); - for (Detector detector : filtered) { - detectorNames.add(detector.getClass().getName()); - } - Collections.sort(detectorNames); - - /* Let's not complain quite yet - String message = String.format("Lint found one or more custom checks using its " - + "older Java API; these checks are still run in compatibility mode, " - + "but this causes duplicated parsing, and in the next version lint " - + "will no longer include this legacy mode. Make sure the following " - + "lint detectors are upgraded to the new API: %1$s", - Joiner.on(", ").join(detectorNames)); - JavaContext first = contexts.get(0); - Project project = first.getProject(); - Location location = Location.create(project.getDir()); - mClient.report(first, - IssueRegistry.LINT_ERROR, - project.getConfiguration(this).getSeverity(IssueRegistry.LINT_ERROR), - location, message, TextFormat.RAW); - */ - - - JavaVisitor oldVisitor = new JavaVisitor(javaParser, filtered); - - oldVisitor.prepare(contexts); - for (JavaContext context : contexts) { - fireEvent(EventType.SCANNING_FILE, context); - oldVisitor.visitFile(context); - if (mCanceled) { - return; - } - } - oldVisitor.dispose(); - } - } - } - - private void checkIndividualJavaFiles( - @NonNull Project project, - @Nullable Project main, - @NonNull List<Detector> checks, - @NonNull List<File> files) { - - JavaParser javaParser = mClient.getJavaParser(project); - if (javaParser == null) { - mClient.log(null, "No java parser provided to lint: not running Java checks"); - return; - } - - List<JavaContext> contexts = Lists.newArrayListWithExpectedSize(files.size()); - for (File file : files) { - if (file.isFile() && file.getPath().endsWith(".kt")) { - contexts.add(new JavaContext(this, project, main, file, javaParser)); - } - } - - if (contexts.isEmpty()) { - return; - } - - visitJavaFiles(checks, javaParser, contexts); - } - - private static void gatherJavaFiles(@NonNull File dir, @NonNull List<File> result) { - File[] files = dir.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isFile() && file.getName().endsWith(".java")) { //$NON-NLS-1$ - result.add(file); - } else if (file.isDirectory()) { - gatherJavaFiles(file, result); - } - } - } - } - - private ResourceFolderType mCurrentFolderType; - private List<ResourceXmlDetector> mCurrentXmlDetectors; - private List<Detector> mCurrentBinaryDetectors; - private ResourceVisitor mCurrentVisitor; - - @Nullable - private ResourceVisitor getVisitor( - @NonNull ResourceFolderType type, - @NonNull List<ResourceXmlDetector> checks, - @Nullable List<Detector> binaryChecks) { - if (type != mCurrentFolderType) { - mCurrentFolderType = type; - - // Determine which XML resource detectors apply to the given folder type - List<ResourceXmlDetector> applicableXmlChecks = - new ArrayList<ResourceXmlDetector>(checks.size()); - for (ResourceXmlDetector check : checks) { - if (check.appliesTo(type)) { - applicableXmlChecks.add(check); - } - } - List<Detector> applicableBinaryChecks = null; - if (binaryChecks != null) { - applicableBinaryChecks = new ArrayList<Detector>(binaryChecks.size()); - for (Detector check : binaryChecks) { - if (check.appliesTo(type)) { - applicableBinaryChecks.add(check); - } - } - } - - // If the list of detectors hasn't changed, then just use the current visitor! - if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableXmlChecks) - && Objects.equal(mCurrentBinaryDetectors, applicableBinaryChecks)) { - return mCurrentVisitor; - } - - mCurrentXmlDetectors = applicableXmlChecks; - mCurrentBinaryDetectors = applicableBinaryChecks; - - if (applicableXmlChecks.isEmpty() - && (applicableBinaryChecks == null || applicableBinaryChecks.isEmpty())) { - mCurrentVisitor = null; - return null; - } - - XmlParser parser = mClient.getXmlParser(); - if (parser != null) { - mCurrentVisitor = new ResourceVisitor(parser, applicableXmlChecks, - applicableBinaryChecks); - } else { - mCurrentVisitor = null; - } - } - - return mCurrentVisitor; - } - - private void checkResFolder( - @NonNull Project project, - @Nullable Project main, - @NonNull File res, - @NonNull List<ResourceXmlDetector> xmlChecks, - @Nullable List<Detector> dirChecks, - @Nullable List<Detector> binaryChecks) { - File[] resourceDirs = res.listFiles(); - if (resourceDirs == null) { - return; - } - - // Sort alphabetically such that we can process related folder types at the - // same time, and to have a defined behavior such that detectors can rely on - // predictable ordering, e.g. layouts are seen before menus are seen before - // values, etc (l < m < v). - - Arrays.sort(resourceDirs); - for (File dir : resourceDirs) { - ResourceFolderType type = ResourceFolderType.getFolderType(dir.getName()); - if (type != null) { - checkResourceFolder(project, main, dir, type, xmlChecks, dirChecks, binaryChecks); - } - - if (mCanceled) { - return; - } - } - } - - private void checkResourceFolder( - @NonNull Project project, - @Nullable Project main, - @NonNull File dir, - @NonNull ResourceFolderType type, - @NonNull List<ResourceXmlDetector> xmlChecks, - @Nullable List<Detector> dirChecks, - @Nullable List<Detector> binaryChecks) { - - // Process the resource folder - - if (dirChecks != null && !dirChecks.isEmpty()) { - ResourceContext context = new ResourceContext(this, project, main, dir, type); - String folderName = dir.getName(); - fireEvent(EventType.SCANNING_FILE, context); - for (Detector check : dirChecks) { - if (check.appliesTo(type)) { - check.beforeCheckFile(context); - check.checkFolder(context, folderName); - check.afterCheckFile(context); - } - } - if (binaryChecks == null && xmlChecks.isEmpty()) { - return; - } - } - - File[] files = dir.listFiles(); - if (files == null || files.length <= 0) { - return; - } - - ResourceVisitor visitor = getVisitor(type, xmlChecks, binaryChecks); - if (visitor != null) { // if not, there are no applicable rules in this folder - // Process files in alphabetical order, to ensure stable output - // (for example for the duplicate resource detector) - Arrays.sort(files); - for (File file : files) { - if (LintUtils.isXmlFile(file)) { - XmlContext context = new XmlContext(this, project, main, file, type, - visitor.getParser()); - fireEvent(EventType.SCANNING_FILE, context); - visitor.visitFile(context, file); - } else if (binaryChecks != null && (LintUtils.isBitmapFile(file) || - type == ResourceFolderType.RAW)) { - ResourceContext context = new ResourceContext(this, project, main, file, type); - fireEvent(EventType.SCANNING_FILE, context); - visitor.visitBinaryResource(context); - } - if (mCanceled) { - return; - } - } - } - } - - /** Checks individual resources */ - private void checkIndividualResources( - @NonNull Project project, - @Nullable Project main, - @NonNull List<ResourceXmlDetector> xmlDetectors, - @Nullable List<Detector> dirChecks, - @Nullable List<Detector> binaryChecks, - @NonNull List<File> files) { - for (File file : files) { - if (file.isDirectory()) { - // Is it a resource folder? - ResourceFolderType type = ResourceFolderType.getFolderType(file.getName()); - if (type != null && new File(file.getParentFile(), RES_FOLDER).exists()) { - // Yes. - checkResourceFolder(project, main, file, type, xmlDetectors, dirChecks, - binaryChecks); - } else if (file.getName().equals(RES_FOLDER)) { // Is it the res folder? - // Yes - checkResFolder(project, main, file, xmlDetectors, dirChecks, binaryChecks); - } else { - mClient.log(null, "Unexpected folder %1$s; should be project, " + - "\"res\" folder or resource folder", file.getPath()); - } - } else if (file.isFile() && LintUtils.isXmlFile(file)) { - // Yes, find out its resource type - String folderName = file.getParentFile().getName(); - ResourceFolderType type = ResourceFolderType.getFolderType(folderName); - if (type != null) { - ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks); - if (visitor != null) { - XmlContext context = new XmlContext(this, project, main, file, type, - visitor.getParser()); - fireEvent(EventType.SCANNING_FILE, context); - visitor.visitFile(context, file); - } - } - } else if (binaryChecks != null && file.isFile() && LintUtils.isBitmapFile(file)) { - // Yes, find out its resource type - String folderName = file.getParentFile().getName(); - ResourceFolderType type = ResourceFolderType.getFolderType(folderName); - if (type != null) { - ResourceVisitor visitor = getVisitor(type, xmlDetectors, binaryChecks); - if (visitor != null) { - ResourceContext context = new ResourceContext(this, project, main, file, - type); - fireEvent(EventType.SCANNING_FILE, context); - visitor.visitBinaryResource(context); - if (mCanceled) { - return; - } - } - } - } - } - } - - /** - * Adds a listener to be notified of lint progress - * - * @param listener the listener to be added - */ - public void addLintListener(@NonNull LintListener listener) { - if (mListeners == null) { - mListeners = new ArrayList<LintListener>(1); - } - mListeners.add(listener); - } - - /** - * Removes a listener such that it is no longer notified of progress - * - * @param listener the listener to be removed - */ - public void removeLintListener(@NonNull LintListener listener) { - mListeners.remove(listener); - if (mListeners.isEmpty()) { - mListeners = null; - } - } - - /** Notifies listeners, if any, that the given event has occurred */ - private void fireEvent(@NonNull LintListener.EventType type, @Nullable Context context) { - if (mListeners != null) { - for (LintListener listener : mListeners) { - listener.update(this, type, context); - } - } - } - - /** - * Wrapper around the lint client. This sits in the middle between a - * detector calling for example {@link LintClient#report} and - * the actual embedding tool, and performs filtering etc such that detectors - * and lint clients don't have to make sure they check for ignored issues or - * filtered out warnings. - */ - private class LintClientWrapper extends LintClient { - @NonNull - private final LintClient mDelegate; - - public LintClientWrapper(@NonNull LintClient delegate) { - super(getClientName()); - mDelegate = delegate; - } - - @Override - public void report( - @NonNull Context context, - @NonNull Issue issue, - @NonNull Severity severity, - @NonNull Location location, - @NonNull String message, - @NonNull TextFormat format) { - //noinspection ConstantConditions - if (location == null) { - // Misbehaving third-party lint detectors - assert false : issue; - return; - } - - assert mCurrentProject != null; - if (!mCurrentProject.getReportIssues()) { - return; - } - - Configuration configuration = context.getConfiguration(); - if (!configuration.isEnabled(issue)) { - if (issue != IssueRegistry.PARSER_ERROR && issue != IssueRegistry.LINT_ERROR) { - mDelegate.log(null, "Incorrect detector reported disabled issue %1$s", - issue.toString()); - } - return; - } - - if (configuration.isIgnored(context, issue, location, message)) { - return; - } - - if (severity == Severity.IGNORE) { - return; - } - - mDelegate.report(context, issue, severity, location, message, format); - } - - // Everything else just delegates to the embedding lint client - - @Override - @NonNull - public Configuration getConfiguration(@NonNull Project project, - @Nullable LintDriver driver) { - return mDelegate.getConfiguration(project, driver); - } - - @Override - public void log(@NonNull Severity severity, @Nullable Throwable exception, - @Nullable String format, @Nullable Object... args) { - mDelegate.log(exception, format, args); - } - - @Override - @NonNull - public String readFile(@NonNull File file) { - return mDelegate.readFile(file); - } - - @Override - @NonNull - public byte[] readBytes(@NonNull File file) throws IOException { - return mDelegate.readBytes(file); - } - - @Override - @NonNull - public List<File> getJavaSourceFolders(@NonNull Project project) { - return mDelegate.getJavaSourceFolders(project); - } - - @Override - @NonNull - public List<File> getJavaClassFolders(@NonNull Project project) { - return mDelegate.getJavaClassFolders(project); - } - - @NonNull - @Override - public List<File> getJavaLibraries(@NonNull Project project, boolean includeProvided) { - return mDelegate.getJavaLibraries(project, includeProvided); - } - - @NonNull - @Override - public List<File> getTestSourceFolders(@NonNull Project project) { - return mDelegate.getTestSourceFolders(project); - } - - @Override - public Collection<Project> getKnownProjects() { - return mDelegate.getKnownProjects(); - } - - @Nullable - @Override - public BuildToolInfo getBuildTools(@NonNull Project project) { - return mDelegate.getBuildTools(project); - } - - @NonNull - @Override - public Map<String, String> createSuperClassMap(@NonNull Project project) { - return mDelegate.createSuperClassMap(project); - } - - @NonNull - @Override - public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() { - return mDelegate.getResourceVisibilityProvider(); - } - - @Override - @NonNull - public List<File> getResourceFolders(@NonNull Project project) { - return mDelegate.getResourceFolders(project); - } - - @Override - @Nullable - public XmlParser getXmlParser() { - return mDelegate.getXmlParser(); - } - - @Override - @NonNull - public Class<? extends Detector> replaceDetector( - @NonNull Class<? extends Detector> detectorClass) { - return mDelegate.replaceDetector(detectorClass); - } - - @Override - @NonNull - public SdkInfo getSdkInfo(@NonNull Project project) { - return mDelegate.getSdkInfo(project); - } - - @Override - @NonNull - public Project getProject(@NonNull File dir, @NonNull File referenceDir) { - return mDelegate.getProject(dir, referenceDir); - } - - @Nullable - @Override - public JavaParser getJavaParser(@Nullable Project project) { - return mDelegate.getJavaParser(project); - } - - @Override - public File findResource(@NonNull String relativePath) { - return mDelegate.findResource(relativePath); - } - - @Override - @Nullable - public File getCacheDir(boolean create) { - return mDelegate.getCacheDir(create); - } - - @Override - @NonNull - protected ClassPathInfo getClassPath(@NonNull Project project) { - return mDelegate.getClassPath(project); - } - - @Override - public void log(@Nullable Throwable exception, @Nullable String format, - @Nullable Object... args) { - mDelegate.log(exception, format, args); - } - - @Override - @Nullable - public File getSdkHome() { - return mDelegate.getSdkHome(); - } - - @Override - @NonNull - public IAndroidTarget[] getTargets() { - return mDelegate.getTargets(); - } - - @Nullable - @Override - public AndroidSdkHandler getSdk() { - return mDelegate.getSdk(); - } - - @Nullable - @Override - public IAndroidTarget getCompileTarget(@NonNull Project project) { - return mDelegate.getCompileTarget(project); - } - - @Override - public int getHighestKnownApiLevel() { - return mDelegate.getHighestKnownApiLevel(); - } - - @Override - @Nullable - public String getSuperClass(@NonNull Project project, @NonNull String name) { - return mDelegate.getSuperClass(project, name); - } - - @Override - @Nullable - public Boolean isSubclassOf(@NonNull Project project, @NonNull String name, - @NonNull String superClassName) { - return mDelegate.isSubclassOf(project, name, superClassName); - } - - @Override - @NonNull - public String getProjectName(@NonNull Project project) { - return mDelegate.getProjectName(project); - } - - @Override - public boolean isGradleProject(Project project) { - return mDelegate.isGradleProject(project); - } - - @NonNull - @Override - protected Project createProject(@NonNull File dir, @NonNull File referenceDir) { - return mDelegate.createProject(dir, referenceDir); - } - - @NonNull - @Override - public List<File> findGlobalRuleJars() { - return mDelegate.findGlobalRuleJars(); - } - - @NonNull - @Override - public List<File> findRuleJars(@NonNull Project project) { - return mDelegate.findRuleJars(project); - } - - @Override - public boolean isProjectDirectory(@NonNull File dir) { - return mDelegate.isProjectDirectory(dir); - } - - @Override - public void registerProject(@NonNull File dir, @NonNull Project project) { - log(Severity.WARNING, null, "Too late to register projects"); - mDelegate.registerProject(dir, project); - } - - @Override - public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) { - return mDelegate.addCustomLintRules(registry); - } - - @NonNull - @Override - public List<File> getAssetFolders(@NonNull Project project) { - return mDelegate.getAssetFolders(project); - } - - @Override - public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) { - return mDelegate.createUrlClassLoader(urls, parent); - } - - @Override - public boolean checkForSuppressComments() { - return mDelegate.checkForSuppressComments(); - } - - @Override - public boolean supportsProjectResources() { - return mDelegate.supportsProjectResources(); - } - - @Nullable - @Override - public AbstractResourceRepository getProjectResources(Project project, - boolean includeDependencies) { - return mDelegate.getProjectResources(project, includeDependencies); - } - - @NonNull - @Override - public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) { - return mDelegate.createResourceItemHandle(item); - } - - @Nullable - @Override - public URLConnection openConnection(@NonNull URL url) throws IOException { - return mDelegate.openConnection(url); - } - - @Override - public void closeConnection(@NonNull URLConnection connection) throws IOException { - mDelegate.closeConnection(connection); - } - } - - /** - * Requests another pass through the data for the given detector. This is - * typically done when a detector needs to do more expensive computation, - * but it only wants to do this once it <b>knows</b> that an error is - * present, or once it knows more specifically what to check for. - * - * @param detector the detector that should be included in the next pass. - * Note that the lint runner may refuse to run more than a couple - * of runs. - * @param scope the scope to be revisited. This must be a subset of the - * current scope ({@link #getScope()}, and it is just a performance hint; - * in particular, the detector should be prepared to be called on other - * scopes as well (since they may have been requested by other detectors). - * You can pall null to indicate "all". - */ - public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) { - if (mRepeatingDetectors == null) { - mRepeatingDetectors = new ArrayList<Detector>(); - } - mRepeatingDetectors.add(detector); - - if (scope != null) { - if (mRepeatScope == null) { - mRepeatScope = scope; - } else { - mRepeatScope = EnumSet.copyOf(mRepeatScope); - mRepeatScope.addAll(scope); - } - } else { - mRepeatScope = Scope.ALL; - } - } - - // Unfortunately, ASMs nodes do not extend a common DOM node type with parent - // pointers, so we have to have multiple methods which pass in each type - // of node (class, method, field) to be checked. - - /** - * Returns whether the given issue is suppressed in the given method. - * - * @param issue the issue to be checked, or null to just check for "all" - * @param classNode the class containing the issue - * @param method the method containing the issue - * @param instruction the instruction within the method, if any - * @return true if there is a suppress annotation covering the specific - * issue on this method - */ - public boolean isSuppressed( - @Nullable Issue issue, - @NonNull ClassNode classNode, - @NonNull MethodNode method, - @Nullable AbstractInsnNode instruction) { - if (method.invisibleAnnotations != null) { - @SuppressWarnings("unchecked") - List<AnnotationNode> annotations = method.invisibleAnnotations; - return isSuppressed(issue, annotations); - } - - // Initializations of fields end up placed in generated methods (<init> - // for members and <clinit> for static fields). - if (instruction != null && method.name.charAt(0) == '<') { - AbstractInsnNode next = LintUtils.getNextInstruction(instruction); - if (next != null && next.getType() == AbstractInsnNode.FIELD_INSN) { - FieldInsnNode fieldRef = (FieldInsnNode) next; - FieldNode field = findField(classNode, fieldRef.owner, fieldRef.name); - if (field != null && isSuppressed(issue, field)) { - return true; - } - } else if (classNode.outerClass != null && classNode.outerMethod == null - && isAnonymousClass(classNode)) { - if (isSuppressed(issue, classNode)) { - return true; - } - } - } - - return false; - } - - @Nullable - private static MethodInsnNode findConstructorInvocation( - @NonNull MethodNode method, - @NonNull String className) { - InsnList nodes = method.instructions; - for (int i = 0, n = nodes.size(); i < n; i++) { - AbstractInsnNode instruction = nodes.get(i); - if (instruction.getOpcode() == Opcodes.INVOKESPECIAL) { - MethodInsnNode call = (MethodInsnNode) instruction; - if (className.equals(call.owner)) { - return call; - } - } - } - - return null; - } - - @Nullable - private FieldNode findField( - @NonNull ClassNode classNode, - @NonNull String owner, - @NonNull String name) { - ClassNode current = classNode; - while (current != null) { - if (owner.equals(current.name)) { - @SuppressWarnings("rawtypes") // ASM API - List fieldList = current.fields; - for (Object f : fieldList) { - FieldNode field = (FieldNode) f; - if (field.name.equals(name)) { - return field; - } - } - return null; - } - current = getOuterClassNode(current); - } - return null; - } - - @Nullable - private MethodNode findMethod( - @NonNull ClassNode classNode, - @NonNull String name, - boolean includeInherited) { - ClassNode current = classNode; - while (current != null) { - @SuppressWarnings("rawtypes") // ASM API - List methodList = current.methods; - for (Object f : methodList) { - MethodNode method = (MethodNode) f; - if (method.name.equals(name)) { - return method; - } - } - - if (includeInherited) { - current = getOuterClassNode(current); - } else { - break; - } - } - return null; - } - - /** - * Returns whether the given issue is suppressed for the given field. - * - * @param issue the issue to be checked, or null to just check for "all" - * @param field the field potentially annotated with a suppress annotation - * @return true if there is a suppress annotation covering the specific - * issue on this field - */ - @SuppressWarnings("MethodMayBeStatic") // API; reserve need to require driver state later - public boolean isSuppressed(@Nullable Issue issue, @NonNull FieldNode field) { - if (field.invisibleAnnotations != null) { - @SuppressWarnings("unchecked") - List<AnnotationNode> annotations = field.invisibleAnnotations; - return isSuppressed(issue, annotations); - } - - return false; - } - - /** - * Returns whether the given issue is suppressed in the given class. - * - * @param issue the issue to be checked, or null to just check for "all" - * @param classNode the class containing the issue - * @return true if there is a suppress annotation covering the specific - * issue in this class - */ - public boolean isSuppressed(@Nullable Issue issue, @NonNull ClassNode classNode) { - if (classNode.invisibleAnnotations != null) { - @SuppressWarnings("unchecked") - List<AnnotationNode> annotations = classNode.invisibleAnnotations; - return isSuppressed(issue, annotations); - } - - if (classNode.outerClass != null && classNode.outerMethod == null - && isAnonymousClass(classNode)) { - ClassNode outer = getOuterClassNode(classNode); - if (outer != null) { - MethodNode m = findMethod(outer, CONSTRUCTOR_NAME, false); - if (m != null) { - MethodInsnNode call = findConstructorInvocation(m, classNode.name); - if (call != null) { - if (isSuppressed(issue, outer, m, call)) { - return true; - } - } - } - m = findMethod(outer, CLASS_CONSTRUCTOR, false); - if (m != null) { - MethodInsnNode call = findConstructorInvocation(m, classNode.name); - if (call != null) { - if (isSuppressed(issue, outer, m, call)) { - return true; - } - } - } - } - } - - return false; - } - - private static boolean isSuppressed(@Nullable Issue issue, List<AnnotationNode> annotations) { - for (AnnotationNode annotation : annotations) { - String desc = annotation.desc; - - // We could obey @SuppressWarnings("all") too, but no need to look for it - // because that annotation only has source retention. - - if (desc.endsWith(SUPPRESS_LINT_VMSIG)) { - if (annotation.values != null) { - for (int i = 0, n = annotation.values.size(); i < n; i += 2) { - String key = (String) annotation.values.get(i); - if (key.equals("value")) { //$NON-NLS-1$ - Object value = annotation.values.get(i + 1); - if (value instanceof String) { - String id = (String) value; - if (matches(issue, id)) { - return true; - } - } else if (value instanceof List) { - @SuppressWarnings("rawtypes") - List list = (List) value; - for (Object v : list) { - if (v instanceof String) { - String id = (String) v; - if (matches(issue, id)) { - return true; - } - } - } - } - } - } - } - } - } - - return false; - } - - private static boolean matches(@Nullable Issue issue, @NonNull String id) { - if (id.equalsIgnoreCase(SUPPRESS_ALL)) { - return true; - } - - if (issue != null) { - String issueId = issue.getId(); - if (id.equalsIgnoreCase(issueId)) { - return true; - } - if (id.startsWith(STUDIO_ID_PREFIX) - && id.regionMatches(true, STUDIO_ID_PREFIX.length(), issueId, 0, issueId.length()) - && id.substring(STUDIO_ID_PREFIX.length()).equalsIgnoreCase(issueId)) { - return true; - } - } - - return false; - } - - /** - * Returns true if the given issue is suppressed by the given suppress string; this - * is typically the same as the issue id, but is allowed to not match case sensitively, - * and is allowed to be a comma separated list, and can be the string "all" - * - * @param issue the issue id to match - * @param string the suppress string -- typically the id, or "all", or a comma separated list - * of ids - * @return true if the issue is suppressed by the given string - */ - private static boolean isSuppressed(@NonNull Issue issue, @NonNull String string) { - if (string.isEmpty()) { - return false; - } - - if (string.indexOf(',') == -1) { - if (matches(issue, string)) { - return true; - } - } else { - for (String id : Splitter.on(',').trimResults().split(string)) { - if (matches(issue, id)) { - return true; - } - } - } - - return false; - } - - /** - * Returns whether the given issue is suppressed in the given parse tree node. - * - * @param context the context for the source being scanned - * @param issue the issue to be checked, or null to just check for "all" - * @param scope the AST node containing the issue - * @return true if there is a suppress annotation covering the specific - * issue in this class - */ - public boolean isSuppressed(@Nullable JavaContext context, @NonNull Issue issue, - @Nullable Node scope) { - boolean checkComments = mClient.checkForSuppressComments() && - context != null && context.containsCommentSuppress(); - while (scope != null) { - Class<? extends Node> type = scope.getClass(); - // The Lombok AST uses a flat hierarchy of node type implementation classes - // so no need to do instanceof stuff here. - if (type == VariableDefinition.class) { - // Variable - VariableDefinition declaration = (VariableDefinition) scope; - if (isSuppressed(issue, declaration.astModifiers())) { - return true; - } - } else if (type == MethodDeclaration.class) { - // Method - // Look for annotations on the method - MethodDeclaration declaration = (MethodDeclaration) scope; - if (isSuppressed(issue, declaration.astModifiers())) { - return true; - } - } else if (type == ConstructorDeclaration.class) { - // Constructor - // Look for annotations on the method - ConstructorDeclaration declaration = (ConstructorDeclaration) scope; - if (isSuppressed(issue, declaration.astModifiers())) { - return true; - } - } else if (TypeDeclaration.class.isAssignableFrom(type)) { - // Class, annotation, enum, interface - TypeDeclaration declaration = (TypeDeclaration) scope; - if (isSuppressed(issue, declaration.astModifiers())) { - return true; - } - } else if (type == AnnotationMethodDeclaration.class) { - // Look for annotations on the method - AnnotationMethodDeclaration declaration = (AnnotationMethodDeclaration) scope; - if (isSuppressed(issue, declaration.astModifiers())) { - return true; - } - } - - if (checkComments && context.isSuppressedWithComment(scope, issue)) { - return true; - } - - scope = scope.getParent(); - } - - return false; - } - - public boolean isSuppressed(@Nullable JavaContext context, @NonNull Issue issue, - @Nullable UElement scope) { - boolean checkComments = mClient.checkForSuppressComments() && - context != null && context.containsCommentSuppress(); - while (scope != null) { - if (scope instanceof UAnnotated) { - if (isSuppressed(issue, (UAnnotated) scope)) { - return true; - } - } - - if (checkComments && context.isSuppressedWithComment(scope, issue)) { - return true; - } - - scope = scope.getUastParent(); - if (scope instanceof UFile) { - return false; - } - } - - return false; - } - - public boolean isSuppressed(@Nullable JavaContext context, @NonNull Issue issue, - @Nullable PsiElement scope) { - boolean checkComments = mClient.checkForSuppressComments() && - context != null && context.containsCommentSuppress(); - while (scope != null) { - if (scope instanceof PsiModifierListOwner) { - PsiModifierListOwner owner = (PsiModifierListOwner) scope; - if (isSuppressed(issue, owner.getModifierList())) { - return true; - } - } - - if (checkComments && context.isSuppressedWithComment(scope, issue)) { - return true; - } - - scope = scope.getParent(); - if (scope instanceof PsiFile) { - return false; - } - } - - return false; - } - - /** - * Returns true if the given AST modifier has a suppress annotation for the - * given issue (which can be null to check for the "all" annotation) - * - * @param issue the issue to be checked - * @param modifiers the modifier to check - * @return true if the issue or all issues should be suppressed for this - * modifier - */ - private static boolean isSuppressed(@Nullable Issue issue, @Nullable Modifiers modifiers) { - if (modifiers == null) { - return false; - } - StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations(); - if (annotations == null) { - return false; - } - - for (Annotation annotation : annotations) { - TypeReference t = annotation.astAnnotationTypeReference(); - String typeName = t.getTypeName(); - if (typeName.endsWith(SUPPRESS_LINT) - || typeName.endsWith("SuppressWarnings")) { //$NON-NLS-1$ - StrictListAccessor<AnnotationElement, Annotation> values = - annotation.astElements(); - if (values != null) { - for (AnnotationElement element : values) { - AnnotationValue valueNode = element.astValue(); - if (valueNode == null) { - continue; - } - if (valueNode instanceof StringLiteral) { - StringLiteral literal = (StringLiteral) valueNode; - String value = literal.astValue(); - if (matches(issue, value)) { - return true; - } - } else if (valueNode instanceof ArrayInitializer) { - ArrayInitializer array = (ArrayInitializer) valueNode; - StrictListAccessor<Expression, ArrayInitializer> expressions = - array.astExpressions(); - if (expressions == null) { - continue; - } - for (Expression arrayElement : expressions) { - if (arrayElement instanceof StringLiteral) { - String value = ((StringLiteral) arrayElement).astValue(); - if (matches(issue, value)) { - return true; - } - } - } - } - } - } - } - } - - return false; - } - - public static final String SUPPRESS_WARNINGS_FQCN = "java.lang.SuppressWarnings"; - - - /** - * Returns true if the given AST modifier has a suppress annotation for the - * given issue (which can be null to check for the "all" annotation) - * - * @param issue the issue to be checked - * @param modifierList the modifier to check - * @return true if the issue or all issues should be suppressed for this - * modifier - */ - public static boolean isSuppressed(@NonNull Issue issue, - @Nullable PsiModifierList modifierList) { - if (modifierList == null) { - return false; - } - - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - String fqcn = annotation.getQualifiedName(); - if (fqcn != null && (fqcn.equals(FQCN_SUPPRESS_LINT) - || fqcn.equals(SUPPRESS_WARNINGS_FQCN) - || fqcn.equals(SUPPRESS_LINT))) { // when missing imports - PsiAnnotationParameterList parameterList = annotation.getParameterList(); - for (PsiNameValuePair pair : parameterList.getAttributes()) { - if (isSuppressed(issue, pair.getValue())) { - return true; - } - } - } - } - - return false; - } - - /** - * Returns true if the annotation member value, assumed to be specified on a a SuppressWarnings - * or SuppressLint annotation, specifies the given id (or "all"). - * - * @param issue the issue to be checked - * @param value the member value to check - * @return true if the issue or all issues should be suppressed for this modifier - */ - public static boolean isSuppressed(@NonNull Issue issue, - @Nullable PsiAnnotationMemberValue value) { - if (value instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral)value; - Object literalValue = literal.getValue(); - if (literalValue instanceof String) { - if (isSuppressed(issue, (String) literalValue)) { - return true; - } - } - } else if (value instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue mv = (PsiArrayInitializerMemberValue)value; - for (PsiAnnotationMemberValue mmv : mv.getInitializers()) { - if (isSuppressed(issue, mmv)) { - return true; - } - } - } else if (value instanceof PsiArrayInitializerExpression) { - PsiArrayInitializerExpression expression = (PsiArrayInitializerExpression) value; - PsiExpression[] initializers = expression.getInitializers(); - for (PsiExpression e : initializers) { - if (isSuppressed(issue, e)) { - return true; - } - } - } - - return false; - } - - /** - * Returns whether the given issue is suppressed in the given XML DOM node. - * - * @param issue the issue to be checked, or null to just check for "all" - * @param node the DOM node containing the issue - * @return true if there is a suppress annotation covering the specific - * issue in this class - */ - public boolean isSuppressed(@Nullable XmlContext context, @NonNull Issue issue, - @Nullable org.w3c.dom.Node node) { - if (node instanceof Attr) { - node = ((Attr) node).getOwnerElement(); - } - boolean checkComments = mClient.checkForSuppressComments() - && context != null && context.containsCommentSuppress(); - while (node != null) { - if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { - Element element = (Element) node; - if (element.hasAttributeNS(TOOLS_URI, ATTR_IGNORE)) { - String ignore = element.getAttributeNS(TOOLS_URI, ATTR_IGNORE); - if (isSuppressed(issue, ignore)) { - return true; - } - } else if (checkComments && context.isSuppressedWithComment(node, issue)) { - return true; - } - } - - node = node.getParentNode(); - } - - return false; - } - - /** - * Returns true if the given AST modifier has a suppress annotation for the - * given issue (which can be null to check for the "all" annotation) - * - * @param issue the issue to be checked - * @param element the element to check - * @return true if the issue or all issues should be suppressed for this - * element - */ - public static boolean isSuppressed(@NonNull Issue issue, - @Nullable UAnnotated element) { - if (element == null) { - return false; - } - - for (UAnnotation annotation : element.getAnnotations()) { - String fqcn = annotation.getQualifiedName(); - if (fqcn != null && (fqcn.equals(FQCN_SUPPRESS_LINT) - || fqcn.equals(SUPPRESS_WARNINGS_FQCN) - || fqcn.equals(SUPPRESS_LINT))) { // when missing imports - UExpression valueAttributeExpression = annotation.findAttributeValue("value"); - if (valueAttributeExpression != null) { - if (UastExpressionUtils.isArrayInitializer(valueAttributeExpression)) { - UCallExpression arrayInitializer = (UCallExpression) valueAttributeExpression; - for (UExpression issueIdExpression : arrayInitializer.getValueArguments()) { - Object value = issueIdExpression.evaluate(); - if (value instanceof String && isSuppressed(issue, (String) value)) { - return true; - } - } - } - else { - Object value = valueAttributeExpression.evaluate(); - if (value instanceof String && isSuppressed(issue, (String) value)) { - return true; - } - } - } - } - } - - return false; - } - - private File mCachedFolder = null; - private int mCachedFolderVersion = -1; - /** Pattern for version qualifiers */ - private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$"); //$NON-NLS-1$ - - /** - * Returns the folder version of the given file. For example, for the file values-v14/foo.xml, - * it returns 14. - * - * @param resourceFile the file to be checked - * @return the folder version, or -1 if no specific version was specified - */ - public int getResourceFolderVersion(@NonNull File resourceFile) { - File parent = resourceFile.getParentFile(); - if (parent == null) { - return -1; - } - if (parent.equals(mCachedFolder)) { - return mCachedFolderVersion; - } - - mCachedFolder = parent; - mCachedFolderVersion = -1; - - for (String qualifier : QUALIFIER_SPLITTER.split(parent.getName())) { - Matcher matcher = VERSION_PATTERN.matcher(qualifier); - if (matcher.matches()) { - String group = matcher.group(1); - assert group != null; - mCachedFolderVersion = Integer.parseInt(group); - break; - } - } - - return mCachedFolderVersion; - } - -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintListener.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintListener.java deleted file mode 100644 index 7ef4ba0..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintListener.java +++ /dev/null
@@ -1,70 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Context; -import com.google.common.annotations.Beta; - -/** - * Interface implemented by listeners to be notified of lint events - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public interface LintListener { - /** The various types of events provided to lint listeners */ - enum EventType { - /** A lint check is about to begin */ - STARTING, - - /** Lint is about to check the given project, see {@link Context#getProject()} */ - SCANNING_PROJECT, - - /** Lint is about to check the given library project, see {@link Context#getProject()} */ - SCANNING_LIBRARY_PROJECT, - - /** Lint is about to check the given file, see {@link Context#file} */ - SCANNING_FILE, - - /** A new pass was initiated */ - NEW_PHASE, - - /** The lint check was canceled */ - CANCELED, - - /** The lint check is done */ - COMPLETED, - } - - /** - * Notifies listeners that the event of the given type has occurred. - * Additional information, such as the file being scanned, or the project - * being scanned, is available in the {@link Context} object (except for the - * {@link EventType#STARTING}, {@link EventType#CANCELED} or - * {@link EventType#COMPLETED} events which are fired outside of project - * contexts.) - * - * @param driver the driver running through the checks - * @param type the type of event that occurred - * @param context the context providing additional information - */ - void update(@NonNull LintDriver driver, @NonNull EventType type, - @Nullable Context context); -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintRequest.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintRequest.java deleted file mode 100644 index f812591..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintRequest.java +++ /dev/null
@@ -1,180 +0,0 @@ -/* - * 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Scope; -import com.google.common.annotations.Beta; - -import java.io.File; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; - -/** - * Information about a request to run lint - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class LintRequest { - @NonNull - protected final LintClient mClient; - - @NonNull - protected final List<File> mFiles; - - @Nullable - protected EnumSet<Scope> mScope; - - @Nullable - protected Boolean mReleaseMode; - - @Nullable - protected Collection<Project> mProjects; - - /** - * Creates a new {@linkplain LintRequest}, to be passed to a {@link LintDriver} - * - * @param client the tool wrapping the analyzer, such as an IDE or a CLI - * @param files the set of files to check with lint. This can reference Android projects, - * or directories containing Android projects, or individual XML or Java files - * (typically for incremental IDE analysis). - */ - public LintRequest(@NonNull LintClient client, @NonNull List<File> files) { - mClient = client; - mFiles = files; - } - - /** - * Returns the lint client requesting the lint check - * - * @return the client, never null - */ - @NonNull - public LintClient getClient() { - return mClient; - } - - /** - * Returns the set of files to check with lint. This can reference Android projects, - * or directories containing Android projects, or individual XML or Java files - * (typically for incremental IDE analysis). - * - * @return the set of files to check, should not be empty - */ - @NonNull - public List<File> getFiles() { - return mFiles; - } - - /** - * Sets the scope to use; lint checks which require a wider scope set - * will be ignored - * - * @return the scope to use, or null to use the default - */ - @Nullable - public EnumSet<Scope> getScope() { - return mScope; - } - - /** - * Sets the scope to use; lint checks which require a wider scope set - * will be ignored - * - * @param scope the scope - * @return this, for constructor chaining - */ - @NonNull - public LintRequest setScope(@Nullable EnumSet<Scope> scope) { - mScope = scope; - return this; - } - - /** - * Returns {@code true} if lint is invoked as part of a release mode build, - * {@code false} if it is part of a debug mode build, and {@code null} if - * the release mode is not known - * - * @return true if this lint is running in release mode, null if not known - */ - @Nullable - public Boolean isReleaseMode() { - return mReleaseMode; - } - - /** - * Sets the release mode. Use {@code true} if lint is invoked as part of a - * release mode build, {@code false} if it is part of a debug mode build, - * and {@code null} if the release mode is not known - * - * @param releaseMode true if this lint is running in release mode, null if not known - * @return this, for constructor chaining - */ - @NonNull - public LintRequest setReleaseMode(@Nullable Boolean releaseMode) { - mReleaseMode = releaseMode; - return this; - } - - /** - * Gets the projects for the lint requests. This is optional; if not provided lint will search - * the {@link #getFiles()} directories and look for projects via {@link - * LintClient#isProjectDirectory(java.io.File)}. However, this method allows a lint client to - * set up all the projects ahead of time, and associate those projects with native resources - * (in an IDE for example, each lint project can be associated with the corresponding IDE - * project). - * - * @return a collection of projects, or null - */ - @Nullable - public Collection<Project> getProjects() { - return mProjects; - } - - /** - * Sets the projects for the lint requests. This is optional; if not provided lint will search - * the {@link #getFiles()} directories and look for projects via {@link - * LintClient#isProjectDirectory(java.io.File)}. However, this method allows a lint client to - * set up all the projects ahead of time, and associate those projects with native resources - * (in an IDE for example, each lint project can be associated with the corresponding IDE - * project). - * - * @param projects a collection of projects, or null - */ - public void setProjects(@Nullable Collection<Project> projects) { - mProjects = projects; - } - - /** - * Returns the project to be used as the main project during analysis. This is - * usually the project itself, but when you are for example analyzing a library project, - * it can be the app project using the library. - * - * @param project the project to look up the main project for - * @return the main project - */ - @SuppressWarnings("MethodMayBeStatic") - @NonNull - public Project getMainProject(@NonNull Project project) { - return project; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/OtherFileVisitor.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/OtherFileVisitor.java deleted file mode 100644 index f6a42f2..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/OtherFileVisitor.java +++ /dev/null
@@ -1,207 +0,0 @@ -/* - * 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.klint.client.api; - -import static com.android.SdkConstants.ANDROID_MANIFEST_XML; -import static com.android.SdkConstants.DOT_CLASS; -import static com.android.SdkConstants.DOT_JAVA; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.FD_ASSETS; -import static com.android.tools.klint.detector.api.Detector.OtherFileScanner; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Scope; -import com.android.utils.SdkUtils; -import com.google.common.collect.Lists; - -import java.io.File; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -/** - * Visitor for "other" files: files that aren't java sources, - * XML sources, etc -- or which should have custom handling in some - * other way. - */ -class OtherFileVisitor { - @NonNull - private final List<Detector> mDetectors; - - @NonNull - private Map<Scope, List<File>> mFiles = new EnumMap<Scope, List<File>>(Scope.class); - - OtherFileVisitor(@NonNull List<Detector> detectors) { - mDetectors = detectors; - } - - /** Analyze other files in the given project */ - void scan( - @NonNull LintDriver driver, - @NonNull Project project, - @Nullable Project main) { - // Collect all project files - File projectFolder = project.getDir(); - - EnumSet<Scope> scopes = EnumSet.noneOf(Scope.class); - for (Detector detector : mDetectors) { - OtherFileScanner fileScanner = (OtherFileScanner) detector; - EnumSet<Scope> applicable = fileScanner.getApplicableFiles(); - if (applicable.contains(Scope.OTHER)) { - scopes = Scope.ALL; - break; - } - scopes.addAll(applicable); - } - - List<File> subset = project.getSubset(); - - if (scopes.contains(Scope.RESOURCE_FILE)) { - if (subset != null && !subset.isEmpty()) { - List<File> files = new ArrayList<File>(subset.size()); - for (File file : subset) { - if (SdkUtils.endsWith(file.getPath(), DOT_XML) && - !file.getName().equals(ANDROID_MANIFEST_XML)) { - files.add(file); - } - } - if (!files.isEmpty()) { - mFiles.put(Scope.RESOURCE_FILE, files); - } - } else { - List<File> files = Lists.newArrayListWithExpectedSize(100); - for (File res : project.getResourceFolders()) { - collectFiles(files, res); - } - File assets = new File(projectFolder, FD_ASSETS); - if (assets.exists()) { - collectFiles(files, assets); - } - if (!files.isEmpty()) { - mFiles.put(Scope.RESOURCE_FILE, files); - } - } - } - - if (scopes.contains(Scope.JAVA_FILE)) { - if (subset != null && !subset.isEmpty()) { - List<File> files = new ArrayList<File>(subset.size()); - for (File file : subset) { - if (file.getPath().endsWith(".kt")) { - files.add(file); - } - } - if (!files.isEmpty()) { - mFiles.put(Scope.JAVA_FILE, files); - } - } else { - List<File> files = Lists.newArrayListWithExpectedSize(100); - for (File srcFolder : project.getJavaSourceFolders()) { - collectFiles(files, srcFolder); - } - if (!files.isEmpty()) { - mFiles.put(Scope.JAVA_FILE, files); - } - } - } - - if (scopes.contains(Scope.CLASS_FILE)) { - if (subset != null && !subset.isEmpty()) { - List<File> files = new ArrayList<File>(subset.size()); - for (File file : subset) { - if (file.getPath().endsWith(DOT_CLASS)) { - files.add(file); - } - } - if (!files.isEmpty()) { - mFiles.put(Scope.CLASS_FILE, files); - } - } else { - List<File> files = Lists.newArrayListWithExpectedSize(100); - for (File classFolder : project.getJavaClassFolders()) { - collectFiles(files, classFolder); - } - if (!files.isEmpty()) { - mFiles.put(Scope.CLASS_FILE, files); - } - } - } - - if (scopes.contains(Scope.MANIFEST)) { - if (subset != null && !subset.isEmpty()) { - List<File> files = new ArrayList<File>(subset.size()); - for (File file : subset) { - if (file.getName().equals(ANDROID_MANIFEST_XML)) { - files.add(file); - } - } - if (!files.isEmpty()) { - mFiles.put(Scope.MANIFEST, files); - } - } else { - List<File> manifestFiles = project.getManifestFiles(); - if (manifestFiles != null) { - mFiles.put(Scope.MANIFEST, manifestFiles); - } - } - } - - for (Map.Entry<Scope, List<File>> entry : mFiles.entrySet()) { - Scope scope = entry.getKey(); - List<File> files = entry.getValue(); - List<Detector> applicable = new ArrayList<Detector>(mDetectors.size()); - for (Detector detector : mDetectors) { - OtherFileScanner fileScanner = (OtherFileScanner) detector; - EnumSet<Scope> appliesTo = fileScanner.getApplicableFiles(); - if (appliesTo.contains(Scope.OTHER) || appliesTo.contains(scope)) { - applicable.add(detector); - } - } - if (!applicable.isEmpty()) { - for (File file : files) { - Context context = new Context(driver, project, main, file); - for (Detector detector : applicable) { - detector.beforeCheckFile(context); - detector.run(context); - detector.afterCheckFile(context); - } - if (driver.isCanceled()) { - return; - } - } - } - } - } - - private static void collectFiles(List<File> files, File file) { - if (file.isDirectory()) { - File[] children = file.listFiles(); - if (children != null) { - for (File child : children) { - collectFiles(files, child); - } - } - } else { - files.add(file); - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ResourceVisitor.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ResourceVisitor.java deleted file mode 100644 index 5ea1008..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ResourceVisitor.java +++ /dev/null
@@ -1,245 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.ResourceContext; -import com.android.tools.klint.detector.api.XmlContext; -import com.google.common.annotations.Beta; - -import org.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.RandomAccess; - -/** - * Specialized visitor for running detectors on resources: typically XML documents, - * but also binary resources. - * <p> - * It operates in two phases: - * <ol> - * <li> First, it computes a set of maps where it generates a map from each - * significant element name, and each significant attribute name, to a list - * of detectors to consult for that element or attribute name. - * The set of element names or attribute names (or both) that a detector - * is interested in is provided by the detectors themselves. - * <li> Second, it iterates over the document a single time. For each element and - * attribute it looks up the list of interested detectors, and runs them. - * </ol> - * It also notifies all the detectors before and after the document is processed - * such that they can do pre- and post-processing. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -class ResourceVisitor { - private final Map<String, List<Detector.XmlScanner>> mElementToCheck = - new HashMap<String, List<Detector.XmlScanner>>(); - private final Map<String, List<Detector.XmlScanner>> mAttributeToCheck = - new HashMap<String, List<Detector.XmlScanner>>(); - private final List<Detector.XmlScanner> mDocumentDetectors = - new ArrayList<Detector.XmlScanner>(); - private final List<Detector.XmlScanner> mAllElementDetectors = - new ArrayList<Detector.XmlScanner>(); - private final List<Detector.XmlScanner> mAllAttributeDetectors = - new ArrayList<Detector.XmlScanner>(); - private final List<? extends Detector> mAllDetectors; - private final List<? extends Detector> mBinaryDetectors; - private final XmlParser mParser; - - // Really want this: - //<T extends List<Detector> & Detector.XmlScanner> XmlVisitor(IDomParser parser, - // T xmlDetectors) { - // but it makes client code tricky and ugly. - ResourceVisitor( - @NonNull XmlParser parser, - @NonNull List<? extends Detector> xmlDetectors, - @Nullable List<Detector> binaryDetectors) { - mParser = parser; - mAllDetectors = xmlDetectors; - mBinaryDetectors = binaryDetectors; - - // TODO: Check appliesTo() for files, and find a quick way to enable/disable - // rules when running through a full project! - for (Detector detector : xmlDetectors) { - Detector.XmlScanner xmlDetector = (XmlScanner) detector; - Collection<String> attributes = xmlDetector.getApplicableAttributes(); - if (attributes == XmlScanner.ALL) { - mAllAttributeDetectors.add(xmlDetector); - } else if (attributes != null) { - for (String attribute : attributes) { - List<Detector.XmlScanner> list = mAttributeToCheck.get(attribute); - if (list == null) { - list = new ArrayList<Detector.XmlScanner>(); - mAttributeToCheck.put(attribute, list); - } - list.add(xmlDetector); - } - } - Collection<String> elements = xmlDetector.getApplicableElements(); - if (elements == XmlScanner.ALL) { - mAllElementDetectors.add(xmlDetector); - } else if (elements != null) { - for (String element : elements) { - List<Detector.XmlScanner> list = mElementToCheck.get(element); - if (list == null) { - list = new ArrayList<Detector.XmlScanner>(); - mElementToCheck.put(element, list); - } - list.add(xmlDetector); - } - } - - if ((attributes == null || (attributes.isEmpty() - && attributes != XmlScanner.ALL)) - && (elements == null || (elements.isEmpty() - && elements != XmlScanner.ALL))) { - mDocumentDetectors.add(xmlDetector); - } - } - } - - void visitFile(@NonNull XmlContext context, @NonNull File file) { - assert LintUtils.isXmlFile(file); - - try { - if (context.document == null) { - context.document = mParser.parseXml(context); - if (context.document == null) { - // No need to log this; the parser should be reporting - // a full warning (such as IssueRegistry#PARSER_ERROR) - // with details, location, etc. - return; - } - if (context.document.getDocumentElement() == null) { - // Ignore empty documents - return; - } - } - - for (Detector check : mAllDetectors) { - check.beforeCheckFile(context); - } - - for (Detector.XmlScanner check : mDocumentDetectors) { - check.visitDocument(context, context.document); - } - - if (!mElementToCheck.isEmpty() || !mAttributeToCheck.isEmpty() - || !mAllAttributeDetectors.isEmpty() || !mAllElementDetectors.isEmpty()) { - visitElement(context, context.document.getDocumentElement()); - } - - for (Detector check : mAllDetectors) { - check.afterCheckFile(context); - } - } finally { - if (context.document != null) { - mParser.dispose(context, context.document); - context.document = null; - } - } - } - - private void visitElement(@NonNull XmlContext context, @NonNull Element element) { - List<Detector.XmlScanner> elementChecks = mElementToCheck.get(element.getTagName()); - if (elementChecks != null) { - assert elementChecks instanceof RandomAccess; - for (XmlScanner check : elementChecks) { - check.visitElement(context, element); - } - } - if (!mAllElementDetectors.isEmpty()) { - for (XmlScanner check : mAllElementDetectors) { - check.visitElement(context, element); - } - } - - if (!mAttributeToCheck.isEmpty() || !mAllAttributeDetectors.isEmpty()) { - NamedNodeMap attributes = element.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Attr attribute = (Attr) attributes.item(i); - String name = attribute.getLocalName(); - if (name == null) { - name = attribute.getName(); - } - List<Detector.XmlScanner> list = mAttributeToCheck.get(name); - if (list != null) { - for (XmlScanner check : list) { - check.visitAttribute(context, attribute); - } - } - if (!mAllAttributeDetectors.isEmpty()) { - for (XmlScanner check : mAllAttributeDetectors) { - check.visitAttribute(context, attribute); - } - } - } - } - - // Visit children - NodeList childNodes = element.getChildNodes(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - visitElement(context, (Element) child); - } - } - - // Post hooks - if (elementChecks != null) { - for (XmlScanner check : elementChecks) { - check.visitElementAfter(context, element); - } - } - if (!mAllElementDetectors.isEmpty()) { - for (XmlScanner check : mAllElementDetectors) { - check.visitElementAfter(context, element); - } - } - } - - @NonNull - public XmlParser getParser() { - return mParser; - } - - public void visitBinaryResource(@NonNull ResourceContext context) { - if (mBinaryDetectors == null) { - return; - } - for (Detector check : mBinaryDetectors) { - check.beforeCheckFile(context); - check.checkBinaryResource(context); - check.afterCheckFile(context); - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/SdkInfo.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/SdkInfo.java deleted file mode 100644 index e109d00c..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/SdkInfo.java +++ /dev/null
@@ -1,90 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.google.common.annotations.Beta; - -/** - * Information about SDKs - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class SdkInfo { - /** - * Returns true if the given child view is the same class or a sub class of - * the given parent view class - * - * @param parentViewFqcn the fully qualified class name of the parent view - * @param childViewFqcn the fully qualified class name of the child view - * @return true if the child view is a sub view of (or the same class as) - * the parent view - */ - public boolean isSubViewOf(@NonNull String parentViewFqcn, @NonNull String childViewFqcn) { - while (!childViewFqcn.equals("android.view.View")) { //$NON-NLS-1$ - if (parentViewFqcn.equals(childViewFqcn)) { - return true; - } - String parent = getParentViewClass(childViewFqcn); - if (parent == null) { - // Unknown view - err on the side of caution - return true; - } - childViewFqcn = parent; - } - - return false; - } - - - /** - * Returns the fully qualified name of the parent view, or null if the view - * is the root android.view.View class. - * - * @param fqcn the fully qualified class name of the view - * @return the fully qualified class name of the parent view, or null - */ - @Nullable - public abstract String getParentViewClass(@NonNull String fqcn); - - /** - * Returns the class name of the parent view, or null if the view is the - * root android.view.View class. This is the same as the - * {@link #getParentViewClass(String)} but without the package. - * - * @param name the view class name to look up the parent for (not including - * package) - * @return the view name of the parent - */ - @Nullable - public abstract String getParentViewName(@NonNull String name); - - /** - * Returns true if the given widget name is a layout - * - * @param tag the XML tag for the view - * @return true if the given tag corresponds to a layout - */ - public boolean isLayout(@NonNull String tag) { - return tag.endsWith("Layout"); //$NON-NLS-1$ - } - - // TODO: Add access to resource resolution here. -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UElementVisitor.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UElementVisitor.java deleted file mode 100644 index f518bae..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UElementVisitor.java +++ /dev/null
@@ -1,1088 +0,0 @@ -/* - * 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Detector.UastScanner; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.JavaContext; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.StandardFileSystems; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.*; - -import static com.android.SdkConstants.ANDROID_PKG; - -/** - * Specialized visitor for running detectors on a Java AST. - * It operates in three phases: - * <ol> - * <li> First, it computes a set of maps where it generates a map from each - * significant AST attribute (such as method call names) to a list - * of detectors to consult whenever that attribute is encountered. - * Examples of "attributes" are method names, Android resource identifiers, - * and general AST node types such as "cast" nodes etc. These are - * defined on the {@link JavaPsiScanner} interface. - * <li> Second, it iterates over the document a single time, delegating to - * the detectors found at each relevant AST attribute. - * <li> Finally, it calls the remaining visitors (those that need to process a - * whole document on their own). - * </ol> - * It also notifies all the detectors before and after the document is processed - * such that they can do pre- and post-processing. - */ -public class UElementVisitor { - /** Default size of lists holding detectors of the same type for a given node type */ - private static final int SAME_TYPE_COUNT = 8; - - private final Map<String, List<VisitingDetector>> mMethodDetectors = - Maps.newHashMapWithExpectedSize(80); - private final Map<String, List<VisitingDetector>> mConstructorDetectors = - Maps.newHashMapWithExpectedSize(12); - private final Map<String, List<VisitingDetector>> mReferenceDetectors = - Maps.newHashMapWithExpectedSize(10); - private Set<String> mConstructorSimpleNames; - private final List<VisitingDetector> mResourceFieldDetectors = - new ArrayList<VisitingDetector>(); - private final List<VisitingDetector> mAllDetectors; - private final List<VisitingDetector> mFullTreeDetectors; - private final Map<Class<? extends UElement>, List<VisitingDetector>> mNodePsiTypeDetectors = - new HashMap<Class<? extends UElement>, List<VisitingDetector>>(16); - private final JavaParser mParser; - private final Map<String, List<VisitingDetector>> mSuperClassDetectors = - new HashMap<String, List<VisitingDetector>>(); - - /** - * Number of fatal exceptions (internal errors, usually from ECJ) we've - * encountered; we don't log each and every one to avoid massive log spam - * in code which triggers this condition - */ - private static int sExceptionCount; - /** Max number of logs to include */ - private static final int MAX_REPORTED_CRASHES = 20; - - UElementVisitor(@NonNull JavaParser parser, @NonNull List<Detector> detectors) { - mParser = parser; - mAllDetectors = new ArrayList<VisitingDetector>(detectors.size()); - mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size()); - - for (Detector detector : detectors) { - UastScanner uastScanner = (UastScanner) detector; - VisitingDetector v = new VisitingDetector(detector, uastScanner); - mAllDetectors.add(v); - - List<String> names = detector.getApplicableMethodNames(); - if (names != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert names != XmlScanner.ALL; - - for (String name : names) { - List<VisitingDetector> list = mMethodDetectors.get(name); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mMethodDetectors.put(name, list); - } - list.add(v); - } - } - - List<String> applicableSuperClasses = detector.applicableSuperClasses(); - if (applicableSuperClasses != null) { - for (String fqn : applicableSuperClasses) { - List<VisitingDetector> list = mSuperClassDetectors.get(fqn); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mSuperClassDetectors.put(fqn, list); - } - list.add(v); - } - continue; - } - - List<Class<? extends UElement>> nodePsiTypes = detector.getApplicableUastTypes(); - if (nodePsiTypes != null) { - for (Class<? extends UElement> type : nodePsiTypes) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(type); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mNodePsiTypeDetectors.put(type, list); - } - list.add(v); - } - } - - List<String> types = detector.getApplicableConstructorTypes(); - if (types != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert types != XmlScanner.ALL; - if (mConstructorSimpleNames == null) { - mConstructorSimpleNames = Sets.newHashSet(); - } - for (String type : types) { - List<VisitingDetector> list = mConstructorDetectors.get(type); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mConstructorDetectors.put(type, list); - mConstructorSimpleNames.add(type.substring(type.lastIndexOf('.')+1)); - } - list.add(v); - } - } - - List<String> referenceNames = detector.getApplicableReferenceNames(); - if (referenceNames != null) { - // not supported in Java visitors; adding a method invocation node is trivial - // for that case. - assert referenceNames != XmlScanner.ALL; - - for (String name : referenceNames) { - List<VisitingDetector> list = mReferenceDetectors.get(name); - if (list == null) { - list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT); - mReferenceDetectors.put(name, list); - } - list.add(v); - } - } - - if (detector.appliesToResourceRefs()) { - mResourceFieldDetectors.add(v); - } else if ((referenceNames == null || referenceNames.isEmpty()) - && (nodePsiTypes == null || nodePsiTypes.isEmpty()) - && (types == null || types.isEmpty())) { - mFullTreeDetectors.add(v); - } - } - } - - void visitFile(@NonNull final JavaContext context) { - try { - Project ideaProject = context.getParser().getIdeaProject(); - if (ideaProject == null) { - return; - } - - VirtualFile virtualFile = StandardFileSystems.local() - .findFileByPath(context.file.getAbsolutePath()); - if (virtualFile == null) { - return; - } - - PsiFile psiFile = PsiManager.getInstance(ideaProject).findFile(virtualFile); - if (psiFile == null) { - return; - } - - UElement uElement = context.getUastContext().convertElementWithParent(psiFile, UFile.class); - if (!(uElement instanceof UFile)) { - // No need to log this; the parser should be reporting - // a full warning (such as IssueRegistry#PARSER_ERROR) - // with details, location, etc. - return; - } - - final UFile uFile = (UFile) uElement; - - try { - context.setUFile(uFile); - - mParser.runReadAction(new Runnable() { - @Override - public void run() { - for (VisitingDetector v : mAllDetectors) { - v.setContext(context); - v.getDetector().beforeCheckFile(context); - } - } - }); - - if (!mSuperClassDetectors.isEmpty()) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - SuperclassPsiVisitor visitor = new SuperclassPsiVisitor(context); - uFile.accept(visitor); - } - }); - } - - for (final VisitingDetector v : mFullTreeDetectors) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - UastVisitor visitor = v.getVisitor(); - ProgressManager.checkCanceled(); - uFile.accept(visitor); - } - }); - } - - if (!mMethodDetectors.isEmpty() - || !mResourceFieldDetectors.isEmpty() - || !mConstructorDetectors.isEmpty() - || !mReferenceDetectors.isEmpty()) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - // TODO: Do we need to break this one up into finer grain - // locking units - UastVisitor visitor = new DelegatingPsiVisitor(context); - uFile.accept(visitor); - } - }); - } else { - if (!mNodePsiTypeDetectors.isEmpty()) { - mParser.runReadAction(new Runnable() { - @Override - public void run() { - // TODO: Do we need to break this one up into finer grain - // locking units - UastVisitor visitor = new DispatchPsiVisitor(); - uFile.accept(visitor); - } - }); - } - } - - mParser.runReadAction(new Runnable() { - @Override - public void run() { - for (VisitingDetector v : mAllDetectors) { - ProgressManager.checkCanceled(); - v.getDetector().afterCheckFile(context); - } - } - }); - } finally { - mParser.dispose(context, uFile); - context.setUFile(null); - } - } catch (ProcessCanceledException ignore) { - // Cancelling inspections in the IDE - } catch (RuntimeException e) { - if (sExceptionCount++ > MAX_REPORTED_CRASHES) { - // No need to keep spamming the user that a lot of the files - // are tripping up ECJ, they get the picture. - return; - } - - if (e.getClass().getSimpleName().equals("IndexNotReadyException")) { - // Attempting to access PSI during startup before indices are ready; ignore these. - // See http://b.android.com/176644 for an example. - return; - } - - // Work around ECJ bugs; see https://code.google.com/p/android/issues/detail?id=172268 - // Don't allow lint bugs to take down the whole build. TRY to log this as a - // lint error instead! - StringBuilder sb = new StringBuilder(100); - sb.append("Unexpected failure during lint analysis of "); - sb.append(context.file.getName()); - sb.append(" (this is a bug in lint or one of the libraries it depends on)\n"); - - sb.append(e.getClass().getSimpleName()); - sb.append(':'); - StackTraceElement[] stackTrace = e.getStackTrace(); - int count = 0; - for (StackTraceElement frame : stackTrace) { - if (count > 0) { - sb.append("<-"); - } - - String className = frame.getClassName(); - sb.append(className.substring(className.lastIndexOf('.') + 1)); - sb.append('.').append(frame.getMethodName()); - sb.append('('); - sb.append(frame.getFileName()).append(':').append(frame.getLineNumber()); - sb.append(')'); - count++; - // Only print the top 3-4 frames such that we can identify the bug - if (count == 4) { - break; - } - } - Throwable throwable = null; // NOT e: this makes for very noisy logs - //noinspection ConstantConditions - context.log(throwable, sb.toString()); - } - } - - /** - * For testing only: returns the number of exceptions thrown during Java AST analysis - * - * @return the number of internal errors found - */ - @VisibleForTesting - public static int getCrashCount() { - return sExceptionCount; - } - - /** - * For testing only: clears the crash counter - */ - @VisibleForTesting - public static void clearCrashCount() { - sExceptionCount = 0; - } - - public void prepare(@NonNull List<JavaContext> contexts) { - mParser.prepareJavaParse(contexts); - } - - public void dispose() { - mParser.dispose(); - } - - @Nullable - private static Set<String> getInterfaceNames( - @Nullable Set<String> addTo, - @NonNull PsiClass cls) { - for (PsiClass resolvedInterface : cls.getInterfaces()) { - String name = resolvedInterface.getQualifiedName(); - if (addTo == null) { - addTo = Sets.newHashSet(); - } else if (addTo.contains(name)) { - // Superclasses can explicitly implement the same interface, - // so keep track of visited interfaces as we traverse up the - // super class chain to avoid checking the same interface - // more than once. - continue; - } - addTo.add(name); - getInterfaceNames(addTo, resolvedInterface); - } - - return addTo; - } - - private static class VisitingDetector { - private UastVisitor mVisitor; - private JavaContext mContext; - public final Detector mDetector; - public final UastScanner mUastScanner; - - public VisitingDetector(@NonNull Detector detector, @NonNull UastScanner uastScanner) { - mDetector = detector; - mUastScanner = uastScanner; - } - - @NonNull - public Detector getDetector() { - return mDetector; - } - - @Nullable - public UastScanner getUastScanner() { - return mUastScanner; - } - - public void setContext(@NonNull JavaContext context) { - mContext = context; - - // The visitors are one-per-context, so clear them out here and construct - // lazily only if needed - mVisitor = null; - } - - @NonNull - UastVisitor getVisitor() { - if (mVisitor == null) { - mVisitor = mDetector.createUastVisitor(mContext); - if (mVisitor == null) { - mVisitor = new AbstractUastVisitor() {}; - } - } - return mVisitor; - } - } - - private class SuperclassPsiVisitor extends AbstractUastVisitor { - private JavaContext mContext; - - public SuperclassPsiVisitor(@NonNull JavaContext context) { - mContext = context; - } - - @Override - public boolean visitClass(UClass node) { - boolean result = super.visitClass(node); - checkClass(node); - return result; - } - - private void checkClass(@NonNull UClass node) { - ProgressManager.checkCanceled(); - - if (node instanceof PsiTypeParameter) { - // Not included: explained in javadoc for JavaPsiScanner#checkClass - return; - } - - UClass cls = node; - int depth = 0; - while (cls != null) { - List<VisitingDetector> list = mSuperClassDetectors.get(cls.getQualifiedName()); - if (list != null) { - for (VisitingDetector v : list) { - UastScanner uastScanner = v.getUastScanner(); - if (uastScanner != null) { - uastScanner.checkClass(mContext, node); - } - } - } - - // Check interfaces too - Set<String> interfaceNames = getInterfaceNames(null, cls); - if (interfaceNames != null) { - for (String name : interfaceNames) { - list = mSuperClassDetectors.get(name); - if (list != null) { - for (VisitingDetector v : list) { - UastScanner javaPsiScanner = v.getUastScanner(); - if (javaPsiScanner != null) { - javaPsiScanner.checkClass(mContext, node); - } - } - } - } - } - - cls = cls.getSuperClass(); - depth++; - if (depth == 500) { - // Shouldn't happen in practice; this prevents the IDE from - // hanging if the user has accidentally typed in an incorrect - // super class which creates a cycle. - break; - } - } - } - } - - private class DispatchPsiVisitor extends AbstractUastVisitor { - - @Override - public boolean visitAnnotation(UAnnotation node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UAnnotation.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitAnnotation(node); - } - } - return super.visitAnnotation(node); - } - - @Override - public boolean visitCatchClause(UCatchClause node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UCatchClause.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCatchClause(node); - } - } - return super.visitCatchClause(node); - } - - @Override - public boolean visitMethod(UMethod node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UMethod.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitMethod(node); - } - } - return super.visitMethod(node); - } - - @Override - public boolean visitVariable(UVariable node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UVariable.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitVariable(node); - } - } - return super.visitVariable(node); - } - - @Override - public boolean visitFile(UFile node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UFile.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitFile(node); - } - } - return super.visitFile(node); - } - - @Override - public boolean visitImportStatement(UImportStatement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UImportStatement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitImportStatement(node); - } - } - return super.visitImportStatement(node); - } - - @Override - public boolean visitElement(UElement node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UElement.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitElement(node); - } - } - return super.visitElement(node); - } - - @Override - public boolean visitClass(UClass node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UClass.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitClass(node); - } - } - return super.visitClass(node); - } - - @Override - public boolean visitInitializer(UClassInitializer node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UClassInitializer.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitInitializer(node); - } - } - return super.visitInitializer(node); - } - - @Override - public boolean visitLabeledExpression(ULabeledExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(ULabeledExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLabeledExpression(node); - } - } - return super.visitLabeledExpression(node); - } - - @Override - public boolean visitBlockExpression(UBlockExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBlockExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBlockExpression(node); - } - } - return super.visitBlockExpression(node); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UCallExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCallExpression(node); - } - } - return super.visitCallExpression(node); - } - - @Override - public boolean visitBinaryExpression(UBinaryExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBinaryExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBinaryExpression(node); - } - } - return super.visitBinaryExpression(node); - } - - @Override - public boolean visitBinaryExpressionWithType(UBinaryExpressionWithType node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBinaryExpressionWithType.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBinaryExpressionWithType(node); - } - } - return super.visitBinaryExpressionWithType(node); - } - - @Override - public boolean visitParenthesizedExpression(UParenthesizedExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UParenthesizedExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitParenthesizedExpression(node); - } - } - return super.visitParenthesizedExpression(node); - } - - @Override - public boolean visitUnaryExpression(UUnaryExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UUnaryExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitUnaryExpression(node); - } - } - return super.visitUnaryExpression(node); - } - - @Override - public boolean visitPrefixExpression(UPrefixExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UPrefixExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPrefixExpression(node); - } - } - return super.visitPrefixExpression(node); - } - - @Override - public boolean visitPostfixExpression(UPostfixExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UPostfixExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitPostfixExpression(node); - } - } - return super.visitPostfixExpression(node); - } - - @Override - public boolean visitIfExpression(UIfExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UIfExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitIfExpression(node); - } - } - return super.visitIfExpression(node); - } - - @Override - public boolean visitSwitchExpression(USwitchExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(USwitchExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSwitchExpression(node); - } - } - return super.visitSwitchExpression(node); - } - - @Override - public boolean visitSwitchClauseExpression(USwitchClauseExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(USwitchClauseExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSwitchClauseExpression(node); - } - } - return super.visitSwitchClauseExpression(node); - } - - @Override - public boolean visitWhileExpression(UWhileExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UWhileExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitWhileExpression(node); - } - } - return super.visitWhileExpression(node); - } - - @Override - public boolean visitDoWhileExpression(UDoWhileExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UDoWhileExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDoWhileExpression(node); - } - } - return super.visitDoWhileExpression(node); - } - - @Override - public boolean visitForExpression(UForExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UForExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitForExpression(node); - } - } - return super.visitForExpression(node); - } - - @Override - public boolean visitForEachExpression(UForEachExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UForEachExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitForEachExpression(node); - } - } - return super.visitForEachExpression(node); - } - - @Override - public boolean visitTryExpression(UTryExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UTryExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTryExpression(node); - } - } - return super.visitTryExpression(node); - } - - @Override - public boolean visitLiteralExpression(ULiteralExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(ULiteralExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLiteralExpression(node); - } - } - return super.visitLiteralExpression(node); - } - - @Override - public boolean visitThisExpression(UThisExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UThisExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitThisExpression(node); - } - } - return super.visitThisExpression(node); - } - - @Override - public boolean visitSuperExpression(USuperExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(USuperExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSuperExpression(node); - } - } - return super.visitSuperExpression(node); - } - - @Override - public boolean visitReturnExpression(UReturnExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UReturnExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitReturnExpression(node); - } - } - return super.visitReturnExpression(node); - } - - @Override - public boolean visitBreakExpression(UBreakExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UBreakExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitBreakExpression(node); - } - } - return super.visitBreakExpression(node); - } - - @Override - public boolean visitContinueExpression(UContinueExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UContinueExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitContinueExpression(node); - } - } - return super.visitContinueExpression(node); - } - - @Override - public boolean visitThrowExpression(UThrowExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UThrowExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitThrowExpression(node); - } - } - return super.visitThrowExpression(node); - } - - @Override - public boolean visitArrayAccessExpression(UArrayAccessExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UArrayAccessExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitArrayAccessExpression(node); - } - } - return super.visitArrayAccessExpression(node); - } - - @Override - public boolean visitCallableReferenceExpression(UCallableReferenceExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UCallableReferenceExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitCallableReferenceExpression(node); - } - } - return super.visitCallableReferenceExpression(node); - } - - @Override - public boolean visitClassLiteralExpression(UClassLiteralExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UClassLiteralExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitClassLiteralExpression(node); - } - } - return super.visitClassLiteralExpression(node); - } - - @Override - public boolean visitLambdaExpression(ULambdaExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(ULambdaExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitLambdaExpression(node); - } - } - return super.visitLambdaExpression(node); - } - - @Override - public boolean visitObjectLiteralExpression(UObjectLiteralExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UObjectLiteralExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitObjectLiteralExpression(node); - } - } - return super.visitObjectLiteralExpression(node); - } - - @Override - public boolean visitExpressionList(UExpressionList node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UExpressionList.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitExpressionList(node); - } - } - return super.visitExpressionList(node); - } - - @Override - public boolean visitTypeReferenceExpression(UTypeReferenceExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UTypeReferenceExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitTypeReferenceExpression(node); - } - } - return super.visitTypeReferenceExpression(node); - } - - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(USimpleNameReferenceExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitSimpleNameReferenceExpression(node); - } - } - return super.visitSimpleNameReferenceExpression(node); - } - - @Override - public boolean visitQualifiedReferenceExpression(UQualifiedReferenceExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UQualifiedReferenceExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitQualifiedReferenceExpression(node); - } - } - return super.visitQualifiedReferenceExpression(node); - } - - @Override - public boolean visitDeclarationsExpression(UDeclarationsExpression node) { - List<VisitingDetector> list = mNodePsiTypeDetectors.get(UDeclarationsExpression.class); - if (list != null) { - for (VisitingDetector v : list) { - v.getVisitor().visitDeclarationsExpression(node); - } - } - return super.visitDeclarationsExpression(node); - } - } - - /** Performs common AST searches for method calls and R-type-field references. - * Note that this is a specialized form of the {@link DispatchPsiVisitor}. */ - private class DelegatingPsiVisitor extends DispatchPsiVisitor { - private final JavaContext mContext; - private final boolean mVisitResources; - private final boolean mVisitMethods; - private final boolean mVisitConstructors; - private final boolean mVisitReferences; - - DelegatingPsiVisitor(JavaContext context) { - mContext = context; - - mVisitMethods = !mMethodDetectors.isEmpty(); - mVisitConstructors = !mConstructorDetectors.isEmpty(); - mVisitResources = !mResourceFieldDetectors.isEmpty(); - mVisitReferences = !mReferenceDetectors.isEmpty(); - } - - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - if (mVisitReferences || mVisitResources) { - ProgressManager.checkCanceled(); - } - - if (mVisitReferences) { - List<VisitingDetector> list = mReferenceDetectors.get(node.getIdentifier()); - if (list != null) { - PsiElement referenced = node.resolve(); - if (referenced != null) { - for (VisitingDetector v : list) { - UastScanner uastScanner = v.getUastScanner(); - if (uastScanner != null) { - uastScanner.visitReference(mContext, v.getVisitor(), - node, referenced); - } - } - } - } - } - - if (mVisitResources) { - AndroidReference androidReference = UastLintUtils.toAndroidReferenceViaResolve(node); - if (androidReference != null) { - for (VisitingDetector v : mResourceFieldDetectors) { - UastScanner uastScanner = v.getUastScanner(); - if (uastScanner != null) { - uastScanner.visitResourceReference(mContext, v.getVisitor(), - androidReference.node, - androidReference.getType(), - androidReference.getName(), - androidReference.getPackage().equals(ANDROID_PKG)); - } - } - } - } - - return super.visitSimpleNameReferenceExpression(node); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - boolean result = super.visitCallExpression(node); - - ProgressManager.checkCanceled(); - - if (UastExpressionUtils.isMethodCall(node)) { - visitMethodCallExpression(node); - } else if (UastExpressionUtils.isConstructorCall(node)) { - visitNewExpression(node); - } - - return result; - } - - private void visitMethodCallExpression(UCallExpression node) { - if (mVisitMethods) { - String methodName = node.getMethodName(); - if (methodName != null) { - List<VisitingDetector> list = mMethodDetectors.get(methodName); - if (list != null) { - PsiMethod function = node.resolve(); - if (function != null) { - for (VisitingDetector v : list) { - UastScanner scanner = v.getUastScanner(); - if (scanner != null) { - scanner.visitMethod(mContext, v.getVisitor(), node, - mContext.getUastContext().getMethod(function)); - } - } - } - } - } - } - } - - private void visitNewExpression(UCallExpression node) { - if (mVisitConstructors) { - PsiMethod resolvedConstructor = node.resolve(); - if (resolvedConstructor == null) { - return; - } - - PsiClass resolvedClass = resolvedConstructor.getContainingClass(); - if (resolvedClass != null) { - List<VisitingDetector> list = mConstructorDetectors.get( - resolvedClass.getQualifiedName()); - if (list != null) { - for (VisitingDetector v : list) { - UastScanner javaPsiScanner = v.getUastScanner(); - if (javaPsiScanner != null) { - javaPsiScanner.visitConstructor(mContext, - v.getVisitor(), node, - mContext.getUastContext().getMethod(resolvedConstructor)); - } - } - } - } - } - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java deleted file mode 100644 index 84922d2..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java +++ /dev/null
@@ -1,338 +0,0 @@ -/* - * 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.klint.client.api; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceType; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.JavaContext; -import com.google.common.base.Joiner; -import com.intellij.psi.*; - -import org.jetbrains.uast.*; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.java.JavaAbstractUExpression; -import org.jetbrains.uast.java.JavaUDeclarationsExpression; - -import java.util.Collections; -import java.util.List; - -public class UastLintUtils { - @Nullable - public static String getQualifiedName(PsiElement element) { - if (element instanceof PsiClass) { - return ((PsiClass) element).getQualifiedName(); - } else if (element instanceof PsiMethod) { - PsiClass containingClass = ((PsiMethod) element).getContainingClass(); - if (containingClass == null) { - return null; - } - String containingClassFqName = getQualifiedName(containingClass); - if (containingClassFqName == null) { - return null; - } - return containingClassFqName + "." + ((PsiMethod) element).getName(); - } else if (element instanceof PsiField) { - PsiClass containingClass = ((PsiField) element).getContainingClass(); - if (containingClass == null) { - return null; - } - String containingClassFqName = getQualifiedName(containingClass); - if (containingClassFqName == null) { - return null; - } - return containingClassFqName + "." + ((PsiField) element).getName(); - } else { - return null; - } - } - - @Nullable - public static PsiElement resolve(ExternalReferenceExpression expression, UElement context) { - UDeclaration declaration = UastUtils.getParentOfType(context, UDeclaration.class); - if (declaration == null) { - return null; - } - - return expression.resolve(declaration.getPsi()); - } - - @NonNull - public static String getClassName(PsiClassType type) { - PsiClass psiClass = type.resolve(); - if (psiClass == null) { - return type.getClassName(); - } else { - return getClassName(psiClass); - } - } - - @NonNull - public static String getClassName(PsiClass psiClass) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(psiClass.getName()); - psiClass = psiClass.getContainingClass(); - while (psiClass != null) { - stringBuilder.insert(0, psiClass.getName() + "."); - psiClass = psiClass.getContainingClass(); - } - return stringBuilder.toString(); - } - - @Nullable - public static UExpression findLastAssignment( - @NonNull PsiVariable variable, - @NonNull UElement call, - @NonNull JavaContext context) { - UElement lastAssignment = null; - - if (variable instanceof UVariable) { - variable = ((UVariable) variable).getPsi(); - } - - if (!variable.hasModifierProperty(PsiModifier.FINAL) && - (variable instanceof PsiLocalVariable || variable instanceof PsiParameter)) { - UMethod containingFunction = UastUtils.getContainingUMethod(call); - if (containingFunction != null) { - ConstantEvaluator.LastAssignmentFinder finder = - new ConstantEvaluator.LastAssignmentFinder(variable, call, context, null, -1); - containingFunction.accept(finder); - lastAssignment = finder.getLastAssignment(); - } - } else { - lastAssignment = context.getUastContext().getInitializerBody(variable); - } - - if (lastAssignment instanceof UExpression) { - return (UExpression) lastAssignment; - } - - return null; - } - - @Nullable - public static String getReferenceName(UReferenceExpression expression) { - if (expression instanceof USimpleNameReferenceExpression) { - return ((USimpleNameReferenceExpression) expression).getIdentifier(); - } else if (expression instanceof UQualifiedReferenceExpression) { - UExpression selector = ((UQualifiedReferenceExpression) expression).getSelector(); - if (selector instanceof USimpleNameReferenceExpression) { - return ((USimpleNameReferenceExpression) selector).getIdentifier(); - } - } - - return null; - } - - @Nullable - public static Object findLastValue( - @NonNull PsiVariable variable, - @NonNull UElement call, - @NonNull JavaContext context, - @NonNull ConstantEvaluator evaluator) { - Object value = null; - - if (!variable.hasModifierProperty(PsiModifier.FINAL) && - (variable instanceof PsiLocalVariable || variable instanceof PsiParameter)) { - UMethod containingFunction = UastUtils.getContainingUMethod(call); - if (containingFunction != null) { - ConstantEvaluator.LastAssignmentFinder - finder = new ConstantEvaluator.LastAssignmentFinder( - variable, call, context, evaluator, 1); - containingFunction.getUastBody().accept(finder); - value = finder.getCurrentValue(); - } - } else { - UExpression initializer = context.getUastContext().getInitializerBody(variable); - if (initializer != null) { - value = initializer.evaluate(); - } - } - - return value; - } - - @Nullable - private static AndroidReference toAndroidReference(UQualifiedReferenceExpression expression) { - List<String> path = UastUtils.asQualifiedPath(expression); - - String packageNameFromResolved = null; - - PsiClass containingClass = UastUtils.getContainingClass(expression.resolve()); - if (containingClass != null) { - String containingClassFqName = containingClass.getQualifiedName(); - - if (containingClassFqName != null) { - int i = containingClassFqName.lastIndexOf(".R."); - if (i >= 0) { - packageNameFromResolved = containingClassFqName.substring(0, i); - } - } - } - - if (path == null) { - return null; - } - - int size = path.size(); - if (size < 3) { - return null; - } - - String r = path.get(size - 3); - if (!r.equals(SdkConstants.R_CLASS)) { - return null; - } - - String packageName = packageNameFromResolved != null - ? packageNameFromResolved - : Joiner.on('.').join(path.subList(0, size - 3)); - - String type = path.get(size - 2); - String name = path.get(size - 1); - - ResourceType resourceType = null; - for (ResourceType value : ResourceType.values()) { - if (value.getName().equals(type)) { - resourceType = value; - break; - } - } - - if (resourceType == null) { - return null; - } - - return new AndroidReference(expression, packageName, resourceType, name); - } - - - @Nullable - public static AndroidReference toAndroidReferenceViaResolve(UElement element) { - if (element instanceof UQualifiedReferenceExpression - && element instanceof JavaAbstractUExpression) { - AndroidReference ref = toAndroidReference((UQualifiedReferenceExpression) element); - if (ref != null) { - return ref; - } - } - - PsiElement declaration; - if (element instanceof UVariable) { - declaration = ((UVariable) element).getPsi(); - } else if (element instanceof UResolvable) { - declaration = ((UResolvable) element).resolve(); - } else { - return null; - } - - if (declaration == null && element instanceof USimpleNameReferenceExpression - && element instanceof JavaAbstractUExpression) { - // R class can't be resolved in tests so we need to use heuristics to calc the reference - UExpression maybeQualified = UastUtils.getQualifiedParentOrThis((UExpression) element); - if (maybeQualified instanceof UQualifiedReferenceExpression) { - AndroidReference ref = toAndroidReference( - (UQualifiedReferenceExpression) maybeQualified); - if (ref != null) { - return ref; - } - } - } - - if (!(declaration instanceof PsiVariable)) { - return null; - } - - PsiVariable variable = (PsiVariable) declaration; - if (!(variable instanceof PsiField) - || variable.getType() != PsiType.INT - || !variable.hasModifierProperty(PsiModifier.STATIC) - || !variable.hasModifierProperty(PsiModifier.FINAL)) { - return null; - } - - PsiClass resTypeClass = ((PsiField) variable).getContainingClass(); - if (resTypeClass == null || !resTypeClass.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - - PsiClass rClass = resTypeClass.getContainingClass(); - if (rClass == null || rClass.getContainingClass() != null || !"R".equals(rClass.getName())) { - return null; - } - - String packageName = ((PsiJavaFile) rClass.getContainingFile()).getPackageName(); - if (packageName.isEmpty()) { - return null; - } - - String resourceTypeName = resTypeClass.getName(); - ResourceType resourceType = null; - for (ResourceType value : ResourceType.values()) { - if (value.getName().equals(resourceTypeName)) { - resourceType = value; - break; - } - } - - if (resourceType == null) { - return null; - } - - String resourceName = variable.getName(); - - UExpression node; - if (element instanceof UExpression) { - node = (UExpression) element; - } else if (element instanceof UVariable) { - node = new JavaUDeclarationsExpression( - null, Collections.singletonList(((UVariable) element))); - } else { - throw new IllegalArgumentException("element must be an expression or an UVariable"); - } - - return new AndroidReference(node, packageName, resourceType, resourceName); - } - - public static boolean areIdentifiersEqual(UExpression first, UExpression second) { - String firstIdentifier = getIdentifier(first); - String secondIdentifier = getIdentifier(second); - return firstIdentifier != null && secondIdentifier != null - && firstIdentifier.equals(secondIdentifier); - } - - @Nullable - public static String getIdentifier(UExpression expression) { - if (expression instanceof ULiteralExpression) { - expression.asRenderString(); - } else if (expression instanceof USimpleNameReferenceExpression) { - return ((USimpleNameReferenceExpression) expression).getIdentifier(); - } else if (expression instanceof UQualifiedReferenceExpression) { - UQualifiedReferenceExpression qualified = (UQualifiedReferenceExpression) expression; - String receiverIdentifier = getIdentifier(qualified.getReceiver()); - String selectorIdentifier = getIdentifier(qualified.getSelector()); - if (receiverIdentifier == null || selectorIdentifier == null) { - return null; - } - return receiverIdentifier + "." + selectorIdentifier; - } - - return null; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/XmlParser.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/XmlParser.java deleted file mode 100644 index 4a50c67..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/XmlParser.java +++ /dev/null
@@ -1,137 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.client.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.XmlContext; -import com.google.common.annotations.Beta; - -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -/** - * A wrapper for an XML parser. This allows tools integrating lint to map directly - * to builtin services, such as already-parsed data structures in XML editors. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class XmlParser { - /** - * Parse the file pointed to by the given context and return as a Document - * - * @param context the context pointing to the file to be parsed, typically - * via {@link Context#getContents()} but the file handle ( - * {@link Context#file} can also be used to map to an existing - * editor buffer in the surrounding tool, etc) - * @return the parsed DOM document, or null if parsing fails - */ - @Nullable - public abstract Document parseXml(@NonNull XmlContext context); - - /** - * Returns a {@link Location} for the given DOM node - * - * @param context information about the file being parsed - * @param node the node to create a location for - * @return a location for the given node - */ - @NonNull - public abstract Location getLocation(@NonNull XmlContext context, @NonNull Node node); - - /** - * Returns a {@link Location} for the given DOM node. Like - * {@link #getLocation(XmlContext, Node)}, but allows a position range that - * is a subset of the node range. - * - * @param context information about the file being parsed - * @param node the node to create a location for - * @param start the starting position within the node, inclusive - * @param end the ending position within the node, exclusive - * @return a location for the given node - */ - @NonNull - public abstract Location getLocation(@NonNull XmlContext context, @NonNull Node node, - int start, int end); - - /** - * Returns a {@link Location} for the given DOM node - * - * @param context information about the file being parsed - * @param node the node to create a location for - * @return a location for the given node - */ - @NonNull - public abstract Location getNameLocation(@NonNull XmlContext context, @NonNull Node node); - - /** - * Returns a {@link Location} for the given DOM node - * - * @param context information about the file being parsed - * @param node the node to create a location for - * @return a location for the given node - */ - @NonNull - public abstract Location getValueLocation(@NonNull XmlContext context, @NonNull Attr node); - - /** - * Creates a light-weight handle to a location for the given node. It can be - * turned into a full fledged location by - * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}. - * - * @param context the context providing the node - * @param node the node (element or attribute) to create a location handle - * for - * @return a location handle - */ - @NonNull - public abstract Location.Handle createLocationHandle(@NonNull XmlContext context, - @NonNull Node node); - - /** - * Dispose any data structures held for the given context. - * @param context information about the file previously parsed - * @param document the document that was parsed and is now being disposed - */ - public void dispose(@NonNull XmlContext context, @NonNull Document document) { - } - - /** - * Returns the start offset of the given node, or -1 if not known - * - * @param context the context providing the node - * @param node the node (element or attribute) to create a location handle - * for - * @return the start offset, or -1 if not known - */ - public abstract int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node); - - /** - * Returns the end offset of the given node, or -1 if not known - * - * @param context the context providing the node - * @param node the node (element or attribute) to create a location handle - * for - * @return the end offset, or -1 if not known - */ - public abstract int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node); -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Category.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Category.java deleted file mode 100644 index 8bcb56a..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Category.java +++ /dev/null
@@ -1,190 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.google.common.annotations.Beta; - -/** - * A category is a container for related issues. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public final class Category implements Comparable<Category> { - private final String mName; - private final int mPriority; - private final Category mParent; - - /** - * Creates a new {@link Category}. - * - * @param parent the name of a parent category, or null - * @param name the name of the category - * @param priority a sorting priority, with higher being more important - */ - private Category( - @Nullable Category parent, - @NonNull String name, - int priority) { - mParent = parent; - mName = name; - mPriority = priority; - } - - /** - * Creates a new top level {@link Category} with the given sorting priority. - * - * @param name the name of the category - * @param priority a sorting priority, with higher being more important - * @return a new category - */ - @NonNull - public static Category create(@NonNull String name, int priority) { - return new Category(null, name, priority); - } - - /** - * Creates a new top level {@link Category} with the given sorting priority. - * - * @param parent the name of a parent category, or null - * @param name the name of the category - * @param priority a sorting priority, with higher being more important - * @return a new category - */ - @NonNull - public static Category create(@Nullable Category parent, @NonNull String name, int priority) { - return new Category(parent, name, priority); - } - - /** - * Returns the parent category, or null if this is a top level category - * - * @return the parent category, or null if this is a top level category - */ - public Category getParent() { - return mParent; - } - - /** - * Returns the name of this category - * - * @return the name of this category - */ - public String getName() { - return mName; - } - - /** - * Returns a full name for this category. For a top level category, this is just - * the {@link #getName()} value, but for nested categories it will include the parent - * names as well. - * - * @return a full name for this category - */ - public String getFullName() { - if (mParent != null) { - return mParent.getFullName() + ':' + mName; - } else { - return mName; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Category category = (Category) o; - - //noinspection SimplifiableIfStatement - if (!mName.equals(category.mName)) { - return false; - } - return mParent != null ? mParent.equals(category.mParent) : category.mParent == null; - - } - - @Override - public String toString() { - return getFullName(); - } - - @Override - public int hashCode() { - return mName.hashCode(); - } - - @Override - public int compareTo(@NonNull Category other) { - if (other.mPriority == mPriority) { - if (mParent == other) { - return 1; - } else if (other.mParent == this) { - return -1; - } - } - - int delta = other.mPriority - mPriority; - if (delta != 0) { - return delta; - } - - return mName.compareTo(other.mName); - } - - /** Issues related to running lint itself */ - public static final Category LINT = create("Lint", 110); - - /** Issues related to correctness */ - public static final Category CORRECTNESS = create("Correctness", 100); - - /** Issues related to security */ - public static final Category SECURITY = create("Security", 90); - - /** Issues related to performance */ - public static final Category PERFORMANCE = create("Performance", 80); - - /** Issues related to usability */ - public static final Category USABILITY = create("Usability", 70); - - /** Issues related to accessibility */ - public static final Category A11Y = create("Accessibility", 60); - - /** Issues related to internationalization */ - public static final Category I18N = create("Internationalization", 50); - - // Sub categories - - /** Issues related to icons */ - public static final Category ICONS = create(USABILITY, "Icons", 73); - - /** Issues related to typography */ - public static final Category TYPOGRAPHY = create(USABILITY, "Typography", 76); - - /** Issues related to messages/strings */ - public static final Category MESSAGES = create(CORRECTNESS, "Messages", 95); - - /** Issues related to right to left and bidirectional text support */ - public static final Category RTL = create(I18N, "Bidirectional Text", 40); -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ClassContext.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ClassContext.java deleted file mode 100644 index 07759bd..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ClassContext.java +++ /dev/null
@@ -1,725 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import static com.android.SdkConstants.CONSTRUCTOR_NAME; -import static com.android.SdkConstants.DOT_CLASS; -import static com.android.SdkConstants.DOT_JAVA; -import static com.android.tools.klint.detector.api.Location.SearchDirection.BACKWARD; -import static com.android.tools.klint.detector.api.Location.SearchDirection.EOL_BACKWARD; -import static com.android.tools.klint.detector.api.Location.SearchDirection.FORWARD; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.detector.api.Location.SearchDirection; -import com.android.tools.klint.detector.api.Location.SearchHints; -import com.google.common.annotations.Beta; -import com.google.common.base.Splitter; - -import org.jetbrains.org.objectweb.asm.Type; -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.jetbrains.org.objectweb.asm.tree.FieldNode; -import org.jetbrains.org.objectweb.asm.tree.LineNumberNode; -import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; -import org.jetbrains.org.objectweb.asm.tree.MethodNode; - -import java.io.File; -import java.util.List; - -/** - * A {@link Context} used when checking .class files. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class ClassContext extends Context { - private final File mBinDir; - /** The class file DOM root node */ - private final ClassNode mClassNode; - /** The class file byte data */ - private final byte[] mBytes; - /** The source file, if known/found */ - private File mSourceFile; - /** The contents of the source file, if source file is known/found */ - private String mSourceContents; - /** Whether we've searched for the source file (used to avoid repeated failed searches) */ - private boolean mSearchedForSource; - /** If the file is a relative path within a jar file, this is the jar file, otherwise null */ - private final File mJarFile; - /** Whether this class is part of a library (rather than corresponding to one of the - * source files in this project */ - private final boolean mFromLibrary; - - /** - * Construct a new {@link ClassContext} - * - * @param driver the driver running through the checks - * @param project the project containing the file being checked - * @param main the main project if this project is a library project, or - * null if this is not a library project. The main project is the - * root project of all library projects, not necessarily the - * directly including project. - * @param file the file being checked - * @param jarFile If the file is a relative path within a jar file, this is - * the jar file, otherwise null - * @param binDir the root binary directory containing this .class file. - * @param bytes the bytecode raw data - * @param classNode the bytecode object model - * @param fromLibrary whether this class is from a library rather than part - * of this project - * @param sourceContents initial contents of the Java source, if known, or - * null - */ - public ClassContext( - @NonNull LintDriver driver, - @NonNull Project project, - @Nullable Project main, - @NonNull File file, - @Nullable File jarFile, - @NonNull File binDir, - @NonNull byte[] bytes, - @NonNull ClassNode classNode, - boolean fromLibrary, - @Nullable String sourceContents) { - super(driver, project, main, file); - mJarFile = jarFile; - mBinDir = binDir; - mBytes = bytes; - mClassNode = classNode; - mFromLibrary = fromLibrary; - mSourceContents = sourceContents; - } - - /** - * Returns the raw bytecode data for this class file - * - * @return the byte array containing the bytecode data - */ - @NonNull - public byte[] getBytecode() { - return mBytes; - } - - /** - * Returns the bytecode object model - * - * @return the bytecode object model, never null - */ - @NonNull - public ClassNode getClassNode() { - return mClassNode; - } - - /** - * Returns the jar file, if any. If this is null, the .class file is a real file - * on disk, otherwise it represents a relative path within the jar file. - * - * @return the jar file, or null - */ - @Nullable - public File getJarFile() { - return mJarFile; - } - - /** - * Returns whether this class is part of a library (not this project). - * - * @return true if this class is part of a library - */ - public boolean isFromClassLibrary() { - return mFromLibrary; - } - - /** - * Returns the source file for this class file, if possible. - * - * @return the source file, or null - */ - @Nullable - public File getSourceFile() { - if (mSourceFile == null && !mSearchedForSource) { - mSearchedForSource = true; - - String source = mClassNode.sourceFile; - if (source == null) { - source = file.getName(); - if (source.endsWith(DOT_CLASS)) { - source = source.substring(0, source.length() - DOT_CLASS.length()) + ".kt"; - } - int index = source.indexOf('$'); - if (index != -1) { - source = source.substring(0, index) + ".kt"; - } - } - if (source != null) { - if (mJarFile != null) { - String relative = file.getParent() + File.separator + source; - List<File> sources = getProject().getJavaSourceFolders(); - for (File dir : sources) { - File sourceFile = new File(dir, relative); - if (sourceFile.exists()) { - mSourceFile = sourceFile; - break; - } - } - } else { - // Determine package - String topPath = mBinDir.getPath(); - String parentPath = file.getParentFile().getPath(); - if (parentPath.startsWith(topPath)) { - int start = topPath.length() + 1; - String relative = start > parentPath.length() ? // default package? - "" : parentPath.substring(start); - List<File> sources = getProject().getJavaSourceFolders(); - for (File dir : sources) { - File sourceFile = new File(dir, relative + File.separator + source); - if (sourceFile.exists()) { - mSourceFile = sourceFile; - break; - } - } - } - } - } - } - - return mSourceFile; - } - - /** - * Returns the contents of the source file for this class file, if found. - * - * @return the source contents, or "" - */ - @NonNull - public String getSourceContents() { - if (mSourceContents == null) { - File sourceFile = getSourceFile(); - if (sourceFile != null) { - mSourceContents = getClient().readFile(mSourceFile); - } - - if (mSourceContents == null) { - mSourceContents = ""; - } - } - - return mSourceContents; - } - - /** - * Returns the contents of the source file for this class file, if found. If - * {@code read} is false, do not read the source contents if it has not - * already been read. (This is primarily intended for the lint - * infrastructure; most client code would call {@link #getSourceContents()} - * .) - * - * @param read whether to read the source contents if it has not already - * been initialized - * @return the source contents, which will never be null if {@code read} is - * true, or null if {@code read} is false and the source contents - * hasn't already been read. - */ - @Nullable - public String getSourceContents(boolean read) { - if (read) { - return getSourceContents(); - } else { - return mSourceContents; - } - } - - /** - * Returns a location for the given source line number in this class file's - * source file, if available. - * - * @param line the line number (1-based, which is what ASM uses) - * @param patternStart optional pattern to search for in the source for - * range start - * @param patternEnd optional pattern to search for in the source for range - * end - * @param hints additional hints about the pattern search (provided - * {@code patternStart} is non null) - * @return a location, never null - */ - @NonNull - public Location getLocationForLine(int line, @Nullable String patternStart, - @Nullable String patternEnd, @Nullable SearchHints hints) { - File sourceFile = getSourceFile(); - if (sourceFile != null) { - // ASM line numbers are 1-based, and lint line numbers are 0-based - if (line != -1) { - return Location.create(sourceFile, getSourceContents(), line - 1, - patternStart, patternEnd, hints); - } else { - return Location.create(sourceFile); - } - } - - return Location.create(file); - } - - /** - * Reports an issue. - * <p> - * Detectors should only call this method if an error applies to the whole class - * scope and there is no specific method or field that applies to the error. - * If so, use - * {@link #report(Issue, MethodNode, AbstractInsnNode, Location, String)} or - * {@link #report(Issue, FieldNode, Location, String)}, such that - * suppress annotations are checked. - * - * @param issue the issue to report - * @param location the location of the issue, or null if not known - * @param message the message for this warning - */ - @Override - public void report( - @NonNull Issue issue, - @NonNull Location location, - @NonNull String message) { - if (mDriver.isSuppressed(issue, mClassNode)) { - return; - } - ClassNode curr = mClassNode; - while (curr != null) { - ClassNode prev = curr; - curr = mDriver.getOuterClassNode(curr); - if (curr != null) { - if (prev.outerMethod != null) { - @SuppressWarnings("rawtypes") // ASM API - List methods = curr.methods; - for (Object m : methods) { - MethodNode method = (MethodNode) m; - if (method.name.equals(prev.outerMethod) - && method.desc.equals(prev.outerMethodDesc)) { - // Found the outer method for this anonymous class; continue - // reporting on it (which will also work its way up the parent - // class hierarchy) - if (method != null && mDriver.isSuppressed(issue, mClassNode, method, - null)) { - return; - } - break; - } - } - } - if (mDriver.isSuppressed(issue, curr)) { - return; - } - } - } - - super.report(issue, location, message); - } - - // Unfortunately, ASMs nodes do not extend a common DOM node type with parent - // pointers, so we have to have multiple methods which pass in each type - // of node (class, method, field) to be checked. - - /** - * Reports an issue applicable to a given method node. - * - * @param issue the issue to report - * @param method the method scope the error applies to. The lint - * infrastructure will check whether there are suppress - * annotations on this method (or its enclosing class) and if so - * suppress the warning without involving the client. - * @param instruction the instruction within the method the error applies - * to. You cannot place annotations on individual method - * instructions (for example, annotations on local variables are - * allowed, but are not kept in the .class file). However, this - * instruction is needed to handle suppressing errors on field - * initializations; in that case, the errors may be reported in - * the {@code <clinit>} method, but the annotation is found not - * on that method but for the {@link FieldNode}'s. - * @param location the location of the issue, or null if not known - * @param message the message for this warning - */ - public void report( - @NonNull Issue issue, - @Nullable MethodNode method, - @Nullable AbstractInsnNode instruction, - @NonNull Location location, - @NonNull String message) { - if (method != null && mDriver.isSuppressed(issue, mClassNode, method, instruction)) { - return; - } - report(issue, location, message); // also checks the class node - } - - /** - * Reports an issue applicable to a given method node. - * - * @param issue the issue to report - * @param field the scope the error applies to. The lint infrastructure - * will check whether there are suppress annotations on this field (or its enclosing - * class) and if so suppress the warning without involving the client. - * @param location the location of the issue, or null if not known - * @param message the message for this warning - */ - public void report( - @NonNull Issue issue, - @Nullable FieldNode field, - @NonNull Location location, - @NonNull String message) { - if (field != null && mDriver.isSuppressed(issue, field)) { - return; - } - report(issue, location, message); // also checks the class node - } - - /** - * Report an error. - * Like {@link #report(Issue, MethodNode, AbstractInsnNode, Location, String)} but with - * a now-unused data parameter at the end. - * - * @deprecated Use {@link #report(Issue, FieldNode, Location, String)} instead; - * this method is here for custom rule compatibility - */ - @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules - @Deprecated - public void report( - @NonNull Issue issue, - @Nullable MethodNode method, - @Nullable AbstractInsnNode instruction, - @NonNull Location location, - @NonNull String message, - @SuppressWarnings("UnusedParameters") @Nullable Object data) { - report(issue, method, instruction, location, message); - } - - /** - * Report an error. - * Like {@link #report(Issue, FieldNode, Location, String)} but with - * a now-unused data parameter at the end. - * - * @deprecated Use {@link #report(Issue, FieldNode, Location, String)} instead; - * this method is here for custom rule compatibility - */ - @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules - @Deprecated - public void report( - @NonNull Issue issue, - @Nullable FieldNode field, - @NonNull Location location, - @NonNull String message, - @SuppressWarnings("UnusedParameters") @Nullable Object data) { - report(issue, field, location, message); - } - - /** - * Finds the line number closest to the given node - * - * @param node the instruction node to get a line number for - * @return the closest line number, or -1 if not known - */ - public static int findLineNumber(@NonNull AbstractInsnNode node) { - AbstractInsnNode curr = node; - - // First search backwards - while (curr != null) { - if (curr.getType() == AbstractInsnNode.LINE) { - return ((LineNumberNode) curr).line; - } - curr = curr.getPrevious(); - } - - // Then search forwards - curr = node; - while (curr != null) { - if (curr.getType() == AbstractInsnNode.LINE) { - return ((LineNumberNode) curr).line; - } - curr = curr.getNext(); - } - - return -1; - } - - /** - * Finds the line number closest to the given method declaration - * - * @param node the method node to get a line number for - * @return the closest line number, or -1 if not known - */ - public static int findLineNumber(@NonNull MethodNode node) { - if (node.instructions != null && node.instructions.size() > 0) { - return findLineNumber(node.instructions.get(0)); - } - - return -1; - } - - /** - * Finds the line number closest to the given class declaration - * - * @param node the method node to get a line number for - * @return the closest line number, or -1 if not known - */ - public static int findLineNumber(@NonNull ClassNode node) { - if (node.methods != null && !node.methods.isEmpty()) { - MethodNode firstMethod = getFirstRealMethod(node); - if (firstMethod != null) { - return findLineNumber(firstMethod); - } - } - - return -1; - } - - /** - * Returns a location for the given {@link ClassNode}, where class node is - * either the top level class, or an inner class, in the current context. - * - * @param classNode the class in the current context - * @return a location pointing to the class declaration, or as close to it - * as possible - */ - @NonNull - public Location getLocation(@NonNull ClassNode classNode) { - // Attempt to find a proper location for this class. This is tricky - // since classes do not have line number entries in the class file; we need - // to find a method, look up the corresponding line number then search - // around it for a suitable tag, such as the class name. - String pattern; - if (isAnonymousClass(classNode.name)) { - pattern = classNode.superName; - } else { - pattern = classNode.name; - } - int index = pattern.lastIndexOf('$'); - if (index != -1) { - pattern = pattern.substring(index + 1); - } - index = pattern.lastIndexOf('/'); - if (index != -1) { - pattern = pattern.substring(index + 1); - } - - return getLocationForLine(findLineNumber(classNode), pattern, null, - SearchHints.create(BACKWARD).matchJavaSymbol()); - } - - @Nullable - private static MethodNode getFirstRealMethod(@NonNull ClassNode classNode) { - // Return the first method in the class for line number purposes. Skip <init>, - // since it's typically not located near the real source of the method. - if (classNode.methods != null) { - @SuppressWarnings("rawtypes") // ASM API - List methods = classNode.methods; - for (Object m : methods) { - MethodNode method = (MethodNode) m; - if (method.name.charAt(0) != '<') { - return method; - } - } - - if (!classNode.methods.isEmpty()) { - return (MethodNode) classNode.methods.get(0); - } - } - - return null; - } - - /** - * Returns a location for the given {@link MethodNode}. - * - * @param methodNode the class in the current context - * @param classNode the class containing the method - * @return a location pointing to the class declaration, or as close to it - * as possible - */ - @NonNull - public Location getLocation(@NonNull MethodNode methodNode, - @NonNull ClassNode classNode) { - // Attempt to find a proper location for this class. This is tricky - // since classes do not have line number entries in the class file; we need - // to find a method, look up the corresponding line number then search - // around it for a suitable tag, such as the class name. - String pattern; - SearchDirection searchMode; - if (methodNode.name.equals(CONSTRUCTOR_NAME)) { - searchMode = EOL_BACKWARD; - if (isAnonymousClass(classNode.name)) { - pattern = classNode.superName.substring(classNode.superName.lastIndexOf('/') + 1); - } else { - pattern = classNode.name.substring(classNode.name.lastIndexOf('$') + 1); - } - } else { - searchMode = BACKWARD; - pattern = methodNode.name; - } - - return getLocationForLine(findLineNumber(methodNode), pattern, null, - SearchHints.create(searchMode).matchJavaSymbol()); - } - - /** - * Returns a location for the given {@link AbstractInsnNode}. - * - * @param instruction the instruction to look up the location for - * @return a location pointing to the instruction, or as close to it - * as possible - */ - @NonNull - public Location getLocation(@NonNull AbstractInsnNode instruction) { - SearchHints hints = SearchHints.create(FORWARD).matchJavaSymbol(); - String pattern = null; - if (instruction instanceof MethodInsnNode) { - MethodInsnNode call = (MethodInsnNode) instruction; - if (call.name.equals(CONSTRUCTOR_NAME)) { - pattern = call.owner; - hints = hints.matchConstructor(); - } else { - pattern = call.name; - } - int index = pattern.lastIndexOf('$'); - if (index != -1) { - pattern = pattern.substring(index + 1); - } - index = pattern.lastIndexOf('/'); - if (index != -1) { - pattern = pattern.substring(index + 1); - } - } - - int line = findLineNumber(instruction); - return getLocationForLine(line, pattern, null, hints); - } - - private static boolean isAnonymousClass(@NonNull String fqcn) { - int lastIndex = fqcn.lastIndexOf('$'); - if (lastIndex != -1 && lastIndex < fqcn.length() - 1) { - if (Character.isDigit(fqcn.charAt(lastIndex + 1))) { - return true; - } - } - return false; - } - - /** - * Converts from a VM owner name (such as foo/bar/Foo$Baz) to a - * fully qualified class name (such as foo.bar.Foo.Baz). - * - * @param owner the owner name to convert - * @return the corresponding fully qualified class name - */ - @NonNull - public static String getFqcn(@NonNull String owner) { - return owner.replace('/', '.').replace('$','.'); - } - - /** - * Computes a user-readable type signature from the given class owner, name - * and description. For example, for owner="foo/bar/Foo$Baz", name="foo", - * description="(I)V", it returns "void foo.bar.Foo.Bar#foo(int)". - * - * @param owner the class name - * @param name the method name - * @param desc the method description - * @return a user-readable string - */ - public static String createSignature(String owner, String name, String desc) { - StringBuilder sb = new StringBuilder(100); - - if (desc != null) { - Type returnType = Type.getReturnType(desc); - sb.append(getTypeString(returnType)); - sb.append(' '); - } - - if (owner != null) { - sb.append(getFqcn(owner)); - } - if (name != null) { - sb.append('#'); - sb.append(name); - if (desc != null) { - Type[] argumentTypes = Type.getArgumentTypes(desc); - if (argumentTypes != null && argumentTypes.length > 0) { - sb.append('('); - boolean first = true; - for (Type type : argumentTypes) { - if (first) { - first = false; - } else { - sb.append(", "); - } - sb.append(getTypeString(type)); - } - sb.append(')'); - } - } - } - - return sb.toString(); - } - - private static String getTypeString(Type type) { - String s = type.getClassName(); - if (s.startsWith("java.lang.")) { //$NON-NLS-1$ - s = s.substring("java.lang.".length()); //$NON-NLS-1$ - } - - return s; - } - - /** - * Computes the internal class name of the given fully qualified class name. - * For example, it converts foo.bar.Foo.Bar into foo/bar/Foo$Bar - * - * @param fqcn the fully qualified class name - * @return the internal class name - */ - @NonNull - public static String getInternalName(@NonNull String fqcn) { - if (fqcn.indexOf('.') == -1) { - return fqcn; - } - - int index = fqcn.indexOf('<'); - if(index != -1) { - fqcn = fqcn.substring(0, index); - } - - // If class name contains $, it's not an ambiguous inner class name. - if (fqcn.indexOf('$') != -1) { - return fqcn.replace('.', '/'); - } - // Let's assume that components that start with Caps are class names. - StringBuilder sb = new StringBuilder(fqcn.length()); - String prev = null; - for (String part : Splitter.on('.').split(fqcn)) { - if (prev != null && !prev.isEmpty()) { - if (Character.isUpperCase(prev.charAt(0))) { - sb.append('$'); - } else { - sb.append('/'); - } - } - sb.append(part); - prev = part; - } - - return sb.toString(); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ConstantEvaluator.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ConstantEvaluator.java deleted file mode 100644 index b6d8650..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ConstantEvaluator.java +++ /dev/null
@@ -1,1924 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.detector.api; - -import static com.android.tools.klint.client.api.JavaParser.TYPE_BOOLEAN; -import static com.android.tools.klint.client.api.JavaParser.TYPE_BYTE; -import static com.android.tools.klint.client.api.JavaParser.TYPE_CHAR; -import static com.android.tools.klint.client.api.JavaParser.TYPE_DOUBLE; -import static com.android.tools.klint.client.api.JavaParser.TYPE_FLOAT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_INT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_LONG; -import static com.android.tools.klint.client.api.JavaParser.TYPE_OBJECT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_SHORT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; -import static com.android.tools.klint.detector.api.JavaContext.getParentOfType; -import static org.jetbrains.uast.UastBinaryExpressionWithTypeKind.TYPE_CAST; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaParser.ResolvedField; -import com.android.tools.klint.client.api.JavaParser.ResolvedNode; -import com.android.tools.klint.client.api.UastLintUtils; -import com.google.common.collect.Lists; -import com.intellij.psi.JavaTokenType; -import com.intellij.psi.PsiArrayInitializerExpression; -import com.intellij.psi.PsiArrayType; -import com.intellij.psi.PsiAssignmentExpression; -import com.intellij.psi.PsiBinaryExpression; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiConditionalExpression; -import com.intellij.psi.PsiDeclarationStatement; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiExpressionStatement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiLiteral; -import com.intellij.psi.PsiLocalVariable; -import com.intellij.psi.PsiNewExpression; -import com.intellij.psi.PsiParenthesizedExpression; -import com.intellij.psi.PsiPrefixExpression; -import com.intellij.psi.PsiPrimitiveType; -import com.intellij.psi.PsiReference; -import com.intellij.psi.PsiReferenceExpression; -import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiType; -import com.intellij.psi.PsiTypeCastExpression; -import com.intellij.psi.PsiTypeElement; -import com.intellij.psi.PsiVariable; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PsiTreeUtil; - -import org.jetbrains.uast.*; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -import lombok.ast.ArrayCreation; -import lombok.ast.ArrayInitializer; -import lombok.ast.BinaryExpression; -import lombok.ast.BinaryOperator; -import lombok.ast.BooleanLiteral; -import lombok.ast.Cast; -import lombok.ast.CharLiteral; -import lombok.ast.Expression; -import lombok.ast.ExpressionStatement; -import lombok.ast.FloatingPointLiteral; -import lombok.ast.InlineIfExpression; -import lombok.ast.IntegralLiteral; -import lombok.ast.Node; -import lombok.ast.NullLiteral; -import lombok.ast.Select; -import lombok.ast.Statement; -import lombok.ast.StrictListAccessor; -import lombok.ast.StringLiteral; -import lombok.ast.TypeReference; -import lombok.ast.UnaryExpression; -import lombok.ast.UnaryOperator; -import lombok.ast.VariableDeclaration; -import lombok.ast.VariableDefinition; -import lombok.ast.VariableDefinitionEntry; -import lombok.ast.VariableReference; - -/** Evaluates constant expressions */ -public class ConstantEvaluator { - private final JavaContext mContext; - private boolean mAllowUnknown; - - /** - * Creates a new constant evaluator - * - * @param context the context to use to resolve field references, if any - */ - public ConstantEvaluator(@Nullable JavaContext context) { - mContext = context; - } - - /** - * Whether we allow computing values where some terms are unknown. For example, the expression - * {@code "foo" + x + "bar"} would return {@code null} without and {@code "foobar"} with. - * - * @return this for constructor chaining - */ - public ConstantEvaluator allowUnknowns() { - mAllowUnknown = true; - return this; - } - - /** - * Evaluates the given node and returns the constant value it resolves to, if any - * - * @param node the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - * @deprecated Use {@link #evaluate(PsiElement)} instead - */ - @Deprecated - @Nullable - public Object evaluate(@NonNull Node node) { - if (node instanceof NullLiteral) { - return null; - } else if (node instanceof BooleanLiteral) { - return ((BooleanLiteral)node).astValue(); - } else if (node instanceof StringLiteral) { - StringLiteral string = (StringLiteral) node; - return string.astValue(); - } else if (node instanceof CharLiteral) { - return ((CharLiteral)node).astValue(); - } else if (node instanceof IntegralLiteral) { - IntegralLiteral literal = (IntegralLiteral) node; - // Don't combine to ?: since that will promote astIntValue to a long - if (literal.astMarkedAsLong()) { - return literal.astLongValue(); - } else { - return literal.astIntValue(); - } - } else if (node instanceof FloatingPointLiteral) { - FloatingPointLiteral literal = (FloatingPointLiteral) node; - // Don't combine to ?: since that will promote astFloatValue to a double - if (literal.astMarkedAsFloat()) { - return literal.astFloatValue(); - } else { - return literal.astDoubleValue(); - } - } else if (node instanceof UnaryExpression) { - UnaryOperator operator = ((UnaryExpression) node).astOperator(); - Object operand = evaluate(((UnaryExpression) node).astOperand()); - if (operand == null) { - return null; - } - switch (operator) { - case LOGICAL_NOT: - if (operand instanceof Boolean) { - return !(Boolean) operand; - } - break; - case UNARY_PLUS: - return operand; - case BINARY_NOT: - if (operand instanceof Integer) { - return ~(Integer) operand; - } else if (operand instanceof Long) { - return ~(Long) operand; - } else if (operand instanceof Short) { - return ~(Short) operand; - } else if (operand instanceof Character) { - return ~(Character) operand; - } else if (operand instanceof Byte) { - return ~(Byte) operand; - } - break; - case UNARY_MINUS: - if (operand instanceof Integer) { - return -(Integer) operand; - } else if (operand instanceof Long) { - return -(Long) operand; - } else if (operand instanceof Double) { - return -(Double) operand; - } else if (operand instanceof Float) { - return -(Float) operand; - } else if (operand instanceof Short) { - return -(Short) operand; - } else if (operand instanceof Character) { - return -(Character) operand; - } else if (operand instanceof Byte) { - return -(Byte) operand; - } - break; - } - } else if (node instanceof InlineIfExpression) { - InlineIfExpression expression = (InlineIfExpression) node; - Object known = evaluate(expression.astCondition()); - if (known == Boolean.TRUE && expression.astIfTrue() != null) { - return evaluate(expression.astIfTrue()); - } else if (known == Boolean.FALSE && expression.astIfFalse() != null) { - return evaluate(expression.astIfFalse()); - } - } else if (node instanceof BinaryExpression) { - BinaryOperator operator = ((BinaryExpression) node).astOperator(); - Object operandLeft = evaluate(((BinaryExpression) node).astLeft()); - Object operandRight = evaluate(((BinaryExpression) node).astRight()); - if (operandLeft == null || operandRight == null) { - if (mAllowUnknown) { - if (operandLeft == null) { - return operandRight; - } else { - return operandLeft; - } - } - return null; - } - if (operandLeft instanceof String && operandRight instanceof String) { - if (operator == BinaryOperator.PLUS) { - return operandLeft.toString() + operandRight.toString(); - } - return null; - } else if (operandLeft instanceof Boolean && operandRight instanceof Boolean) { - boolean left = (Boolean) operandLeft; - boolean right = (Boolean) operandRight; - switch (operator) { - case LOGICAL_OR: - return left || right; - case LOGICAL_AND: - return left && right; - case BITWISE_OR: - return left | right; - case BITWISE_XOR: - return left ^ right; - case BITWISE_AND: - return left & right; - case EQUALS: - return left == right; - case NOT_EQUALS: - return left != right; - } - } else if (operandLeft instanceof Number && operandRight instanceof Number) { - Number left = (Number) operandLeft; - Number right = (Number) operandRight; - boolean isInteger = - !(left instanceof Float || left instanceof Double - || right instanceof Float || right instanceof Double); - boolean isWide = - isInteger ? (left instanceof Long || right instanceof Long) - : (left instanceof Double || right instanceof Double); - - switch (operator) { - case BITWISE_OR: - if (isWide) { - return left.longValue() | right.longValue(); - } else { - return left.intValue() | right.intValue(); - } - case BITWISE_XOR: - if (isWide) { - return left.longValue() ^ right.longValue(); - } else { - return left.intValue() ^ right.intValue(); - } - case BITWISE_AND: - if (isWide) { - return left.longValue() & right.longValue(); - } else { - return left.intValue() & right.intValue(); - } - case EQUALS: - if (isInteger) { - return left.longValue() == right.longValue(); - } else { - return left.doubleValue() == right.doubleValue(); - } - case NOT_EQUALS: - if (isInteger) { - return left.longValue() != right.longValue(); - } else { - return left.doubleValue() != right.doubleValue(); - } - case GREATER: - if (isInteger) { - return left.longValue() > right.longValue(); - } else { - return left.doubleValue() > right.doubleValue(); - } - case GREATER_OR_EQUAL: - if (isInteger) { - return left.longValue() >= right.longValue(); - } else { - return left.doubleValue() >= right.doubleValue(); - } - case LESS: - if (isInteger) { - return left.longValue() < right.longValue(); - } else { - return left.doubleValue() < right.doubleValue(); - } - case LESS_OR_EQUAL: - if (isInteger) { - return left.longValue() <= right.longValue(); - } else { - return left.doubleValue() <= right.doubleValue(); - } - case SHIFT_LEFT: - if (isWide) { - return left.longValue() << right.intValue(); - } else { - return left.intValue() << right.intValue(); - } - case SHIFT_RIGHT: - if (isWide) { - return left.longValue() >> right.intValue(); - } else { - return left.intValue() >> right.intValue(); - } - case BITWISE_SHIFT_RIGHT: - if (isWide) { - return left.longValue() >>> right.intValue(); - } else { - return left.intValue() >>> right.intValue(); - } - case PLUS: - if (isInteger) { - if (isWide) { - return left.longValue() + right.longValue(); - } else { - return left.intValue() + right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() + right.doubleValue(); - } else { - return left.floatValue() + right.floatValue(); - } - } - case MINUS: - if (isInteger) { - if (isWide) { - return left.longValue() - right.longValue(); - } else { - return left.intValue() - right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() - right.doubleValue(); - } else { - return left.floatValue() - right.floatValue(); - } - } - case MULTIPLY: - if (isInteger) { - if (isWide) { - return left.longValue() * right.longValue(); - } else { - return left.intValue() * right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() * right.doubleValue(); - } else { - return left.floatValue() * right.floatValue(); - } - } - case DIVIDE: - if (isInteger) { - if (isWide) { - return left.longValue() / right.longValue(); - } else { - return left.intValue() / right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() / right.doubleValue(); - } else { - return left.floatValue() / right.floatValue(); - } - } - case REMAINDER: - if (isInteger) { - if (isWide) { - return left.longValue() % right.longValue(); - } else { - return left.intValue() % right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() % right.doubleValue(); - } else { - return left.floatValue() % right.floatValue(); - } - } - default: - return null; - } - } - } else if (node instanceof Cast) { - Cast cast = (Cast)node; - Object operandValue = evaluate(cast.astOperand()); - if (operandValue instanceof Number) { - Number number = (Number)operandValue; - String typeName = cast.astTypeReference().getTypeName(); - if (typeName.equals("float")) { - return number.floatValue(); - } else if (typeName.equals("double")) { - return number.doubleValue(); - } else if (typeName.equals("int")) { - return number.intValue(); - } else if (typeName.equals("long")) { - return number.longValue(); - } else if (typeName.equals("short")) { - return number.shortValue(); - } else if (typeName.equals("byte")) { - return number.byteValue(); - } - } - return operandValue; - } else if (mContext != null && (node instanceof VariableReference || - node instanceof Select)) { - ResolvedNode resolved = mContext.resolve(node); - if (resolved instanceof ResolvedField) { - ResolvedField field = (ResolvedField) resolved; - Object value = field.getValue(); - if (value != null) { - return value; - } - Node astNode = field.findAstNode(); - if (astNode instanceof VariableDeclaration) { - VariableDeclaration declaration = (VariableDeclaration) astNode; - VariableDefinition definition = declaration.astDefinition(); - if (definition != null && definition.astModifiers().isFinal()) { - StrictListAccessor<VariableDefinitionEntry, VariableDefinition> variables = - definition.astVariables(); - if (variables.size() == 1) { - VariableDefinitionEntry first = variables.first(); - if (first.astInitializer() != null) { - return evaluate(first.astInitializer()); - } - } - } - } - return null; - } else if (node instanceof VariableReference) { - Statement statement = getParentOfType(node, Statement.class, false); - if (statement != null) { - ListIterator<Node> iterator = statement.getParent().getChildren().listIterator(); - while (iterator.hasNext()) { - if (iterator.next() == statement) { - if (iterator.hasPrevious()) { // should always be true - iterator.previous(); - } - break; - } - } - - String targetName = ((VariableReference)node).astIdentifier().astValue(); - while (iterator.hasPrevious()) { - Node previous = iterator.previous(); - if (previous instanceof VariableDeclaration) { - VariableDeclaration declaration = (VariableDeclaration) previous; - VariableDefinition definition = declaration.astDefinition(); - for (VariableDefinitionEntry entry : definition - .astVariables()) { - if (entry.astInitializer() != null - && entry.astName().astValue().equals(targetName)) { - return evaluate(entry.astInitializer()); - } - } - } else if (previous instanceof ExpressionStatement) { - ExpressionStatement expressionStatement = (ExpressionStatement) previous; - Expression expression = expressionStatement.astExpression(); - if (expression instanceof BinaryExpression && - ((BinaryExpression) expression).astOperator() - == BinaryOperator.ASSIGN) { - BinaryExpression binaryExpression = (BinaryExpression) expression; - if (targetName.equals(binaryExpression.astLeft().toString())) { - return evaluate(binaryExpression.astRight()); - } - } - } - } - } - } - } else if (node instanceof ArrayCreation) { - ArrayCreation creation = (ArrayCreation) node; - ArrayInitializer initializer = creation.astInitializer(); - if (initializer != null) { - TypeReference typeReference = creation.astComponentTypeReference(); - StrictListAccessor<Expression, ArrayInitializer> expressions = initializer - .astExpressions(); - List<Object> values = Lists.newArrayListWithExpectedSize(expressions.size()); - Class<?> commonType = null; - for (Expression expression : expressions) { - Object value = evaluate(expression); - if (value != null) { - values.add(value); - if (commonType == null) { - commonType = value.getClass(); - } else { - while (!commonType.isAssignableFrom(value.getClass())) { - commonType = commonType.getSuperclass(); - } - } - } else if (!mAllowUnknown) { - // Inconclusive - return null; - } - } - if (!values.isEmpty()) { - Object o = Array.newInstance(commonType, values.size()); - return values.toArray((Object[]) o); - } else if (mContext != null) { - ResolvedNode type = mContext.resolve(typeReference); - System.out.println(type); - // TODO: return new array of this type - } - } else { - // something like "new byte[3]" but with no initializer. - String type = creation.astComponentTypeReference().toString(); - // TODO: Look up the size and only if small, use it. E.g. if it was byte[3] - // we could return a byte[3] array, but if it's say byte[1024*1024] we don't - // want to do that. - int size = 0; - if (TYPE_BYTE.equals(type)) { - return new byte[size]; - } - if (TYPE_BOOLEAN.equals(type)) { - return new boolean[size]; - } - if (TYPE_INT.equals(type)) { - return new int[size]; - } - if (TYPE_LONG.equals(type)) { - return new long[size]; - } - if (TYPE_CHAR.equals(type)) { - return new char[size]; - } - if (TYPE_FLOAT.equals(type)) { - return new float[size]; - } - if (TYPE_DOUBLE.equals(type)) { - return new double[size]; - } - if (TYPE_STRING.equals(type)) { - //noinspection SSBasedInspection - return new String[size]; - } - if (TYPE_SHORT.equals(type)) { - return new short[size]; - } - if (TYPE_OBJECT.equals(type)) { - //noinspection SSBasedInspection - return new Object[size]; - } - } - } - - // TODO: Check for MethodInvocation and perform some common operations - - // Math.* methods, String utility methods like notNullize, etc - - return null; - } - - /** - * Evaluates the given node and returns the constant value it resolves to, if any - * - * @param node the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - */ - @Nullable - public Object evaluate(@Nullable UElement node) { - if (node == null) { - return null; - } - - if (node instanceof ULiteralExpression) { - return ((ULiteralExpression) node).getValue(); - } else if (node instanceof UPrefixExpression) { - UastPrefixOperator operator = ((UPrefixExpression) node).getOperator(); - Object operand = evaluate(((UPrefixExpression) node).getOperand()); - if (operand == null) { - return null; - } - if (operator == UastPrefixOperator.LOGICAL_NOT) { - if (operand instanceof Boolean) { - return !(Boolean) operand; - } - } else if (operator == UastPrefixOperator.UNARY_PLUS) { - return operand; - } else if (operator == UastPrefixOperator.BITWISE_NOT) { - if (operand instanceof Integer) { - return ~(Integer) operand; - } else if (operand instanceof Long) { - return ~(Long) operand; - } else if (operand instanceof Short) { - return ~(Short) operand; - } else if (operand instanceof Character) { - return ~(Character) operand; - } else if (operand instanceof Byte) { - return ~(Byte) operand; - } - } else if (operator == UastPrefixOperator.UNARY_MINUS) { - if (operand instanceof Integer) { - return -(Integer) operand; - } else if (operand instanceof Long) { - return -(Long) operand; - } else if (operand instanceof Double) { - return -(Double) operand; - } else if (operand instanceof Float) { - return -(Float) operand; - } else if (operand instanceof Short) { - return -(Short) operand; - } else if (operand instanceof Character) { - return -(Character) operand; - } else if (operand instanceof Byte) { - return -(Byte) operand; - } - } - } else if (node instanceof UIfExpression - && ((UIfExpression) node).getExpressionType() != null) { - UIfExpression expression = (UIfExpression) node; - Object known = evaluate(expression.getCondition()); - if (known == Boolean.TRUE && expression.getThenExpression() != null) { - return evaluate(expression.getThenExpression()); - } else if (known == Boolean.FALSE && expression.getElseExpression() != null) { - return evaluate(expression.getElseExpression()); - } - } else if (node instanceof UParenthesizedExpression) { - UParenthesizedExpression parenthesizedExpression = (UParenthesizedExpression) node; - UExpression expression = parenthesizedExpression.getExpression(); - return evaluate(expression); - } else if (node instanceof UBinaryExpression) { - UastBinaryOperator operator = ((UBinaryExpression) node).getOperator(); - Object operandLeft = evaluate(((UBinaryExpression) node).getLeftOperand()); - Object operandRight = evaluate(((UBinaryExpression) node).getRightOperand()); - if (operandLeft == null || operandRight == null) { - if (mAllowUnknown) { - if (operandLeft == null) { - return operandRight; - } else { - return operandLeft; - } - } - return null; - } - if (operandLeft instanceof String && operandRight instanceof String) { - if (operator == UastBinaryOperator.PLUS) { - return operandLeft.toString() + operandRight.toString(); - } - return null; - } else if (operandLeft instanceof Boolean && operandRight instanceof Boolean) { - boolean left = (Boolean) operandLeft; - boolean right = (Boolean) operandRight; - if (operator == UastBinaryOperator.LOGICAL_OR) { - return left || right; - } else if (operator == UastBinaryOperator.LOGICAL_AND) { - return left && right; - } else if (operator == UastBinaryOperator.BITWISE_OR) { - return left | right; - } else if (operator == UastBinaryOperator.BITWISE_XOR) { - return left ^ right; - } else if (operator == UastBinaryOperator.BITWISE_AND) { - return left & right; - } else if (operator == UastBinaryOperator.IDENTITY_EQUALS - || operator == UastBinaryOperator.EQUALS) { - return left == right; - } else if (operator == UastBinaryOperator.IDENTITY_NOT_EQUALS - || operator == UastBinaryOperator.NOT_EQUALS) { - return left != right; - } - } else if (operandLeft instanceof Number && operandRight instanceof Number) { - Number left = (Number) operandLeft; - Number right = (Number) operandRight; - boolean isInteger = - !(left instanceof Float || left instanceof Double - || right instanceof Float || right instanceof Double); - boolean isWide = - isInteger ? (left instanceof Long || right instanceof Long) - : (left instanceof Double || right instanceof Double); - - if (operator == UastBinaryOperator.BITWISE_OR) { - if (isWide) { - return left.longValue() | right.longValue(); - } else { - return left.intValue() | right.intValue(); - } - } else if (operator == UastBinaryOperator.BITWISE_XOR) { - if (isWide) { - return left.longValue() ^ right.longValue(); - } else { - return left.intValue() ^ right.intValue(); - } - } else if (operator == UastBinaryOperator.BITWISE_AND) { - if (isWide) { - return left.longValue() & right.longValue(); - } else { - return left.intValue() & right.intValue(); - } - } else if (operator == UastBinaryOperator.EQUALS - || operator == UastBinaryOperator.IDENTITY_EQUALS) { - if (isInteger) { - return left.longValue() == right.longValue(); - } else { - return left.doubleValue() == right.doubleValue(); - } - } else if (operator == UastBinaryOperator.NOT_EQUALS - || operator == UastBinaryOperator.IDENTITY_NOT_EQUALS) { - if (isInteger) { - return left.longValue() != right.longValue(); - } else { - return left.doubleValue() != right.doubleValue(); - } - } else if (operator == UastBinaryOperator.GREATER) { - if (isInteger) { - return left.longValue() > right.longValue(); - } else { - return left.doubleValue() > right.doubleValue(); - } - } else if (operator == UastBinaryOperator.GREATER_OR_EQUALS) { - if (isInteger) { - return left.longValue() >= right.longValue(); - } else { - return left.doubleValue() >= right.doubleValue(); - } - } else if (operator == UastBinaryOperator.LESS) { - if (isInteger) { - return left.longValue() < right.longValue(); - } else { - return left.doubleValue() < right.doubleValue(); - } - } else if (operator == UastBinaryOperator.LESS_OR_EQUALS) { - if (isInteger) { - return left.longValue() <= right.longValue(); - } else { - return left.doubleValue() <= right.doubleValue(); - } - } else if (operator == UastBinaryOperator.SHIFT_LEFT) { - if (isWide) { - return left.longValue() << right.intValue(); - } else { - return left.intValue() << right.intValue(); - } - } else if (operator == UastBinaryOperator.SHIFT_RIGHT) { - if (isWide) { - return left.longValue() >> right.intValue(); - } else { - return left.intValue() >> right.intValue(); - } - } else if (operator == UastBinaryOperator.UNSIGNED_SHIFT_RIGHT) { - if (isWide) { - return left.longValue() >>> right.intValue(); - } else { - return left.intValue() >>> right.intValue(); - } - } else if (operator == UastBinaryOperator.PLUS) { - if (isInteger) { - if (isWide) { - return left.longValue() + right.longValue(); - } else { - return left.intValue() + right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() + right.doubleValue(); - } else { - return left.floatValue() + right.floatValue(); - } - } - } else if (operator == UastBinaryOperator.MINUS) { - if (isInteger) { - if (isWide) { - return left.longValue() - right.longValue(); - } else { - return left.intValue() - right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() - right.doubleValue(); - } else { - return left.floatValue() - right.floatValue(); - } - } - } else if (operator == UastBinaryOperator.MULTIPLY) { - if (isInteger) { - if (isWide) { - return left.longValue() * right.longValue(); - } else { - return left.intValue() * right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() * right.doubleValue(); - } else { - return left.floatValue() * right.floatValue(); - } - } - } else if (operator == UastBinaryOperator.DIV) { - if (isInteger) { - if (isWide) { - return left.longValue() / right.longValue(); - } else { - return left.intValue() / right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() / right.doubleValue(); - } else { - return left.floatValue() / right.floatValue(); - } - } - } else if (operator == UastBinaryOperator.MOD) { - if (isInteger) { - if (isWide) { - return left.longValue() % right.longValue(); - } else { - return left.intValue() % right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() % right.doubleValue(); - } else { - return left.floatValue() % right.floatValue(); - } - } - } else { - return null; - } - } - } else if (node instanceof UBinaryExpressionWithType && - ((UBinaryExpressionWithType) node).getOperationKind() == TYPE_CAST) { - UBinaryExpressionWithType cast = (UBinaryExpressionWithType) node; - Object operandValue = evaluate(cast.getOperand()); - if (operandValue instanceof Number) { - Number number = (Number) operandValue; - PsiType type = cast.getType(); - if (PsiType.FLOAT.equals(type)) { - return number.floatValue(); - } else if (PsiType.DOUBLE.equals(type)) { - return number.doubleValue(); - } else if (PsiType.INT.equals(type)) { - return number.intValue(); - } else if (PsiType.LONG.equals(type)) { - return number.longValue(); - } else if (PsiType.SHORT.equals(type)) { - return number.shortValue(); - } else if (PsiType.BYTE.equals(type)) { - return number.byteValue(); - } - } - return operandValue; - } else if (mContext != null && node instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) node).resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - Object value = UastLintUtils.findLastValue(variable, node, mContext, this); - - if (value != null) { - return value; - } - if (variable.getInitializer() != null) { - return evaluate(variable.getInitializer()); - } - return null; - } - } else if (UastExpressionUtils.isNewArrayWithDimensions((UExpression) node)) { - UCallExpression call = (UCallExpression) node; - PsiType arrayType = call.getExpressionType(); - if (arrayType instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) arrayType).getComponentType(); - // Single-dimension array - if (!(componentType instanceof PsiArrayType) - && call.getValueArgumentCount() == 1) { - Object lengthObj = evaluate(call.getValueArguments().get(0)); - if (lengthObj instanceof Number) { - int length = ((Number) lengthObj).intValue(); - if (length > 30) { - length = 30; - } - if (componentType == PsiType.BOOLEAN) { - return new boolean[length]; - } else if (isObjectType(componentType)) { - return new Object[length]; - } else if (componentType == PsiType.CHAR) { - return new char[length]; - } else if (componentType == PsiType.BYTE) { - return new byte[length]; - } else if (componentType == PsiType.DOUBLE) { - return new double[length]; - } else if (componentType == PsiType.FLOAT) { - return new float[length]; - } else if (componentType == PsiType.INT) { - return new int[length]; - } else if (componentType == PsiType.SHORT) { - return new short[length]; - } else if (componentType == PsiType.LONG) { - return new long[length]; - } else if (isStringType(componentType)) { - return new String[length]; - } - } - } - } - } else if (UastExpressionUtils.isNewArrayWithInitializer(node)) { - UCallExpression call = (UCallExpression) node; - PsiType arrayType = call.getExpressionType(); - if (arrayType instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) arrayType).getComponentType(); - // Single-dimension array - if (!(componentType instanceof PsiArrayType)) { - int length = call.getValueArgumentCount(); - List<Object> evaluatedArgs = new ArrayList<Object>(length); - for (UExpression arg : call.getValueArguments()) { - Object evaluatedArg = evaluate(arg); - if (!mAllowUnknown && evaluatedArg == null) { - // Inconclusive - return null; - } - evaluatedArgs.add(evaluatedArg); - } - - if (componentType == PsiType.BOOLEAN) { - boolean[] arr = new boolean[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Boolean) { - arr[i] = (Boolean) o; - } - } - return arr; - } else if (isObjectType(componentType)) { - Object[] arr = new Object[length]; - for (int i = 0; i < length; ++i) { - arr[i] = evaluatedArgs.get(i); - } - return arr; - } else if (componentType.equals(PsiType.CHAR)) { - char[] arr = new char[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Character) { - arr[i] = (Character) o; - } - } - return arr; - } else if (componentType.equals(PsiType.BYTE)) { - byte[] arr = new byte[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Byte) { - arr[i] = (Byte) o; - } - } - return arr; - } else if (componentType.equals(PsiType.DOUBLE)) { - double[] arr = new double[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Double) { - arr[i] = (Double) o; - } - } - return arr; - } else if (componentType.equals(PsiType.FLOAT)) { - float[] arr = new float[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Float) { - arr[i] = (Float) o; - } - } - return arr; - } else if (componentType.equals(PsiType.INT)) { - int[] arr = new int[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Integer) { - arr[i] = (Integer) o; - } - } - return arr; - } else if (componentType.equals(PsiType.SHORT)) { - short[] arr = new short[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Short) { - arr[i] = (Short) o; - } - } - return arr; - } else if (componentType.equals(PsiType.LONG)) { - long[] arr = new long[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof Long) { - arr[i] = (Long) o; - } - } - return arr; - } else if (isStringType(componentType)) { - String[] arr = new String[length]; - for (int i = 0; i < length; ++i) { - Object o = evaluatedArgs.get(i); - if (o instanceof String) { - arr[i] = (String) o; - } - } - return arr; - } - } - } - } - - if (node instanceof UExpression) { - Object evaluated = ((UExpression) node).evaluate(); - if (evaluated != null) { - return evaluated; - } - } - - // TODO: Check for MethodInvocation and perform some common operations - - // Math.* methods, String utility methods like notNullize, etc - - return null; - } - - private static boolean isStringType(PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; - } - - PsiClass resolvedClass = ((PsiClassType) type).resolve(); - return resolvedClass != null && TYPE_STRING.equals(resolvedClass.getQualifiedName()); - } - - private static boolean isObjectType(PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; - } - - PsiClass resolvedClass = ((PsiClassType) type).resolve(); - return resolvedClass != null && TYPE_OBJECT.equals(resolvedClass.getQualifiedName()); - } - - public static class LastAssignmentFinder extends AbstractUastVisitor { - private final PsiVariable mVariable; - private final UElement mEndAt; - - private final ConstantEvaluator mConstantEvaluator; - - private boolean mDone = false; - private int mCurrentLevel = 0; - private int mVariableLevel = -1; - private Object mCurrentValue; - private UElement mLastAssignment; - - public LastAssignmentFinder( - @NonNull PsiVariable variable, - @NonNull UElement endAt, - @NonNull JavaContext context, - @Nullable ConstantEvaluator constantEvaluator, - int variableLevel) { - mVariable = variable; - mEndAt = endAt; - UExpression initializer = context.getUastContext().getInitializerBody(variable); - mLastAssignment = initializer; - mConstantEvaluator = constantEvaluator; - if (initializer != null && constantEvaluator != null) { - mCurrentValue = constantEvaluator.evaluate(initializer); - } - this.mVariableLevel = variableLevel; - } - - @Nullable - public Object getCurrentValue() { - return mCurrentValue; - } - - @Nullable - public UElement getLastAssignment() { - return mLastAssignment; - } - - @Override - public boolean visitElement(UElement node) { - if (elementHasLevel(node)) { - mCurrentLevel++; - } - if (node.equals(mEndAt)) { - mDone = true; - } - return mDone || super.visitElement(node); - } - - @Override - public boolean visitVariable(UVariable node) { - if (mVariableLevel < 0 && node.getPsi().isEquivalentTo(mVariable)) { - mVariableLevel = mCurrentLevel; - } - - return super.visitVariable(node); - } - - @Override - public void afterVisitBinaryExpression(UBinaryExpression node) { - if (!mDone - && node.getOperator() instanceof UastBinaryOperator.AssignOperator - && mVariableLevel >= 0) { - UExpression leftOperand = node.getLeftOperand(); - UastBinaryOperator operator = node.getOperator(); - - if (!(operator instanceof UastBinaryOperator.AssignOperator) - || !(leftOperand instanceof UResolvable)) { - return; - } - - PsiElement resolved = ((UResolvable) leftOperand).resolve(); - if (!mVariable.equals(resolved)) { - return; - } - - // Last assignment is unknown if we see an assignment inside - // some conditional or loop statement. - if (mCurrentLevel > mVariableLevel + 1) { - mLastAssignment = null; - mCurrentValue = null; - return; - } - - UExpression rightOperand = node.getRightOperand(); - ConstantEvaluator constantEvaluator = mConstantEvaluator; - - mCurrentValue = (constantEvaluator != null) - ? constantEvaluator.evaluate(rightOperand) - : null; - mLastAssignment = rightOperand; - } - - super.afterVisitBinaryExpression(node); - } - - @Override - public void afterVisitElement(UElement node) { - if (elementHasLevel(node)) { - mCurrentLevel--; - } - super.afterVisitElement(node); - } - - private static boolean elementHasLevel(UElement node) { - return !(node instanceof UBlockExpression - || node instanceof UDeclarationsExpression); - } - } - - /** - * Evaluates the given node and returns the constant value it resolves to, if any - * - * @param node the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - */ - @Nullable - public Object evaluate(@Nullable PsiElement node) { - if (node == null) { - return null; - } - if (node instanceof PsiLiteral) { - return ((PsiLiteral)node).getValue(); - } else if (node instanceof PsiPrefixExpression) { - IElementType operator = ((PsiPrefixExpression) node).getOperationTokenType(); - Object operand = evaluate(((PsiPrefixExpression) node).getOperand()); - if (operand == null) { - return null; - } - if (operator == JavaTokenType.EXCL) { - if (operand instanceof Boolean) { - return !(Boolean) operand; - } - } else if (operator == JavaTokenType.PLUS) { - return operand; - } else if (operator == JavaTokenType.TILDE) { - if (operand instanceof Integer) { - return ~(Integer) operand; - } else if (operand instanceof Long) { - return ~(Long) operand; - } else if (operand instanceof Short) { - return ~(Short) operand; - } else if (operand instanceof Character) { - return ~(Character) operand; - } else if (operand instanceof Byte) { - return ~(Byte) operand; - } - } else if (operator == JavaTokenType.MINUS) { - if (operand instanceof Integer) { - return -(Integer) operand; - } else if (operand instanceof Long) { - return -(Long) operand; - } else if (operand instanceof Double) { - return -(Double) operand; - } else if (operand instanceof Float) { - return -(Float) operand; - } else if (operand instanceof Short) { - return -(Short) operand; - } else if (operand instanceof Character) { - return -(Character) operand; - } else if (operand instanceof Byte) { - return -(Byte) operand; - } - } - } else if (node instanceof PsiConditionalExpression) { - PsiConditionalExpression expression = (PsiConditionalExpression) node; - Object known = evaluate(expression.getCondition()); - if (known == Boolean.TRUE && expression.getThenExpression() != null) { - return evaluate(expression.getThenExpression()); - } else if (known == Boolean.FALSE && expression.getElseExpression() != null) { - return evaluate(expression.getElseExpression()); - } - } else if (node instanceof PsiParenthesizedExpression) { - PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) node; - PsiExpression expression = parenthesizedExpression.getExpression(); - if (expression != null) { - return evaluate(expression); - } - } else if (node instanceof PsiBinaryExpression) { - IElementType operator = ((PsiBinaryExpression) node).getOperationTokenType(); - Object operandLeft = evaluate(((PsiBinaryExpression) node).getLOperand()); - Object operandRight = evaluate(((PsiBinaryExpression) node).getROperand()); - if (operandLeft == null || operandRight == null) { - if (mAllowUnknown) { - if (operandLeft == null) { - return operandRight; - } else { - return operandLeft; - } - } - return null; - } - if (operandLeft instanceof String && operandRight instanceof String) { - if (operator == JavaTokenType.PLUS) { - return operandLeft.toString() + operandRight.toString(); - } - return null; - } else if (operandLeft instanceof Boolean && operandRight instanceof Boolean) { - boolean left = (Boolean) operandLeft; - boolean right = (Boolean) operandRight; - if (operator == JavaTokenType.OROR) { - return left || right; - } else if (operator == JavaTokenType.ANDAND) { - return left && right; - } else if (operator == JavaTokenType.OR) { - return left | right; - } else if (operator == JavaTokenType.XOR) { - return left ^ right; - } else if (operator == JavaTokenType.AND) { - return left & right; - } else if (operator == JavaTokenType.EQEQ) { - return left == right; - } else if (operator == JavaTokenType.NE) { - return left != right; - } - } else if (operandLeft instanceof Number && operandRight instanceof Number) { - Number left = (Number) operandLeft; - Number right = (Number) operandRight; - boolean isInteger = - !(left instanceof Float || left instanceof Double - || right instanceof Float || right instanceof Double); - boolean isWide = - isInteger ? (left instanceof Long || right instanceof Long) - : (left instanceof Double || right instanceof Double); - - if (operator == JavaTokenType.OR) { - if (isWide) { - return left.longValue() | right.longValue(); - } else { - return left.intValue() | right.intValue(); - } - } else if (operator == JavaTokenType.XOR) { - if (isWide) { - return left.longValue() ^ right.longValue(); - } else { - return left.intValue() ^ right.intValue(); - } - } else if (operator == JavaTokenType.AND) { - if (isWide) { - return left.longValue() & right.longValue(); - } else { - return left.intValue() & right.intValue(); - } - } else if (operator == JavaTokenType.EQEQ) { - if (isInteger) { - return left.longValue() == right.longValue(); - } else { - return left.doubleValue() == right.doubleValue(); - } - } else if (operator == JavaTokenType.NE) { - if (isInteger) { - return left.longValue() != right.longValue(); - } else { - return left.doubleValue() != right.doubleValue(); - } - } else if (operator == JavaTokenType.GT) { - if (isInteger) { - return left.longValue() > right.longValue(); - } else { - return left.doubleValue() > right.doubleValue(); - } - } else if (operator == JavaTokenType.GE) { - if (isInteger) { - return left.longValue() >= right.longValue(); - } else { - return left.doubleValue() >= right.doubleValue(); - } - } else if (operator == JavaTokenType.LT) { - if (isInteger) { - return left.longValue() < right.longValue(); - } else { - return left.doubleValue() < right.doubleValue(); - } - } else if (operator == JavaTokenType.LE) { - if (isInteger) { - return left.longValue() <= right.longValue(); - } else { - return left.doubleValue() <= right.doubleValue(); - } - } else if (operator == JavaTokenType.LTLT) { - if (isWide) { - return left.longValue() << right.intValue(); - } else { - return left.intValue() << right.intValue(); - } - } else if (operator == JavaTokenType.GTGT) { - if (isWide) { - return left.longValue() >> right.intValue(); - } else { - return left.intValue() >> right.intValue(); - } - } else if (operator == JavaTokenType.GTGTGT) { - if (isWide) { - return left.longValue() >>> right.intValue(); - } else { - return left.intValue() >>> right.intValue(); - } - } else if (operator == JavaTokenType.PLUS) { - if (isInteger) { - if (isWide) { - return left.longValue() + right.longValue(); - } else { - return left.intValue() + right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() + right.doubleValue(); - } else { - return left.floatValue() + right.floatValue(); - } - } - } else if (operator == JavaTokenType.MINUS) { - if (isInteger) { - if (isWide) { - return left.longValue() - right.longValue(); - } else { - return left.intValue() - right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() - right.doubleValue(); - } else { - return left.floatValue() - right.floatValue(); - } - } - } else if (operator == JavaTokenType.ASTERISK) { - if (isInteger) { - if (isWide) { - return left.longValue() * right.longValue(); - } else { - return left.intValue() * right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() * right.doubleValue(); - } else { - return left.floatValue() * right.floatValue(); - } - } - } else if (operator == JavaTokenType.DIV) { - if (isInteger) { - if (isWide) { - return left.longValue() / right.longValue(); - } else { - return left.intValue() / right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() / right.doubleValue(); - } else { - return left.floatValue() / right.floatValue(); - } - } - } else if (operator == JavaTokenType.PERC) { - if (isInteger) { - if (isWide) { - return left.longValue() % right.longValue(); - } else { - return left.intValue() % right.intValue(); - } - } else { - if (isWide) { - return left.doubleValue() % right.doubleValue(); - } else { - return left.floatValue() % right.floatValue(); - } - } - } else { - return null; - } - } - } else if (node instanceof PsiTypeCastExpression) { - PsiTypeCastExpression cast = (PsiTypeCastExpression) node; - Object operandValue = evaluate(cast.getOperand()); - if (operandValue instanceof Number) { - Number number = (Number) operandValue; - PsiTypeElement typeElement = cast.getCastType(); - if (typeElement != null) { - PsiType type = typeElement.getType(); - if (PsiType.FLOAT.equals(type)) { - return number.floatValue(); - } else if (PsiType.DOUBLE.equals(type)) { - return number.doubleValue(); - } else if (PsiType.INT.equals(type)) { - return number.intValue(); - } else if (PsiType.LONG.equals(type)) { - return number.longValue(); - } else if (PsiType.SHORT.equals(type)) { - return number.shortValue(); - } else if (PsiType.BYTE.equals(type)) { - return number.byteValue(); - } - } - } - return operandValue; - } else if (node instanceof PsiReference) { - PsiElement resolved = ((PsiReference) node).resolve(); - if (resolved instanceof PsiField) { - PsiField field = (PsiField) resolved; - Object value = field.computeConstantValue(); - if (value != null) { - return value; - } - if (field.getInitializer() != null) { - return evaluate(field.getInitializer()); - } - return null; - } else if (resolved instanceof PsiLocalVariable) { - PsiLocalVariable variable = (PsiLocalVariable) resolved; - PsiStatement statement = PsiTreeUtil.getParentOfType(node, PsiStatement.class, - false); - if (statement != null) { - PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, - PsiStatement.class); - String targetName = variable.getName(); - if (targetName == null) { - return null; - } - while (prev != null) { - if (prev instanceof PsiDeclarationStatement) { - for (PsiElement element : ((PsiDeclarationStatement) prev) - .getDeclaredElements()) { - if (variable.equals(element)) { - return evaluate(variable.getInitializer()); - } - } - } else if (prev instanceof PsiExpressionStatement) { - PsiExpression expression = ((PsiExpressionStatement) prev) - .getExpression(); - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assign - = (PsiAssignmentExpression) expression; - PsiExpression lhs = assign.getLExpression(); - if (lhs instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) lhs; - if (targetName.equals(reference.getReferenceName()) && - reference.getQualifier() == null) { - return evaluate(assign.getRExpression()); - } - } - } - } - prev = PsiTreeUtil.getPrevSiblingOfType(prev, - PsiStatement.class); - } - } - } - } else if (node instanceof PsiNewExpression) { - PsiNewExpression creation = (PsiNewExpression) node; - PsiArrayInitializerExpression initializer = creation.getArrayInitializer(); - PsiType type = creation.getType(); - if (type instanceof PsiArrayType) { - if (initializer != null) { - PsiExpression[] initializers = initializer.getInitializers(); - Class<?> commonType = null; - List<Object> values = Lists.newArrayListWithExpectedSize(initializers.length); - int count = 0; - for (PsiExpression expression : initializers) { - Object value = evaluate(expression); - if (value != null) { - values.add(value); - if (commonType == null) { - commonType = value.getClass(); - } else { - while (!commonType.isAssignableFrom(value.getClass())) { - commonType = commonType.getSuperclass(); - } - } - } else if (!mAllowUnknown) { - // Inconclusive - return null; - } - count++; - if (count == 20) { // avoid large initializers - break; - } - } - type = type.getDeepComponentType(); - if (type == PsiType.INT) { - if (!values.isEmpty()) { - int[] array = new int[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Integer) { - array[i] = (Integer) o; - } - } - return array; - } - return new int[0]; - } else if (type == PsiType.BOOLEAN) { - if (!values.isEmpty()) { - boolean[] array = new boolean[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Boolean) { - array[i] = (Boolean) o; - } - } - return array; - } - return new boolean[0]; - } else if (type == PsiType.DOUBLE) { - if (!values.isEmpty()) { - double[] array = new double[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Double) { - array[i] = (Double) o; - } - } - return array; - } - return new double[0]; - } else if (type == PsiType.LONG) { - if (!values.isEmpty()) { - long[] array = new long[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Long) { - array[i] = (Long) o; - } - } - return array; - } - return new long[0]; - } else if (type == PsiType.FLOAT) { - if (!values.isEmpty()) { - float[] array = new float[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Float) { - array[i] = (Float) o; - } - } - return array; - } - return new float[0]; - } else if (type == PsiType.CHAR) { - if (!values.isEmpty()) { - char[] array = new char[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Character) { - array[i] = (Character) o; - } - } - return array; - } - return new char[0]; - } else if (type == PsiType.BYTE) { - if (!values.isEmpty()) { - byte[] array = new byte[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Byte) { - array[i] = (Byte) o; - } - } - return array; - } - return new byte[0]; - } else if (type == PsiType.SHORT) { - if (!values.isEmpty()) { - short[] array = new short[values.size()]; - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - if (o instanceof Short) { - array[i] = (Short) o; - } - } - return array; - } - return new short[0]; - } else { - if (!values.isEmpty()) { - Object o = Array.newInstance(commonType, values.size()); - return values.toArray((Object[]) o); - } - return null; - } - } else { - // something like "new byte[3]" but with no initializer. - // Look up the size and only if small, use it. E.g. if it was byte[3] - // we return a byte[3] array, but if it's say byte[1024*1024] we don't - // want to do that. - PsiExpression[] arrayDimensions = creation.getArrayDimensions(); - int size = 0; - if (arrayDimensions.length == 1) { - Object fixedSize = evaluate(arrayDimensions[0]); - if (fixedSize instanceof Number) { - size = ((Number)fixedSize).intValue(); - if (size > 30) { - size = 30; - } - } - } - type = type.getDeepComponentType(); - if (type instanceof PsiPrimitiveType) { - if (PsiType.BYTE.equals(type)) { - return new byte[size]; - } - if (PsiType.BOOLEAN.equals(type)) { - return new boolean[size]; - } - if (PsiType.INT.equals(type)) { - return new int[size]; - } - if (PsiType.LONG.equals(type)) { - return new long[size]; - } - if (PsiType.CHAR.equals(type)) { - return new char[size]; - } - if (PsiType.FLOAT.equals(type)) { - return new float[size]; - } - if (PsiType.DOUBLE.equals(type)) { - return new double[size]; - } - if (PsiType.SHORT.equals(type)) { - return new short[size]; - } - } else if (type instanceof PsiClassType) { - String className = type.getCanonicalText(); - if (TYPE_STRING.equals(className)) { - //noinspection SSBasedInspection - return new String[size]; - } - if (TYPE_OBJECT.equals(className)) { - //noinspection SSBasedInspection - return new Object[size]; - } - } - } - } - } - - // TODO: Check for MethodInvocation and perform some common operations - - // Math.* methods, String utility methods like notNullize, etc - - return null; - } - - /** - * Returns true if the node is pointing to a an array literal - */ - public static boolean isArrayLiteral(@Nullable UElement node, @NonNull JavaContext context) { - if (node instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) node).resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - UExpression lastAssignment = - UastLintUtils.findLastAssignment(variable, node, context); - - if (lastAssignment != null) { - return isArrayLiteral(lastAssignment, context); - } - } - } else if (UastExpressionUtils.isNewArrayWithDimensions(node)) { - return true; - } else if (UastExpressionUtils.isNewArrayWithInitializer(node)) { - return true; - } else if (node instanceof UParenthesizedExpression) { - UParenthesizedExpression parenthesizedExpression = (UParenthesizedExpression) node; - UExpression expression = parenthesizedExpression.getExpression(); - return isArrayLiteral(expression, context); - } else if (UastExpressionUtils.isTypeCast(node)) { - UBinaryExpressionWithType castExpression = (UBinaryExpressionWithType) node; - assert castExpression != null; - UExpression operand = castExpression.getOperand(); - return isArrayLiteral(operand, context); - } - - return false; - } - - /** - * Returns true if the node is pointing to a an array literal - */ - public static boolean isArrayLiteral(@Nullable PsiElement node) { - if (node instanceof PsiReference) { - PsiElement resolved = ((PsiReference) node).resolve(); - if (resolved instanceof PsiField) { - PsiField field = (PsiField) resolved; - if (field.getInitializer() != null) { - return isArrayLiteral(field.getInitializer()); - } - } else if (resolved instanceof PsiLocalVariable) { - PsiLocalVariable variable = (PsiLocalVariable) resolved; - PsiStatement statement = PsiTreeUtil.getParentOfType(node, PsiStatement.class, - false); - if (statement != null) { - PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, - PsiStatement.class); - String targetName = variable.getName(); - if (targetName == null) { - return false; - } - while (prev != null) { - if (prev instanceof PsiDeclarationStatement) { - for (PsiElement element : ((PsiDeclarationStatement) prev) - .getDeclaredElements()) { - if (variable.equals(element)) { - return isArrayLiteral(variable.getInitializer()); - } - } - } else if (prev instanceof PsiExpressionStatement) { - PsiExpression expression = ((PsiExpressionStatement) prev) - .getExpression(); - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assign - = (PsiAssignmentExpression) expression; - PsiExpression lhs = assign.getLExpression(); - if (lhs instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) lhs; - if (targetName.equals(reference.getReferenceName()) && - reference.getQualifier() == null) { - return isArrayLiteral(assign.getRExpression()); - } - } - } - } - prev = PsiTreeUtil.getPrevSiblingOfType(prev, - PsiStatement.class); - } - } - } - } else if (node instanceof PsiNewExpression) { - PsiNewExpression creation = (PsiNewExpression) node; - if (creation.getArrayInitializer() != null) { - return true; - } - PsiType type = creation.getType(); - if (type instanceof PsiArrayType) { - return true; - } - } else if (node instanceof PsiParenthesizedExpression) { - PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) node; - PsiExpression expression = parenthesizedExpression.getExpression(); - if (expression != null) { - return isArrayLiteral(expression); - } - } else if (node instanceof PsiTypeCastExpression) { - PsiTypeCastExpression castExpression = (PsiTypeCastExpression) node; - PsiExpression operand = castExpression.getOperand(); - if (operand != null) { - return isArrayLiteral(operand); - } - } - - return false; - } - - /** - * Evaluates the given node and returns the constant value it resolves to, if any. Convenience - * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns - * the result. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - * @deprecated Use {@link #evaluate(JavaContext, PsiElement)} instead - */ - @Deprecated - @Nullable - public static Object evaluate(@NonNull JavaContext context, @NonNull Node node) { - return new ConstantEvaluator(context).evaluate(node); - } - - /** - * Evaluates the given node and returns the constant string it resolves to, if any. Convenience - * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns - * the result if the result is a string. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the constant value for - * @param allowUnknown whether we should construct the string even if some parts of it are - * unknown - * @return the corresponding string, if any - * @deprecated Use {@link #evaluateString(JavaContext, PsiElement, boolean)} instead - */ - @Deprecated - @Nullable - public static String evaluateString(@NonNull JavaContext context, @NonNull Node node, - boolean allowUnknown) { - ConstantEvaluator evaluator = new ConstantEvaluator(context); - if (allowUnknown) { - evaluator.allowUnknowns(); - } - Object value = evaluator.evaluate(node); - return value instanceof String ? (String) value : null; - } - - /** - * Evaluates the given node and returns the constant value it resolves to, if any. Convenience - * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns - * the result. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - */ - @Nullable - public static Object evaluate(@Nullable JavaContext context, @NonNull PsiElement node) { - return new ConstantEvaluator(context).evaluate(node); - } - - /** - * Evaluates the given node and returns the constant value it resolves to, if any. Convenience - * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns - * the result. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - */ - @Nullable - public static Object evaluate(@Nullable JavaContext context, @NonNull UElement node) { - return new ConstantEvaluator(context).evaluate(node); - } - - /** - * Evaluates the given node and returns the constant string it resolves to, if any. Convenience - * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns - * the result if the result is a string. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the constant value for - * @param allowUnknown whether we should construct the string even if some parts of it are - * unknown - * @return the corresponding string, if any - */ - @Nullable - public static String evaluateString(@Nullable JavaContext context, @NonNull PsiElement node, - boolean allowUnknown) { - ConstantEvaluator evaluator = new ConstantEvaluator(context); - if (allowUnknown) { - evaluator.allowUnknowns(); - } - Object value = evaluator.evaluate(node); - return value instanceof String ? (String) value : null; - } - - /** - * Evaluates the given node and returns the constant string it resolves to, if any. Convenience - * wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns - * the result if the result is a string. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the constant value for - * @param allowUnknown whether we should construct the string even if some parts of it are - * unknown - * @return the corresponding string, if any - */ - @Nullable - public static String evaluateString(@Nullable JavaContext context, @NonNull UElement node, - boolean allowUnknown) { - ConstantEvaluator evaluator = new ConstantEvaluator(context); - if (allowUnknown) { - evaluator.allowUnknowns(); - } - Object value = evaluator.evaluate(node); - return value instanceof String ? (String) value : null; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Context.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Context.java deleted file mode 100644 index 9d87fae..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Context.java +++ /dev/null
@@ -1,451 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import static com.android.SdkConstants.DOT_GRADLE; -import static com.android.SdkConstants.DOT_JAVA; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.SUPPRESS_ALL; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.Configuration; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.client.api.SdkInfo; -import com.google.common.annotations.Beta; - -import java.io.File; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; - -/** - * Context passed to the detectors during an analysis run. It provides - * information about the file being analyzed, it allows shared properties (so - * the detectors can share results), etc. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class Context { - /** - * The file being checked. Note that this may not always be to a concrete - * file. For example, in the {@link Detector#beforeCheckProject(Context)} - * method, the context file is the directory of the project. - */ - public final File file; - - /** The driver running through the checks */ - protected final LintDriver mDriver; - - /** The project containing the file being checked */ - @NonNull - private final Project mProject; - - /** - * The "main" project. For normal projects, this is the same as {@link #mProject}, - * but for library projects, it's the root project that includes (possibly indirectly) - * the various library projects and their library projects. - * <p> - * Note that this is a property on the {@link Context}, not the - * {@link Project}, since a library project can be included from multiple - * different top level projects, so there isn't <b>one</b> main project, - * just one per main project being analyzed with its library projects. - */ - private final Project mMainProject; - - /** The current configuration controlling which checks are enabled etc */ - private final Configuration mConfiguration; - - /** The contents of the file */ - private String mContents; - - /** Map of properties to share results between detectors */ - private Map<String, Object> mProperties; - - /** Whether this file contains any suppress markers (null means not yet determined) */ - private Boolean mContainsCommentSuppress; - - /** - * Construct a new {@link Context} - * - * @param driver the driver running through the checks - * @param project the project containing the file being checked - * @param main the main project if this project is a library project, or - * null if this is not a library project. The main project is - * the root project of all library projects, not necessarily the - * directly including project. - * @param file the file being checked - */ - public Context( - @NonNull LintDriver driver, - @NonNull Project project, - @Nullable Project main, - @NonNull File file) { - this.file = file; - - mDriver = driver; - mProject = project; - mMainProject = main; - mConfiguration = project.getConfiguration(driver); - } - - /** - * Returns the scope for the lint job - * - * @return the scope, never null - */ - @NonNull - public EnumSet<Scope> getScope() { - return mDriver.getScope(); - } - - /** - * Returns the configuration for this project. - * - * @return the configuration, never null - */ - @NonNull - public Configuration getConfiguration() { - return mConfiguration; - } - - /** - * Returns the project containing the file being checked - * - * @return the project, never null - */ - @NonNull - public Project getProject() { - return mProject; - } - - /** - * Returns the main project if this project is a library project, or self - * if this is not a library project. The main project is the root project - * of all library projects, not necessarily the directly including project. - * - * @return the main project, never null - */ - @NonNull - public Project getMainProject() { - return mMainProject != null ? mMainProject : mProject; - } - - /** - * Returns the lint client requesting the lint check - * - * @return the client, never null - */ - @NonNull - public LintClient getClient() { - return mDriver.getClient(); - } - - /** - * Returns the driver running through the lint checks - * - * @return the driver - */ - @NonNull - public LintDriver getDriver() { - return mDriver; - } - - /** - * Returns the contents of the file. This may not be the contents of the - * file on disk, since it delegates to the {@link LintClient}, which in turn - * may decide to return the current edited contents of the file open in an - * editor. - * - * @return the contents of the given file, or null if an error occurs. - */ - @Nullable - public String getContents() { - if (mContents == null) { - mContents = mDriver.getClient().readFile(file); - } - - return mContents; - } - - /** - * Returns the value of the given named property, or null. - * - * @param name the name of the property - * @return the corresponding value, or null - */ - @SuppressWarnings("UnusedDeclaration") // Used in ADT - @Nullable - public Object getProperty(String name) { - if (mProperties == null) { - return null; - } - - return mProperties.get(name); - } - - /** - * Sets the value of the given named property. - * - * @param name the name of the property - * @param value the corresponding value - */ - @SuppressWarnings("UnusedDeclaration") // Used in ADT - public void setProperty(@NonNull String name, @Nullable Object value) { - if (value == null) { - if (mProperties != null) { - mProperties.remove(name); - } - } else { - if (mProperties == null) { - mProperties = new HashMap<String, Object>(); - } - mProperties.put(name, value); - } - } - - /** - * Gets the SDK info for the current project. - * - * @return the SDK info for the current project, never null - */ - @NonNull - public SdkInfo getSdkInfo() { - return mProject.getSdkInfo(); - } - - // ---- Convenience wrappers ---- (makes the detector code a bit leaner) - - /** - * Returns false if the given issue has been disabled. Convenience wrapper - * around {@link Configuration#getSeverity(Issue)}. - * - * @param issue the issue to check - * @return false if the issue has been disabled - */ - public boolean isEnabled(@NonNull Issue issue) { - return mConfiguration.isEnabled(issue); - } - - /** - * Reports an issue. Convenience wrapper around {@link LintClient#report} - * - * @param issue the issue to report - * @param location the location of the issue - * @param message the message for this warning - */ - public void report( - @NonNull Issue issue, - @NonNull Location location, - @NonNull String message) { - //noinspection ConstantConditions - if (location == null) { - // Misbehaving third-party lint detectors - assert false : issue; - return; - } - - if (location == Location.NONE) { - // Detector reported error for issue in a non-applicable location etc - return; - } - - Configuration configuration = mConfiguration; - - // If this error was computed for a context where the context corresponds to - // a project instead of a file, the actual error may be in a different project (e.g. - // a library project), so adjust the configuration as necessary. - Project project = mDriver.findProjectFor(location.getFile()); - if (project != null) { - configuration = project.getConfiguration(mDriver); - } - - // If an error occurs in a library project, but you've disabled that check in the - // main project, disable it in the library project too. (In some cases you don't - // control the lint.xml of a library project, and besides, if you're not interested in - // a check for your main project you probably don't care about it in the library either.) - if (configuration != mConfiguration - && mConfiguration.getSeverity(issue) == Severity.IGNORE) { - return; - } - - Severity severity = configuration.getSeverity(issue); - if (severity == Severity.IGNORE) { - return; - } - - mDriver.getClient().report(this, issue, severity, location, message, TextFormat.RAW); - } - - /** - * Report an error. - * Like {@link #report(Issue, Location, String)} but with - * a now-unused data parameter at the end - * - * @deprecated Use {@link #report(Issue, Location, String)} instead; - * this method is here for custom rule compatibility - */ - @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules - @Deprecated - public void report( - @NonNull Issue issue, - @NonNull Location location, - @NonNull String message, - @SuppressWarnings("UnusedParameters") @Nullable Object data) { - report(issue, location, message); - } - - /** - * Send an exception to the log. Convenience wrapper around {@link LintClient#log}. - * - * @param exception the exception, possibly null - * @param format the error message using {@link String#format} syntax, possibly null - * @param args any arguments for the format string - */ - public void log( - @Nullable Throwable exception, - @Nullable String format, - @Nullable Object... args) { - mDriver.getClient().log(exception, format, args); - } - - /** - * Returns the current phase number. The first pass is numbered 1. Only one pass - * will be performed, unless a {@link Detector} calls {@link #requestRepeat}. - * - * @return the current phase, usually 1 - */ - public int getPhase() { - return mDriver.getPhase(); - } - - /** - * Requests another pass through the data for the given detector. This is - * typically done when a detector needs to do more expensive computation, - * but it only wants to do this once it <b>knows</b> that an error is - * present, or once it knows more specifically what to check for. - * - * @param detector the detector that should be included in the next pass. - * Note that the lint runner may refuse to run more than a couple - * of runs. - * @param scope the scope to be revisited. This must be a subset of the - * current scope ({@link #getScope()}, and it is just a performance hint; - * in particular, the detector should be prepared to be called on other - * scopes as well (since they may have been requested by other detectors). - * You can pall null to indicate "all". - */ - public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) { - mDriver.requestRepeat(detector, scope); - } - - /** Returns the comment marker used in Studio to suppress statements for language, if any */ - @Nullable - protected String getSuppressCommentPrefix() { - // Java and XML files are handled in sub classes (XmlContext, JavaContext) - - String path = file.getPath(); - if (path.endsWith(DOT_JAVA) || path.endsWith(".kt") || path.endsWith(DOT_GRADLE)) { - return JavaContext.SUPPRESS_COMMENT_PREFIX; - } else if (path.endsWith(DOT_XML)) { - return XmlContext.SUPPRESS_COMMENT_PREFIX; - } else if (path.endsWith(".cfg") || path.endsWith(".pro")) { - return "#suppress "; - } - - return null; - } - - /** Returns whether this file contains any suppress comment markers */ - public boolean containsCommentSuppress() { - if (mContainsCommentSuppress == null) { - mContainsCommentSuppress = false; - String prefix = getSuppressCommentPrefix(); - if (prefix != null) { - String contents = getContents(); - if (contents != null) { - mContainsCommentSuppress = contents.contains(prefix); - } - } - } - - return mContainsCommentSuppress; - } - - /** - * Returns true if the given issue is suppressed at the given character offset - * in the file's contents - */ - public boolean isSuppressedWithComment(int startOffset, @NonNull Issue issue) { - String prefix = getSuppressCommentPrefix(); - if (prefix == null) { - return false; - } - - if (startOffset <= 0) { - return false; - } - - // Check whether there is a comment marker - String contents = getContents(); - assert contents != null; // otherwise we wouldn't be here - if (startOffset >= contents.length()) { - return false; - } - - // Scan backwards to the previous line and see if it contains the marker - int lineStart = contents.lastIndexOf('\n', startOffset) + 1; - if (lineStart <= 1) { - return false; - } - int index = findPrefixOnPreviousLine(contents, lineStart, prefix); - if (index != -1 &&index+prefix.length() < lineStart) { - String line = contents.substring(index + prefix.length(), lineStart); - return line.contains(issue.getId()) - || line.contains(SUPPRESS_ALL) && line.trim().startsWith(SUPPRESS_ALL); - } - - return false; - } - - private static int findPrefixOnPreviousLine(String contents, int lineStart, String prefix) { - // Search backwards on the previous line until you find the prefix start (also look - // back on previous lines if the previous line(s) contain just whitespace - char first = prefix.charAt(0); - int offset = lineStart - 2; // 0: first char on this line, -1: \n on previous line, -2 last - boolean seenNonWhitespace = false; - for (; offset >= 0; offset--) { - char c = contents.charAt(offset); - if (seenNonWhitespace && c == '\n') { - return -1; - } - - if (!seenNonWhitespace && !Character.isWhitespace(c)) { - seenNonWhitespace = true; - } - - if (c == first && contents.regionMatches(false, offset, prefix, 0, - prefix.length())) { - return offset; - } - } - - return -1; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/DefaultPosition.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/DefaultPosition.java deleted file mode 100644 index fd370cc..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/DefaultPosition.java +++ /dev/null
@@ -1,68 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.google.common.annotations.Beta; - -/** - * A simple offset-based position * - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class DefaultPosition extends Position { - /** The line number (0-based where the first line is line 0) */ - private final int mLine; - - /** - * The column number (where the first character on the line is 0), or -1 if - * unknown - */ - private final int mColumn; - - /** The character offset */ - private final int mOffset; - - /** - * Creates a new {@link DefaultPosition} - * - * @param line the 0-based line number, or -1 if unknown - * @param column the 0-based column number, or -1 if unknown - * @param offset the offset, or -1 if unknown - */ - public DefaultPosition(int line, int column, int offset) { - mLine = line; - mColumn = column; - mOffset = offset; - } - - @Override - public int getLine() { - return mLine; - } - - @Override - public int getOffset() { - return mOffset; - } - - @Override - public int getColumn() { - return mColumn; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Detector.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Detector.java deleted file mode 100644 index fc60ac8..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Detector.java +++ /dev/null
@@ -1,1515 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.JavaParser.ResolvedClass; -import com.android.tools.klint.client.api.JavaParser.ResolvedMethod; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.client.api.UElementVisitor; -import com.google.common.annotations.Beta; -import com.intellij.psi.JavaElementVisitor; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiJavaCodeReferenceElement; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiNewExpression; - -import com.intellij.psi.PsiTypeParameter; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.visitor.UastVisitor; -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; -import org.jetbrains.org.objectweb.asm.tree.MethodNode; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -import lombok.ast.AstVisitor; -import lombok.ast.ClassDeclaration; -import lombok.ast.ConstructorInvocation; -import lombok.ast.MethodInvocation; -import lombok.ast.Node; - -/** - * A detector is able to find a particular problem (or a set of related problems). - * Each problem type is uniquely identified as an {@link Issue}. - * <p> - * Detectors will be called in a predefined order: - * <ol> - * <li> Manifest file - * <li> Resource files, in alphabetical order by resource type - * (therefore, "layout" is checked before "values", "values-de" is checked before - * "values-en" but after "values", and so on. - * <li> Java sources - * <li> Java classes - * <li> Gradle files - * <li> Generic files - * <li> Proguard files - * <li> Property files - * </ol> - * If a detector needs information when processing a file type that comes from a type of - * file later in the order above, they can request a second phase; see - * {@link LintDriver#requestRepeat}. - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class Detector { - /** - * Specialized interface for detectors that scan Java source file parse trees - * @deprecated Use {@link JavaPsiScanner} instead - */ - @Deprecated @SuppressWarnings("unused") // Still here for third-party rules - public interface JavaScanner { - /** - * Create a parse tree visitor to process the parse tree. All - * {@link JavaScanner} detectors must provide a visitor, unless they - * either return true from {@link #appliesToResourceRefs()} or return - * non null from {@link #getApplicableMethodNames()}. - * <p> - * If you return specific AST node types from - * {@link #getApplicableNodeTypes()}, then the visitor will <b>only</b> - * be called for the specific requested node types. This is more - * efficient, since it allows many detectors that apply to only a small - * part of the AST (such as method call nodes) to share iteration of the - * majority of the parse tree. - * <p> - * If you return null from {@link #getApplicableNodeTypes()}, then your - * visitor will be called from the top and all node types visited. - * <p> - * Note that a new visitor is created for each separate compilation - * unit, so you can store per file state in the visitor. - * - * @param context the {@link Context} for the file being analyzed - * @return a visitor, or null. - */ - @Nullable - AstVisitor createJavaVisitor(@NonNull JavaContext context); - - /** - * Return the types of AST nodes that the visitor returned from - * {@link #createJavaVisitor(JavaContext)} should visit. See the - * documentation for {@link #createJavaVisitor(JavaContext)} for details - * on how the shared visitor is used. - * <p> - * If you return null from this method, then the visitor will process - * the full tree instead. - * <p> - * Note that for the shared visitor, the return codes from the visit - * methods are ignored: returning true will <b>not</b> prune iteration - * of the subtree, since there may be other node types interested in the - * children. If you need to ensure that your visitor only processes a - * part of the tree, use a full visitor instead. See the - * OverdrawDetector implementation for an example of this. - * - * @return the list of applicable node types (AST node classes), or null - */ - @Nullable - List<Class<? extends Node>> getApplicableNodeTypes(); - - /** - * Return the list of method names this detector is interested in, or - * null. If this method returns non-null, then any AST nodes that match - * a method call in the list will be passed to the - * {@link #visitMethod(JavaContext, AstVisitor, MethodInvocation)} - * method for processing. The visitor created by - * {@link #createJavaVisitor(JavaContext)} is also passed to that - * method, although it can be null. - * <p> - * This makes it easy to write detectors that focus on some fixed calls. - * For example, the StringFormatDetector uses this mechanism to look for - * "format" calls, and when found it looks around (using the AST's - * {@link Node#getParent()} method) to see if it's called on - * a String class instance, and if so do its normal processing. Note - * that since it doesn't need to do any other AST processing, that - * detector does not actually supply a visitor. - * - * @return a set of applicable method names, or null. - */ - @Nullable - List<String> getApplicableMethodNames(); - - /** - * Method invoked for any method calls found that matches any names - * returned by {@link #getApplicableMethodNames()}. This also passes - * back the visitor that was created by - * {@link #createJavaVisitor(JavaContext)}, but a visitor is not - * required. It is intended for detectors that need to do additional AST - * processing, but also want the convenience of not having to look for - * method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from - * {@link #createJavaVisitor(JavaContext)}, or null - * @param node the {@link MethodInvocation} node for the invoked method - */ - void visitMethod( - @NonNull JavaContext context, - @Nullable AstVisitor visitor, - @NonNull MethodInvocation node); - - /** - * Return the list of constructor types this detector is interested in, or - * null. If this method returns non-null, then any AST nodes that match - * a constructor call in the list will be passed to the - * {@link #visitConstructor(JavaContext, AstVisitor, ConstructorInvocation, ResolvedMethod)} - * method for processing. The visitor created by - * {@link #createJavaVisitor(JavaContext)} is also passed to that - * method, although it can be null. - * <p> - * This makes it easy to write detectors that focus on some fixed constructors. - * - * @return a set of applicable fully qualified types, or null. - */ - @Nullable - List<String> getApplicableConstructorTypes(); - - /** - * Method invoked for any constructor calls found that matches any names - * returned by {@link #getApplicableConstructorTypes()}. This also passes - * back the visitor that was created by - * {@link #createJavaVisitor(JavaContext)}, but a visitor is not - * required. It is intended for detectors that need to do additional AST - * processing, but also want the convenience of not having to look for - * method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from - * {@link #createJavaVisitor(JavaContext)}, or null - * @param node the {@link ConstructorInvocation} node for the invoked method - * @param constructor the resolved constructor method with type information - */ - void visitConstructor( - @NonNull JavaContext context, - @Nullable AstVisitor visitor, - @NonNull ConstructorInvocation node, - @NonNull ResolvedMethod constructor); - - /** - * Returns whether this detector cares about Android resource references - * (such as {@code R.layout.main} or {@code R.string.app_name}). If it - * does, then the visitor will look for these patterns, and if found, it - * will invoke {@link #visitResourceReference} passing the resource type - * and resource name. It also passes the visitor, if any, that was - * created by {@link #createJavaVisitor(JavaContext)}, such that a - * detector can do more than just look for resources. - * - * @return true if this detector wants to be notified of R resource - * identifiers found in the code. - */ - boolean appliesToResourceRefs(); - - /** - * Called for any resource references (such as {@code R.layout.main} - * found in Java code, provided this detector returned {@code true} from - * {@link #appliesToResourceRefs()}. - * - * @param context the lint scanning context - * @param visitor the visitor created from - * {@link #createJavaVisitor(JavaContext)}, or null - * @param node the variable reference for the resource - * @param type the resource type, such as "layout" or "string" - * @param name the resource name, such as "main" from - * {@code R.layout.main} - * @param isFramework whether the resource is a framework resource - * (android.R) or a local project resource (R) - */ - void visitResourceReference( - @NonNull JavaContext context, - @Nullable AstVisitor visitor, - @NonNull Node node, - @NonNull String type, - @NonNull String name, - boolean isFramework); - - /** - * Returns a list of fully qualified names for super classes that this - * detector cares about. If not null, this detector will *only* be called - * if the current class is a subclass of one of the specified superclasses. - * - * @return a list of fully qualified names - */ - @Nullable - List<String> applicableSuperClasses(); - - /** - * Called for each class that extends one of the super classes specified with - * {@link #applicableSuperClasses()} - * - * @param context the lint scanning context - * @param declaration the class declaration node, or null for anonymous classes - * @param node the class declaration node or the anonymous class construction node - * @param resolvedClass the resolved class - */ - // TODO: Change signature to pass in the NormalTypeBody instead of the plain Node? - void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration, - @NonNull Node node, @NonNull ResolvedClass resolvedClass); - } - - /** - Interface to be implemented by lint detectors that want to analyze - Java source files. - <p> - The Lint Java API sits on top of IntelliJ IDEA's "PSI" API, an API - which exposes the underlying abstract syntax tree as well as providing - core services like resolving symbols. - <p> - This new API replaces the older Lombok AST API that was used for Java - source checks. Migrating a check from the Lombok APIs to the new PSI - based APIs is relatively straightforward. - <p> - First, replace "implements JavaScanner" with "implements - JavaPsiScanner" in your detector signature, and then locate all the - JavaScanner methods your detector was overriding and replace them with - the new corresponding signatures. - <p> - For example, replace - <pre> - {@code List<Class<? extends Node>> getApplicableNodeTypes();} - </pre> - with - <pre> - {@code List<Class<? extends PsiElement>> getApplicablePsiTypes();} - </pre> - and replace - <pre> - void visitMethod( - @NonNull JavaContext context, - @Nullable AstVisitor visitor, - @NonNull MethodInvocation node); - </pre> - with - <pre> - void visitMethod( - @NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, - @NonNull PsiMethodCallExpression call, - @NonNull PsiMethod method); - </pre> - and so on. - <p> - Finally, replace the various Lombok iteration code with PSI based - code. Both Lombok and PSI used class names that closely resemble the - Java language specification, so guessing the corresponding names is - straightforward; here are some examples: - <pre> - ClassDeclaration ⇒ PsiClass - MethodDeclaration ⇒ PsiMethod - MethodInvocation ⇒ PsiMethodCallExpression - ConstructorInvocation ⇒ PsiNewExpression - If ⇒ PsiIfStatement - For ⇒ PsiForStatement - Continue ⇒ PsiContinueStatement - StringLiteral ⇒ PsiLiteral - IntegralLiteral ⇒ PsiLiteral - ... etc - </pre> - Lombok AST had no support for symbol and type resolution, so lint - added its own separate API to support (the "ResolvedNode" - hierarchy). This is no longer needed since PSI supports it directly - (for example, on a PsiMethodCallExpression you just call - "resolveMethod" to get the PsiMethod the method calls, and on a - PsiExpression you just call getType() to get the corresponding - <p> - The old ResolvedNode interface was written for lint so it made certain - kinds of common checks very easy. To help make porting lint rules from - the old API easier, and to make writing future lint checks easier - too), there is a new helper class, "JavaEvaluator" (which you can - obtain from JavaContext). This lets you for example quickly check - whether a given method is a member of a subclass of a given class, or - whether a method has a certain set of parameters, etc. It also makes - it easy to check whether a given method is private, abstract or - static, and so on. (And most of its parameters are nullable which - makes it simpler to use; you don't have to null check resolve results - before calling into it.) - <p> - Some further porting tips: - <ul> - <li> Make sure you don't call toString() on nodes to get their - contents. In Lombok, toString returned the underlying source - text. In PSI, call getText() instead, since toString() is meant for - debugging and includes node types etc. - - <li> ResolvedClass#getName() used to return *qualified* name. In PSI, - PsiClass#getName() returns just the simple name, so call - #getQualifiedName() instead if that's what your code needs! Node - also that PsiClassType#getClassName() returns the simple name; if - you want the fully qualified name, call PsiType#getCanonicalText(). - - <li> Lombok didn't distinguish between a local variable declaration, a - parameter and a field declaration. These are all different in PSI, - so when writing visitors, make sure you replace a single - visitVariableDeclaration with visitField, visitLocalVariable and - visitParameter methods as applicable. - - <li> Note that when lint runs in the IDE, there may be extra PSI nodes in - the hierarchy representing whitespace as well as parentheses. Watch - out for this when calling getParent, getPrevSibling or - getNextSibling - don't just go one level up and check instanceof - {@code <something>}; instead, use LintUtils.skipParentheses (or the - corresponding methods to skip whitespace left and right.) Note that - when you write lint unit tests, the infrastructure will run your - tests twice, one with a normal AST and once where it has inserted - whitespace and parentheses everywhere, and it asserts that you come - up with the same analysis results. (This caught 16 failing tests - across 7 different detectors.) - - <li> Annotation handling is a bit different. In ResolvedAnnotations I had - (for convenience) inlined things like annotations on the class; you - now have to resolve the annotation name reference to the - corresponding annotation class and look there. - </ul> - - Some additional conversion examples: replace - <pre> - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, - @NonNull MethodInvocation node) { - ResolvedNode resolved = context.resolve(node); - if (resolved instanceof ResolvedMethod) { - ResolvedMethod method = (ResolvedMethod) resolved; - if (method.getContainingClass().matches("android.os.Parcel")) { - ... - </pre> - with - <pre> - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, - @NonNull PsiCall node) { - if (method != null && method.getContainingClass() != null && - "android.os.Parcel".equals(method.getContainingClass().getQualifiedName())) { - .... - </pre> - - Similarly: - <pre> - if (method.getArgumentCount() != 2 - || !method.getArgumentType(0).matchesName(TYPE_OBJECT) - || !method.getArgumentType(1).matchesName(TYPE_STRING)) { - return; - } - </pre> - can now be written as - <pre> - JavaEvaluator resolver = context.getEvaluator(); - if (!resolver.methodMatches(method, WEB_VIEW, true, TYPE_OBJECT, TYPE_STRING)) { - return; - } - </pre> - Finally, note that many deprecated methods in lint itself point to the replacement - methods, see for example {@link JavaContext#findSurroundingMethod(Node)}. - */ - public interface JavaPsiScanner { - /** - * Create a parse tree visitor to process the parse tree. All - * {@link JavaScanner} detectors must provide a visitor, unless they - * either return true from {@link #appliesToResourceRefs()} or return - * non null from {@link #getApplicableMethodNames()}. - * <p> - * If you return specific AST node types from - * {@link #getApplicablePsiTypes()}, then the visitor will <b>only</b> - * be called for the specific requested node types. This is more - * efficient, since it allows many detectors that apply to only a small - * part of the AST (such as method call nodes) to share iteration of the - * majority of the parse tree. - * <p> - * If you return null from {@link #getApplicablePsiTypes()}, then your - * visitor will be called from the top and all node types visited. - * <p> - * Note that a new visitor is created for each separate compilation - * unit, so you can store per file state in the visitor. - * <p> - * <b> - * NOTE: Your visitor should <b>NOT</b> extend JavaRecursiveElementVisitor. - * Your visitor should only visit the current node type; the infrastructure - * will do the recursion. (Lint's unit test infrastructure will check and - * enforce this restriction.) - * </b> - * - * @param context the {@link Context} for the file being analyzed - * @return a visitor, or null. - */ - @Nullable - JavaElementVisitor createPsiVisitor(@NonNull JavaContext context); - - /** - * Return the types of AST nodes that the visitor returned from - * {@link #createJavaVisitor(JavaContext)} should visit. See the - * documentation for {@link #createJavaVisitor(JavaContext)} for details - * on how the shared visitor is used. - * <p> - * If you return null from this method, then the visitor will process - * the full tree instead. - * <p> - * Note that for the shared visitor, the return codes from the visit - * methods are ignored: returning true will <b>not</b> prune iteration - * of the subtree, since there may be other node types interested in the - * children. If you need to ensure that your visitor only processes a - * part of the tree, use a full visitor instead. See the - * OverdrawDetector implementation for an example of this. - * - * @return the list of applicable node types (AST node classes), or null - */ - @Nullable - List<Class<? extends PsiElement>> getApplicablePsiTypes(); - - /** - * Return the list of method names this detector is interested in, or - * null. If this method returns non-null, then any AST nodes that match - * a method call in the list will be passed to the - * {@link #visitMethod(JavaContext, JavaElementVisitor, PsiMethodCallExpression, PsiMethod)} - * method for processing. The visitor created by - * {@link #createPsiVisitor(JavaContext)} is also passed to that - * method, although it can be null. - * <p> - * This makes it easy to write detectors that focus on some fixed calls. - * For example, the StringFormatDetector uses this mechanism to look for - * "format" calls, and when found it looks around (using the AST's - * {@link PsiElement#getParent()} method) to see if it's called on - * a String class instance, and if so do its normal processing. Note - * that since it doesn't need to do any other AST processing, that - * detector does not actually supply a visitor. - * - * @return a set of applicable method names, or null. - */ - @Nullable - List<String> getApplicableMethodNames(); - - /** - * Method invoked for any method calls found that matches any names - * returned by {@link #getApplicableMethodNames()}. This also passes - * back the visitor that was created by - * {@link #createJavaVisitor(JavaContext)}, but a visitor is not - * required. It is intended for detectors that need to do additional AST - * processing, but also want the convenience of not having to look for - * method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from - * {@link #createPsiVisitor(JavaContext)}, or null - * @param call the {@link PsiMethodCallExpression} node for the invoked method - * @param method the {@link PsiMethod} being called - */ - void visitMethod( - @NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, - @NonNull PsiMethodCallExpression call, - @NonNull PsiMethod method); - - /** - * Return the list of constructor types this detector is interested in, or - * null. If this method returns non-null, then any AST nodes that match - * a constructor call in the list will be passed to the - * {@link #visitConstructor(JavaContext, JavaElementVisitor, PsiNewExpression, PsiMethod)} - * method for processing. The visitor created by - * {@link #createJavaVisitor(JavaContext)} is also passed to that - * method, although it can be null. - * <p> - * This makes it easy to write detectors that focus on some fixed constructors. - * - * @return a set of applicable fully qualified types, or null. - */ - @Nullable - List<String> getApplicableConstructorTypes(); - - /** - * Method invoked for any constructor calls found that matches any names - * returned by {@link #getApplicableConstructorTypes()}. This also passes - * back the visitor that was created by - * {@link #createPsiVisitor(JavaContext)}, but a visitor is not - * required. It is intended for detectors that need to do additional AST - * processing, but also want the convenience of not having to look for - * method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from - * {@link #createPsiVisitor(JavaContext)}, or null - * @param node the {@link PsiNewExpression} node for the invoked method - * @param constructor the called constructor method - */ - void visitConstructor( - @NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, - @NonNull PsiNewExpression node, - @NonNull PsiMethod constructor); - - /** - * Return the list of reference names types this detector is interested in, or null. If this - * method returns non-null, then any AST elements that match a reference in the list will be - * passed to the {@link #visitReference(JavaContext, JavaElementVisitor, - * PsiJavaCodeReferenceElement, PsiElement)} method for processing. The visitor created by - * {@link #createJavaVisitor(JavaContext)} is also passed to that method, although it can be - * null. <p> This makes it easy to write detectors that focus on some fixed references. - * - * @return a set of applicable reference names, or null. - */ - @Nullable - List<String> getApplicableReferenceNames(); - - /** - * Method invoked for any references found that matches any names returned by {@link - * #getApplicableReferenceNames()}. This also passes back the visitor that was created by - * {@link #createPsiVisitor(JavaContext)}, but a visitor is not required. It is intended for - * detectors that need to do additional AST processing, but also want the convenience of not - * having to look for method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from {@link #createPsiVisitor(JavaContext)}, or - * null - * @param reference the {@link PsiJavaCodeReferenceElement} element - * @param referenced the referenced element - */ - void visitReference( - @NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, - @NonNull PsiJavaCodeReferenceElement reference, - @NonNull PsiElement referenced); - - /** - * Returns whether this detector cares about Android resource references - * (such as {@code R.layout.main} or {@code R.string.app_name}). If it - * does, then the visitor will look for these patterns, and if found, it - * will invoke {@link #visitResourceReference} passing the resource type - * and resource name. It also passes the visitor, if any, that was - * created by {@link #createJavaVisitor(JavaContext)}, such that a - * detector can do more than just look for resources. - * - * @return true if this detector wants to be notified of R resource - * identifiers found in the code. - */ - boolean appliesToResourceRefs(); - - /** - * Called for any resource references (such as {@code R.layout.main} - * found in Java code, provided this detector returned {@code true} from - * {@link #appliesToResourceRefs()}. - * - * @param context the lint scanning context - * @param visitor the visitor created from - * {@link #createPsiVisitor(JavaContext)}, or null - * @param node the variable reference for the resource - * @param type the resource type, such as "layout" or "string" - * @param name the resource name, such as "main" from - * {@code R.layout.main} - * @param isFramework whether the resource is a framework resource - * (android.R) or a local project resource (R) - */ - void visitResourceReference( - @NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, - @NonNull PsiElement node, - @NonNull ResourceType type, - @NonNull String name, - boolean isFramework); - - /** - * Returns a list of fully qualified names for super classes that this - * detector cares about. If not null, this detector will <b>only</b> be called - * if the current class is a subclass of one of the specified superclasses. - * - * @return a list of fully qualified names - */ - @Nullable - List<String> applicableSuperClasses(); - - /** - * Called for each class that extends one of the super classes specified with - * {@link #applicableSuperClasses()}. - * <p> - * Note: This method will not be called for {@link PsiTypeParameter} classes. These - * aren't really classes in the sense most lint detectors think of them, so these - * are excluded to avoid having lint checks that don't defensively code for these - * accidentally report errors on type parameters. If you really need to check these, - * use {@link #getApplicablePsiTypes} with {@code PsiTypeParameter.class} instead. - * - * @param context the lint scanning context - * @param declaration the class declaration node, or null for anonymous classes - */ - void checkClass(@NonNull JavaContext context, @NonNull PsiClass declaration); - } - - public interface UastScanner { - /** - * Create a parse tree visitor to process the parse tree. All - * {@link JavaScanner} detectors must provide a visitor, unless they - * either return true from {@link #appliesToResourceRefs()} or return - * non null from {@link #getApplicableMethodNames()}. - * <p> - * If you return specific AST node types from - * {@link #getApplicablePsiTypes()}, then the visitor will <b>only</b> - * be called for the specific requested node types. This is more - * efficient, since it allows many detectors that apply to only a small - * part of the AST (such as method call nodes) to share iteration of the - * majority of the parse tree. - * <p> - * If you return null from {@link #getApplicablePsiTypes()}, then your - * visitor will be called from the top and all node types visited. - * <p> - * Note that a new visitor is created for each separate compilation - * unit, so you can store per file state in the visitor. - * <p> - * <b> - * NOTE: Your visitor should <b>NOT</b> extend JavaRecursiveElementVisitor. - * Your visitor should only visit the current node type; the infrastructure - * will do the recursion. (Lint's unit test infrastructure will check and - * enforce this restriction.) - * </b> - * - * @param context the {@link Context} for the file being analyzed - * @return a visitor, or null. - */ - @Nullable - UastVisitor createUastVisitor(@NonNull JavaContext context); - - /** - * Return the types of AST nodes that the visitor returned from - * {@link #createJavaVisitor(JavaContext)} should visit. See the - * documentation for {@link #createJavaVisitor(JavaContext)} for details - * on how the shared visitor is used. - * <p> - * If you return null from this method, then the visitor will process - * the full tree instead. - * <p> - * Note that for the shared visitor, the return codes from the visit - * methods are ignored: returning true will <b>not</b> prune iteration - * of the subtree, since there may be other node types interested in the - * children. If you need to ensure that your visitor only processes a - * part of the tree, use a full visitor instead. See the - * OverdrawDetector implementation for an example of this. - * - * @return the list of applicable node types (AST node classes), or null - */ - @Nullable - List<Class<? extends UElement>> getApplicableUastTypes(); - - @Nullable - List<Class<? extends PsiElement>> getApplicablePsiTypes(); - - /** - * Return the list of method names this detector is interested in, or - * null. If this method returns non-null, then any AST nodes that match - * a method call in the list will be passed to the - * {@link #visitMethod(JavaContext, JavaElementVisitor, PsiMethodCallExpression, PsiMethod)} - * method for processing. The visitor created by - * {@link #createPsiVisitor(JavaContext)} is also passed to that - * method, although it can be null. - * <p> - * This makes it easy to write detectors that focus on some fixed calls. - * For example, the StringFormatDetector uses this mechanism to look for - * "format" calls, and when found it looks around (using the AST's - * {@link PsiElement#getParent()} method) to see if it's called on - * a String class instance, and if so do its normal processing. Note - * that since it doesn't need to do any other AST processing, that - * detector does not actually supply a visitor. - * - * @return a set of applicable method names, or null. - */ - @Nullable - List<String> getApplicableMethodNames(); - - /** - * Method invoked for any method calls found that matches any names - * returned by {@link #getApplicableMethodNames()}. This also passes - * back the visitor that was created by - * {@link #createJavaVisitor(JavaContext)}, but a visitor is not - * required. It is intended for detectors that need to do additional AST - * processing, but also want the convenience of not having to look for - * method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from - * {@link #createPsiVisitor(JavaContext)}, or null - * @param node the {@link PsiMethodCallExpression} node for the invoked method - * @param method the {@link PsiMethod} being called - */ - void visitMethod( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UCallExpression node, - @NonNull UMethod method); - - /** - * Return the list of constructor types this detector is interested in, or - * null. If this method returns non-null, then any AST nodes that match - * a constructor call in the list will be passed to the - * {@link #visitConstructor(JavaContext, JavaElementVisitor, PsiNewExpression, PsiMethod)} - * method for processing. The visitor created by - * {@link #createJavaVisitor(JavaContext)} is also passed to that - * method, although it can be null. - * <p> - * This makes it easy to write detectors that focus on some fixed constructors. - * - * @return a set of applicable fully qualified types, or null. - */ - @Nullable - List<String> getApplicableConstructorTypes(); - - /** - * Method invoked for any constructor calls found that matches any names - * returned by {@link #getApplicableConstructorTypes()}. This also passes - * back the visitor that was created by - * {@link #createPsiVisitor(JavaContext)}, but a visitor is not - * required. It is intended for detectors that need to do additional AST - * processing, but also want the convenience of not having to look for - * method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from - * {@link #createPsiVisitor(JavaContext)}, or null - * @param node the {@link PsiNewExpression} node for the invoked method - * @param constructor the called constructor method - */ - void visitConstructor( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UCallExpression node, - @NonNull UMethod constructor); - - /** - * Return the list of reference names types this detector is interested in, or null. If this - * method returns non-null, then any AST elements that match a reference in the list will be - * passed to the {@link #visitReference(JavaContext, JavaElementVisitor, - * PsiJavaCodeReferenceElement, PsiElement)} method for processing. The visitor created by - * {@link #createJavaVisitor(JavaContext)} is also passed to that method, although it can be - * null. <p> This makes it easy to write detectors that focus on some fixed references. - * - * @return a set of applicable reference names, or null. - */ - @Nullable - List<String> getApplicableReferenceNames(); - - /** - * Method invoked for any references found that matches any names returned by {@link - * #getApplicableReferenceNames()}. This also passes back the visitor that was created by - * {@link #createPsiVisitor(JavaContext)}, but a visitor is not required. It is intended for - * detectors that need to do additional AST processing, but also want the convenience of not - * having to look for method names on their own. - * - * @param context the context of the lint request - * @param visitor the visitor created from {@link #createPsiVisitor(JavaContext)}, or - * null - * @param reference the {@link PsiJavaCodeReferenceElement} element - * @param referenced the referenced element - */ - void visitReference( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UReferenceExpression reference, - @NonNull PsiElement referenced); - - /** - * Returns whether this detector cares about Android resource references - * (such as {@code R.layout.main} or {@code R.string.app_name}). If it - * does, then the visitor will look for these patterns, and if found, it - * will invoke {@link #visitResourceReference} passing the resource type - * and resource name. It also passes the visitor, if any, that was - * created by {@link #createJavaVisitor(JavaContext)}, such that a - * detector can do more than just look for resources. - * - * @return true if this detector wants to be notified of R resource - * identifiers found in the code. - */ - boolean appliesToResourceRefs(); - - /** - * Called for any resource references (such as {@code R.layout.main} - * found in Java code, provided this detector returned {@code true} from - * {@link #appliesToResourceRefs()}. - * - * @param context the lint scanning context - * @param visitor the visitor created from - * {@link #createPsiVisitor(JavaContext)}, or null - * @param node the variable reference for the resource - * @param type the resource type, such as "layout" or "string" - * @param name the resource name, such as "main" from - * {@code R.layout.main} - * @param isFramework whether the resource is a framework resource - * (android.R) or a local project resource (R) - */ - void visitResourceReference( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UElement node, - @NonNull ResourceType type, - @NonNull String name, - boolean isFramework); - - /** - * Returns a list of fully qualified names for super classes that this - * detector cares about. If not null, this detector will <b>only</b> be called - * if the current class is a subclass of one of the specified superclasses. - * - * @return a list of fully qualified names - */ - @Nullable - List<String> applicableSuperClasses(); - - /** - * Called for each class that extends one of the super classes specified with - * {@link #applicableSuperClasses()}. - * <p> - * Note: This method will not be called for {@link PsiTypeParameter} classes. These - * aren't really classes in the sense most lint detectors think of them, so these - * are excluded to avoid having lint checks that don't defensively code for these - * accidentally report errors on type parameters. If you really need to check these, - * use {@link #getApplicablePsiTypes} with {@code PsiTypeParameter.class} instead. - * - * @param context the lint scanning context - * @param declaration the class declaration node, or null for anonymous classes - */ - void checkClass(@NonNull JavaContext context, @NonNull UClass declaration); - } - - /** Specialized interface for detectors that scan Java class files */ - public interface ClassScanner { - /** - * Checks the given class' bytecode for issues. - * - * @param context the context of the lint check, pointing to for example - * the file - * @param classNode the root class node - */ - void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode); - - /** - * Returns the list of node types (corresponding to the constants in the - * {@link AbstractInsnNode} class) that this scanner applies to. The - * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)} - * method will be called for each match. - * - * @return an array containing all the node types this detector should be - * called for, or null if none. - */ - @Nullable - int[] getApplicableAsmNodeTypes(); - - /** - * Process a given instruction node, and register lint issues if - * applicable. - * - * @param context the context of the lint check, pointing to for example - * the file - * @param classNode the root class node - * @param method the method node containing the call - * @param instruction the actual instruction - */ - void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode, - @NonNull MethodNode method, @NonNull AbstractInsnNode instruction); - - /** - * Return the list of method call names (in VM format, e.g. {@code "<init>"} for - * constructors, etc) for method calls this detector is interested in, - * or null. T his will be used to dispatch calls to - * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)} - * for only the method calls in owners that the detector is interested - * in. - * <p> - * <b>NOTE</b>: If you return non null from this method, then <b>only</b> - * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)} - * will be called if a suitable method is found; - * {@link #checkClass(ClassContext, ClassNode)} will not be called under - * any circumstances. - * <p> - * This makes it easy to write detectors that focus on some fixed calls, - * and allows lint to make a single pass over the bytecode over a class, - * and efficiently dispatch method calls to any detectors that are - * interested in it. Without this, each new lint check interested in a - * single method, would be doing a complete pass through all the - * bytecode instructions of the class via the - * {@link #checkClass(ClassContext, ClassNode)} method, which would make - * each newly added lint check make lint slower. Now a single dispatch - * map is used instead, and for each encountered call in the single - * dispatch, it looks up in the map which if any detectors are - * interested in the given call name, and dispatches to each one in - * turn. - * - * @return a list of applicable method names, or null. - */ - @Nullable - List<String> getApplicableCallNames(); - - /** - * Just like {@link Detector#getApplicableCallNames()}, but for the owner - * field instead. The - * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)} - * method will be called for all {@link MethodInsnNode} instances where the - * owner field matches any of the members returned in this node. - * <p> - * Note that if your detector provides both a name and an owner, the - * method will be called for any nodes matching either the name <b>or</b> - * the owner, not only where they match <b>both</b>. Note also that it will - * be called twice - once for the name match, and (at least once) for the owner - * match. - * - * @return a list of applicable owner names, or null. - */ - @Nullable - List<String> getApplicableCallOwners(); - - /** - * Process a given method call node, and register lint issues if - * applicable. This is similar to the - * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)} - * method, but has the additional advantage that it is only called for known - * method names or method owners, according to - * {@link #getApplicableCallNames()} and {@link #getApplicableCallOwners()}. - * - * @param context the context of the lint check, pointing to for example - * the file - * @param classNode the root class node - * @param method the method node containing the call - * @param call the actual method call node - */ - void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode, - @NonNull MethodNode method, @NonNull MethodInsnNode call); - } - - /** - * Specialized interface for detectors that scan binary resource files - * (typically bitmaps but also files in res/raw) - */ - public interface BinaryResourceScanner { - /** - * Called for each resource folder - * - * @param context the context for the resource file - */ - void checkBinaryResource(@NonNull ResourceContext context); - - /** - * Returns whether this detector applies to the given folder type. This - * allows the detectors to be pruned from iteration, so for example when we - * are analyzing a string value file we don't need to look up detectors - * related to layout. - * - * @param folderType the folder type to be visited - * @return true if this detector can apply to resources in folders of the - * given type - */ - boolean appliesTo(@NonNull ResourceFolderType folderType); - } - - /** Specialized interface for detectors that scan resource folders (the folder directory - * itself, not the individual files within it */ - public interface ResourceFolderScanner { - /** - * Called for each resource folder - * - * @param context the context for the resource folder - * @param folderName the resource folder name - */ - void checkFolder(@NonNull ResourceContext context, @NonNull String folderName); - - /** - * Returns whether this detector applies to the given folder type. This - * allows the detectors to be pruned from iteration, so for example when we - * are analyzing a string value file we don't need to look up detectors - * related to layout. - * - * @param folderType the folder type to be visited - * @return true if this detector can apply to resources in folders of the - * given type - */ - boolean appliesTo(@NonNull ResourceFolderType folderType); - } - - /** Specialized interface for detectors that scan XML files */ - public interface XmlScanner { - /** - * Visit the given document. The detector is responsible for its own iteration - * through the document. - * @param context information about the document being analyzed - * @param document the document to examine - */ - void visitDocument(@NonNull XmlContext context, @NonNull Document document); - - /** - * Visit the given element. - * @param context information about the document being analyzed - * @param element the element to examine - */ - void visitElement(@NonNull XmlContext context, @NonNull Element element); - - /** - * Visit the given element after its children have been analyzed. - * @param context information about the document being analyzed - * @param element the element to examine - */ - void visitElementAfter(@NonNull XmlContext context, @NonNull Element element); - - /** - * Visit the given attribute. - * @param context information about the document being analyzed - * @param attribute the attribute node to examine - */ - void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute); - - /** - * Returns the list of elements that this detector wants to analyze. If non - * null, this detector will be called (specifically, the - * {@link #visitElement} method) for each matching element in the document. - * <p> - * If this method returns null, and {@link #getApplicableAttributes()} also returns - * null, then the {@link #visitDocument} method will be called instead. - * - * @return a collection of elements, or null, or the special - * {@link XmlScanner#ALL} marker to indicate that every single - * element should be analyzed. - */ - @Nullable - Collection<String> getApplicableElements(); - - /** - * Returns the list of attributes that this detector wants to analyze. If non - * null, this detector will be called (specifically, the - * {@link #visitAttribute} method) for each matching attribute in the document. - * <p> - * If this method returns null, and {@link #getApplicableElements()} also returns - * null, then the {@link #visitDocument} method will be called instead. - * - * @return a collection of attributes, or null, or the special - * {@link XmlScanner#ALL} marker to indicate that every single - * attribute should be analyzed. - */ - @Nullable - Collection<String> getApplicableAttributes(); - - /** - * Special marker collection returned by {@link #getApplicableElements()} or - * {@link #getApplicableAttributes()} to indicate that the check should be - * invoked on all elements or all attributes - */ - @NonNull - List<String> ALL = new ArrayList<String>(0); // NOT Collections.EMPTY! - // We want to distinguish this from just an *empty* list returned by the caller! - } - - /** Specialized interface for detectors that scan Gradle files */ - public interface GradleScanner { - void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData); - } - - /** Specialized interface for detectors that scan other files */ - public interface OtherFileScanner { - /** - * Returns the set of files this scanner wants to consider. If this includes - * {@link Scope#OTHER} then all source files will be checked. Note that the - * set of files will not just include files of the indicated type, but all files - * within the relevant source folder. For example, returning {@link Scope#JAVA_FILE} - * will not just return {@code .java} files, but also other resource files such as - * {@code .html} and other files found within the Java source folders. - * <p> - * Lint will call the {@link #run(Context)}} method when the file should be checked. - * - * @return set of scopes that define the types of source files the - * detector wants to consider - */ - @NonNull - EnumSet<Scope> getApplicableFiles(); - } - - /** - * Runs the detector. This method will not be called for certain specialized - * detectors, such as {@link XmlScanner} and {@link JavaScanner}, where - * there are specialized analysis methods instead such as - * {@link XmlScanner#visitElement(XmlContext, Element)}. - * - * @param context the context describing the work to be done - */ - public void run(@NonNull Context context) { - } - - /** - * Returns true if this detector applies to the given file - * - * @param context the context to check - * @param file the file in the context to check - * @return true if this detector applies to the given context and file - */ - @Deprecated // Slated for removal in lint 2.0 - public boolean appliesTo(@NonNull Context context, @NonNull File file) { - return false; - } - - /** - * Analysis is about to begin, perform any setup steps. - * - * @param context the context for the check referencing the project, lint - * client, etc - */ - public void beforeCheckProject(@NonNull Context context) { - } - - /** - * Analysis has just been finished for the whole project, perform any - * cleanup or report issues that require project-wide analysis. - * - * @param context the context for the check referencing the project, lint - * client, etc - */ - public void afterCheckProject(@NonNull Context context) { - } - - /** - * Analysis is about to begin for the given library project, perform any setup steps. - * - * @param context the context for the check referencing the project, lint - * client, etc - */ - public void beforeCheckLibraryProject(@NonNull Context context) { - } - - /** - * Analysis has just been finished for the given library project, perform any - * cleanup or report issues that require library-project-wide analysis. - * - * @param context the context for the check referencing the project, lint - * client, etc - */ - public void afterCheckLibraryProject(@NonNull Context context) { - } - - /** - * Analysis is about to be performed on a specific file, perform any setup - * steps. - * <p> - * Note: When this method is called at the beginning of checking an XML - * file, the context is guaranteed to be an instance of {@link XmlContext}, - * and similarly for a Java source file, the context will be a - * {@link JavaContext} and so on. - * - * @param context the context for the check referencing the file to be - * checked, the project, etc. - */ - public void beforeCheckFile(@NonNull Context context) { - } - - /** - * Analysis has just been finished for a specific file, perform any cleanup - * or report issues found - * <p> - * Note: When this method is called at the end of checking an XML - * file, the context is guaranteed to be an instance of {@link XmlContext}, - * and similarly for a Java source file, the context will be a - * {@link JavaContext} and so on. - * - * @param context the context for the check referencing the file to be - * checked, the project, etc. - */ - public void afterCheckFile(@NonNull Context context) { - } - - /** - * Returns the expected speed of this detector - * - * @return the expected speed of this detector - */ - @NonNull - @Deprecated // Slated for removal in Lint 2.0 - public Speed getSpeed() { - return Speed.NORMAL; - } - - /** - * Returns the expected speed of this detector. - * The issue parameter is made available for subclasses which analyze multiple issues - * and which need to distinguish implementation cost by issue. If the detector does - * not analyze multiple issues or does not vary in speed by issue type, just override - * {@link #getSpeed()} instead. - * - * @param issue the issue to look up the analysis speed for - * @return the expected speed of this detector - */ - @NonNull - @Deprecated // Slated for removal in Lint 2.0 - public Speed getSpeed(@SuppressWarnings("UnusedParameters") @NonNull Issue issue) { - // If not overridden, this detector does not distinguish speed by issue type - return getSpeed(); - } - - // ---- Dummy implementations to make implementing XmlScanner easier: ---- - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitDocument(@NonNull XmlContext context, @NonNull Document document) { - // This method must be overridden if your detector does - // not return something from getApplicableElements or - // getApplicableAttributes - assert false; - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - // This method must be overridden if your detector returns - // tag names from getApplicableElements - assert false; - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) { - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { - // This method must be overridden if your detector returns - // attribute names from getApplicableAttributes - assert false; - } - - @SuppressWarnings("javadoc") - @Nullable - public Collection<String> getApplicableElements() { - return null; - } - - @Nullable - @SuppressWarnings("javadoc") - public Collection<String> getApplicableAttributes() { - return null; - } - - // ---- Dummy implementations to make implementing JavaScanner easier: ---- - - @Deprecated @Nullable @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public AstVisitor createJavaVisitor(@NonNull JavaContext context) { - return null; - } - - @Deprecated @Nullable@SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public List<Class<? extends Node>> getApplicableNodeTypes() { - return null; - } - - @Deprecated @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, - @NonNull MethodInvocation node) { - } - - @Deprecated @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor, - @NonNull Node node, @NonNull String type, @NonNull String name, - boolean isFramework) { - } - - @Deprecated @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration, - @NonNull Node node, @NonNull ResolvedClass resolvedClass) { - } - - @Deprecated @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitConstructor( - @NonNull JavaContext context, - @Nullable AstVisitor visitor, - @NonNull ConstructorInvocation node, - @NonNull ResolvedMethod constructor) { - } - - // ---- Dummy implementations to make implementing a ClassScanner easier: ---- - - @SuppressWarnings("javadoc") - public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) { - } - - @SuppressWarnings("javadoc") - @Nullable - public List<String> getApplicableCallNames() { - return null; - } - - @SuppressWarnings("javadoc") - @Nullable - public List<String> getApplicableCallOwners() { - return null; - } - - @SuppressWarnings("javadoc") - public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode, - @NonNull MethodNode method, @NonNull MethodInsnNode call) { - } - - @SuppressWarnings("javadoc") - @Nullable - public int[] getApplicableAsmNodeTypes() { - return null; - } - - @SuppressWarnings("javadoc") - public void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode, - @NonNull MethodNode method, @NonNull AbstractInsnNode instruction) { - } - - // ---- Dummy implementations to make implementing an OtherFileScanner easier: ---- - - @SuppressWarnings({"UnusedParameters", "unused"}) - public boolean appliesToFolder(@NonNull Scope scope, @Nullable ResourceFolderType folderType) { - return false; - } - - @NonNull - public EnumSet<Scope> getApplicableFiles() { - return Scope.OTHER_SCOPE; - } - - // ---- Dummy implementations to make implementing an GradleScanner easier: ---- - - public void visitBuildScript(@NonNull Context context, Map<String, Object> sharedData) { - } - - // ---- Dummy implementations to make implementing a resource folder scanner easier: ---- - - public void checkFolder(@NonNull ResourceContext context, @NonNull String folderName) { - } - - // ---- Dummy implementations to make implementing a binary resource scanner easier: ---- - - public void checkBinaryResource(@NonNull ResourceContext context) { - } - - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return true; - } - - // ---- Dummy implementation to make implementing UastScanner easier: ---- - - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - } - - public void visitReference( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UReferenceExpression reference, - @NonNull PsiElement referenced) { - } - - public void visitConstructor( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UCallExpression node, - @NonNull UMethod constructor) { - } - - public void visitMethod( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UCallExpression node, - @NonNull UMethod method) { - } - - @Nullable - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return null; - } - - @Nullable - public List<Class<? extends UElement>> getApplicableUastTypes() { - return null; - } - - public void visitResourceReference( - @NonNull JavaContext context, - @Nullable UastVisitor visitor, - @NonNull UElement node, - @NonNull ResourceType type, - @NonNull String name, - boolean isFramework) { - } - - // ---- Dummy implementation to make implementing JavaPsiScanner easier: ---- - - @Nullable - public List<String> getApplicableMethodNames() { - return null; - } - - @Nullable @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public List<String> getApplicableConstructorTypes() { - return null; - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public boolean appliesToResourceRefs() { - return false; - } - - @Nullable @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public List<String> applicableSuperClasses() { - return null; - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitMethod(@NonNull JavaContext context, @Nullable JavaElementVisitor visitor, - @NonNull PsiMethodCallExpression call, @NonNull PsiMethod method) { - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitConstructor( - @NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, - @NonNull PsiNewExpression node, - @NonNull PsiMethod constructor) { - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitResourceReference(@NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, @NonNull PsiElement node, - @NonNull ResourceType type, @NonNull String name, boolean isFramework) { - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void checkClass(@NonNull JavaContext context, @NonNull PsiClass declaration) { - } - - @Nullable @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public JavaElementVisitor createPsiVisitor(@NonNull JavaContext context) { - return null; - } - - @Nullable @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public List<Class<? extends PsiElement>> getApplicablePsiTypes() { - return null; - } - - @Nullable @SuppressWarnings({"unused", "javadoc"}) - public List<String> getApplicableReferenceNames() { - return null; - } - - @SuppressWarnings({"UnusedParameters", "unused", "javadoc"}) - public void visitReference( - @NonNull JavaContext context, - @Nullable JavaElementVisitor visitor, - @NonNull PsiJavaCodeReferenceElement reference, - @NonNull PsiElement referenced) { - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Implementation.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Implementation.java deleted file mode 100644 index eb5358c..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Implementation.java +++ /dev/null
@@ -1,189 +0,0 @@ -/* - * 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.google.common.annotations.Beta; - -import java.util.EnumSet; - -/** - * An {@linkplain Implementation} of an {@link Issue} maps to the {@link Detector} - * class responsible for analyzing the issue, as well as the {@link Scope} required - * by the detector to perform its analysis. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class Implementation { - private final Class<? extends Detector> mClass; - private final EnumSet<Scope> mScope; - private EnumSet<Scope>[] mAnalysisScopes; - - @SuppressWarnings("unchecked") - private static final EnumSet<Scope>[] EMPTY = new EnumSet[0]; - - /** - * Creates a new implementation for analyzing one or more issues - * - * @param detectorClass the class of the detector to find this issue - * @param scope the scope of files required to analyze this issue - */ - @SuppressWarnings("unchecked") - public Implementation( - @NonNull Class<? extends Detector> detectorClass, - @NonNull EnumSet<Scope> scope) { - this(detectorClass, scope, EMPTY); - } - - /** - * Creates a new implementation for analyzing one or more issues - * - * @param detectorClass the class of the detector to find this issue - * @param scope the scope of files required to analyze this issue - * @param analysisScopes optional set of extra scopes the detector is capable of working in - */ - public Implementation( - @NonNull Class<? extends Detector> detectorClass, - @NonNull EnumSet<Scope> scope, - @NonNull EnumSet<Scope>... analysisScopes) { - mClass = detectorClass; - mScope = scope; - mAnalysisScopes = analysisScopes; - } - - /** - * Returns the class of the detector to use to find this issue - * - * @return the class of the detector to use to find this issue - */ - @NonNull - public Class<? extends Detector> getDetectorClass() { - return mClass; - } - - @Override - public String toString() { - return mClass.toString(); - } - - /** - * Returns the scope required to analyze the code to detect this issue. - * This is determined by the detectors which reports the issue. - * - * @return the required scope - */ - @NonNull - public EnumSet<Scope> getScope() { - return mScope; - } - - /** - * Returns the sets of scopes required to analyze this issue, or null if all - * scopes named by {@link #getScope()} are necessary. Note that only - * <b>one</b> match out of this collection is required, not all, and that - * the scope set returned by {@link #getScope()} does not have to be returned - * by this method, but is always implied to be included. - * <p> - * The scopes returned by {@link #getScope()} list all the various - * scopes that are <b>affected</b> by this issue, meaning the detector - * should consider it. Frequently, the detector must analyze all these - * scopes in order to properly decide whether an issue is found. For - * example, the unused resource detector needs to consider both the XML - * resource files and the Java source files in order to decide if a resource - * is unused. If it analyzes just the Java files for example, it might - * incorrectly conclude that a resource is unused because it did not - * discover a resource reference in an XML file. - * <p> - * However, there are other issues where the issue can occur in a variety of - * files, but the detector can consider each in isolation. For example, the - * API checker is affected by both XML files and Java class files (detecting - * both layout constructor references in XML layout files as well as code - * references in .class files). It doesn't have to analyze both; it is - * capable of incrementally analyzing just an XML file, or just a class - * file, without considering the other. - * <p> - * The required scope list provides a list of scope sets that can be used to - * analyze this issue. For each scope set, all the scopes must be matched by - * the incremental analysis, but any one of the scope sets can be analyzed - * in isolation. - * <p> - * The required scope list is not required to include the full scope set - * returned by {@link #getScope()}; that set is always assumed to be - * included. - * <p> - * NOTE: You would normally call {@link #isAdequate(EnumSet)} rather - * than calling this method directly. - * - * @return a list of required scopes, or null. - */ - @NonNull - public EnumSet<Scope>[] getAnalysisScopes() { - return mAnalysisScopes; - } - - /** - * Returns true if the given scope is adequate for analyzing this issue. - * This looks through the analysis scopes (see - * {@link #getAnalysisScopes()}) and if the scope passed in fully - * covers at least one of them, or if it covers the scope of the issue - * itself (see {@link #getScope()}, which should be a superset of all the - * analysis scopes) returns true. - * <p> - * The scope set returned by {@link #getScope()} lists all the various - * scopes that are <b>affected</b> by this issue, meaning the detector - * should consider it. Frequently, the detector must analyze all these - * scopes in order to properly decide whether an issue is found. For - * example, the unused resource detector needs to consider both the XML - * resource files and the Java source files in order to decide if a resource - * is unused. If it analyzes just the Java files for example, it might - * incorrectly conclude that a resource is unused because it did not - * discover a resource reference in an XML file. - * <p> - * However, there are other issues where the issue can occur in a variety of - * files, but the detector can consider each in isolation. For example, the - * API checker is affected by both XML files and Java class files (detecting - * both layout constructor references in XML layout files as well as code - * references in .class files). It doesn't have to analyze both; it is - * capable of incrementally analyzing just an XML file, or just a class - * file, without considering the other. - * <p> - * An issue can register additional scope sets that can are adequate - * for analyzing the issue, by supplying it to - * {@link #Implementation(Class, java.util.EnumSet, java.util.EnumSet[])}. - * This method returns true if the given scope matches one or more analysis - * scope, or the overall scope. - * - * @param scope the scope available for analysis - * @return true if this issue can be analyzed with the given available scope - */ - public boolean isAdequate(@NonNull EnumSet<Scope> scope) { - if (scope.containsAll(mScope)) { - return true; - } - - if (mAnalysisScopes != null) { - for (EnumSet<Scope> analysisScope : mAnalysisScopes) { - if (scope.containsAll(analysisScope)) { - return true; - } - } - } - - return false; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Issue.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Issue.java deleted file mode 100644 index 2427132..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Issue.java +++ /dev/null
@@ -1,307 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import static com.android.tools.klint.detector.api.TextFormat.RAW; - -import com.android.annotations.NonNull; -import com.android.tools.klint.client.api.Configuration; -import com.google.common.annotations.Beta; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * An issue is a potential bug in an Android application. An issue is discovered - * by a {@link Detector}, and has an associated {@link Severity}. - * <p> - * Issues and detectors are separate classes because a detector can discover - * multiple different issues as it's analyzing code, and we want to be able to - * different severities for different issues, the ability to suppress one but - * not other issues from the same detector, and so on. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public final class Issue implements Comparable<Issue> { - private final String mId; - private final String mBriefDescription; - private final String mExplanation; - private final Category mCategory; - private final int mPriority; - private final Severity mSeverity; - private Object mMoreInfoUrls; - private boolean mEnabledByDefault = true; - private Implementation mImplementation; - - // Use factory methods - private Issue( - @NonNull String id, - @NonNull String shortDescription, - @NonNull String explanation, - @NonNull Category category, - int priority, - @NonNull Severity severity, - @NonNull Implementation implementation) { - assert !shortDescription.isEmpty(); - assert !explanation.isEmpty(); - - mId = id; - mBriefDescription = shortDescription; - mExplanation = explanation; - mCategory = category; - mPriority = priority; - mSeverity = severity; - mImplementation = implementation; - } - - /** - * Creates a new issue. The description strings can use some simple markup; - * see the {@link TextFormat#RAW} documentation - * for details. - * - * @param id the fixed id of the issue - * @param briefDescription short summary (typically 5-6 words or less), typically - * describing the <b>problem</b> rather than the <b>fix</b> - * (e.g. "Missing minSdkVersion") - * @param explanation a full explanation of the issue, with suggestions for - * how to fix it - * @param category the associated category, if any - * @param priority the priority, a number from 1 to 10 with 10 being most - * important/severe - * @param severity the default severity of the issue - * @param implementation the default implementation for this issue - * @return a new {@link Issue} - */ - @NonNull - public static Issue create( - @NonNull String id, - @NonNull String briefDescription, - @NonNull String explanation, - @NonNull Category category, - int priority, - @NonNull Severity severity, - @NonNull Implementation implementation) { - return new Issue(id, briefDescription, explanation, category, priority, - severity, implementation); - } - - /** - * For compatibility with older custom rules) - * - * @deprecated Use {@link #create(String, String, String, Category, int, Severity, Implementation)} instead - */ - @NonNull - @Deprecated - public static Issue create( - @NonNull String id, - @NonNull String briefDescription, - @SuppressWarnings("UnusedParameters") @NonNull String description, - @NonNull String explanation, - @NonNull Category category, - int priority, - @NonNull Severity severity, - @NonNull Implementation implementation) { - return new Issue(id, briefDescription, explanation, category, priority, - severity, implementation); - } - - /** - * Returns the unique id of this issue. These should not change over time - * since they are used to persist the names of issues suppressed by the user - * etc. It is typically a single camel-cased word. - * - * @return the associated fixed id, never null and always unique - */ - @NonNull - public String getId() { - return mId; - } - - /** - * Briefly (in a couple of words) describes these errors - * - * @return a brief summary of the issue, never null, never empty - */ - @NonNull - public String getBriefDescription(@NonNull TextFormat format) { - return RAW.convertTo(mBriefDescription, format); - } - - /** - * Describes the error found by this rule, e.g. - * "Buttons must define contentDescriptions". Preferably the explanation - * should also contain a description of how the problem should be solved. - * Additional info can be provided via {@link #getMoreInfo()}. - * - * @param format the format to write the format as - * @return an explanation of the issue, never null, never empty - */ - @NonNull - public String getExplanation(@NonNull TextFormat format) { - return RAW.convertTo(mExplanation, format); - } - - /** - * The primary category of the issue - * - * @return the primary category of the issue, never null - */ - @NonNull - public Category getCategory() { - return mCategory; - } - - /** - * Returns a priority, in the range 1-10, with 10 being the most severe and - * 1 the least - * - * @return a priority from 1 to 10 - */ - public int getPriority() { - return mPriority; - } - - /** - * Returns the default severity of the issues found by this detector (some - * tools may allow the user to specify custom severities for detectors). - * <p> - * Note that even though the normal way for an issue to be disabled is for - * the {@link Configuration} to return {@link Severity#IGNORE}, there is a - * {@link #isEnabledByDefault()} method which can be used to turn off issues - * by default. This is done rather than just having the severity as the only - * attribute on the issue such that an issue can be configured with an - * appropriate severity (such as {@link Severity#ERROR}) even when issues - * are disabled by default for example because they are experimental or not - * yet stable. - * - * @return the severity of the issues found by this detector - */ - @NonNull - public Severity getDefaultSeverity() { - return mSeverity; - } - - /** - * Returns a link (a URL string) to more information, or null - * - * @return a link to more information, or null - */ - @NonNull - public List<String> getMoreInfo() { - if (mMoreInfoUrls == null) { - return Collections.emptyList(); - } else if (mMoreInfoUrls instanceof String) { - return Collections.singletonList((String) mMoreInfoUrls); - } else { - assert mMoreInfoUrls instanceof List; - //noinspection unchecked - return (List<String>) mMoreInfoUrls; - } - } - - /** - * Adds a more info URL string - * - * @param moreInfoUrl url string - * @return this, for constructor chaining - */ - @NonNull - public Issue addMoreInfo(@NonNull String moreInfoUrl) { - // Nearly all issues supply at most a single URL, so don't bother with - // lists wrappers for most of these issues - if (mMoreInfoUrls == null) { - mMoreInfoUrls = moreInfoUrl; - } else if (mMoreInfoUrls instanceof String) { - String existing = (String) mMoreInfoUrls; - List<String> list = new ArrayList<String>(2); - list.add(existing); - list.add(moreInfoUrl); - mMoreInfoUrls = list; - } else { - assert mMoreInfoUrls instanceof List; - //noinspection unchecked - ((List<String>) mMoreInfoUrls).add(moreInfoUrl); - } - return this; - } - - /** - * Returns whether this issue should be enabled by default, unless the user - * has explicitly disabled it. - * - * @return true if this issue should be enabled by default - */ - public boolean isEnabledByDefault() { - return mEnabledByDefault; - } - - /** - * Returns the implementation for the given issue - * - * @return the implementation for this issue - */ - @NonNull - public Implementation getImplementation() { - return mImplementation; - } - - /** - * Sets the implementation for the given issue. This is typically done by - * IDEs that can offer a replacement for a given issue which performs better - * or in some other way works better within the IDE. - * - * @param implementation the new implementation to use - */ - public void setImplementation(@NonNull Implementation implementation) { - mImplementation = implementation; - } - - /** - * Sorts the detectors alphabetically by id. This is intended to make it - * convenient to store settings for detectors in a fixed order. It is not - * intended as the order to be shown to the user; for that, a tool embedding - * lint might consider the priorities, categories, severities etc of the - * various detectors. - * - * @param other the {@link Issue} to compare this issue to - */ - @Override - public int compareTo(@NonNull Issue other) { - return getId().compareTo(other.getId()); - } - - /** - * Sets whether this issue is enabled by default. - * - * @param enabledByDefault whether the issue should be enabled by default - * @return this, for constructor chaining - */ - @NonNull - public Issue setEnabledByDefault(boolean enabledByDefault) { - mEnabledByDefault = enabledByDefault; - return this; - } - - @Override - public String toString() { - return mId; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/JavaContext.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/JavaContext.java deleted file mode 100644 index 4dd1b93..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/JavaContext.java +++ /dev/null
@@ -1,823 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.JavaParser; -import com.android.tools.klint.client.api.JavaParser.ResolvedClass; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.containers.ContainerUtil; -import lombok.ast.*; -import lombok.ast.Position; -import org.jetbrains.uast.*; -import org.jetbrains.uast.psi.UElementWithLocation; - -import java.io.File; -import java.util.Iterator; - -import static com.android.SdkConstants.CLASS_CONTEXT; -import static com.android.tools.klint.client.api.JavaParser.ResolvedNode; -import static com.android.tools.klint.client.api.JavaParser.TypeDescriptor; - -/** - * A {@link Context} used when checking Java files. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -public class JavaContext extends Context { - static final String SUPPRESS_COMMENT_PREFIX = "//noinspection "; //$NON-NLS-1$ - - /** - * The parse tree - * - * @deprecated Use {@link #mJavaFile} instead (see {@link JavaPsiScanner}) - */ - @Deprecated - private Node mCompilationUnit; - - /** The parse tree */ - private PsiJavaFile mJavaFile; - - private UFile mUFile; - - /** The parser which produced the parse tree */ - private final JavaParser mParser; - - /** - * Constructs a {@link JavaContext} for running lint on the given file, with - * the given scope, in the given project reporting errors to the given - * client. - * - * @param driver the driver running through the checks - * @param project the project to run lint on which contains the given file - * @param main the main project if this project is a library project, or - * null if this is not a library project. The main project is - * the root project of all library projects, not necessarily the - * directly including project. - * @param file the file to be analyzed - * @param parser the parser to use - */ - public JavaContext( - @NonNull LintDriver driver, - @NonNull Project project, - @Nullable Project main, - @NonNull File file, - @NonNull JavaParser parser) { - super(driver, project, main, file); - mParser = parser; - } - - @NonNull - public UastContext getUastContext() { - return mParser.getUastContext(); - } - - /** - * Returns a location for the given node - * - * @param node the AST node to get a location for - * @return a location for the given node - */ - @NonNull - public Location getLocation(@NonNull Node node) { - return mParser.getLocation(this, node); - } - - /** - * Returns a location for the given node range (from the starting offset of the first node to - * the ending offset of the second node). - * - * @param from the AST node to get a starting location from - * @param fromDelta Offset delta to apply to the starting offset - * @param to the AST node to get a ending location from - * @param toDelta Offset delta to apply to the ending offset - * @return a location for the given node - */ - @NonNull - public Location getRangeLocation( - @NonNull Node from, - int fromDelta, - @NonNull Node to, - int toDelta) { - return mParser.getRangeLocation(this, from, fromDelta, to, toDelta); - } - - /** - * Returns a location for the given node range (from the starting offset of the first node to - * the ending offset of the second node). - * - * @param from the AST node to get a starting location from - * @param fromDelta Offset delta to apply to the starting offset - * @param to the AST node to get a ending location from - * @param toDelta Offset delta to apply to the ending offset - * @return a location for the given node - */ - @NonNull - public Location getRangeLocation( - @NonNull PsiElement from, - int fromDelta, - @NonNull PsiElement to, - int toDelta) { - return mParser.getRangeLocation(this, from, fromDelta, to, toDelta); - } - - /** - * Returns a {@link Location} for the given node. This attempts to pick a shorter - * location range than the entire node; for a class or method for example, it picks - * the name node (if found). For statement constructs such as a {@code switch} statement - * it will highlight the keyword, etc. - * - * @param node the AST node to create a location for - * @return a location for the given node - */ - @NonNull - public Location getNameLocation(@NonNull Node node) { - return mParser.getNameLocation(this, node); - } - - /** - * Returns a {@link Location} for the given node. This attempts to pick a shorter - * location range than the entire node; for a class or method for example, it picks - * the name node (if found). For statement constructs such as a {@code switch} statement - * it will highlight the keyword, etc. - * - * @param element the AST node to create a location for - * @return a location for the given node - */ - @NonNull - public Location getNameLocation(@NonNull PsiElement element) { - if (element instanceof PsiSwitchStatement) { - // Just use keyword - return mParser.getRangeLocation(this, element, 0, 6); // 6: "switch".length() - } - return mParser.getNameLocation(this, element); - } - - @NonNull - public Location getUastNameLocation(@NonNull UElement element) { - if (element instanceof UDeclaration) { - UElement nameIdentifier = ((UDeclaration) element).getUastAnchor(); - if (nameIdentifier != null) { - return getUastLocation(nameIdentifier); - } - } else if (element instanceof PsiNameIdentifierOwner) { - PsiElement nameIdentifier = ((PsiNameIdentifierOwner) element).getNameIdentifier(); - if (nameIdentifier != null) { - return getLocation(nameIdentifier); - } - } else if (element instanceof UCallExpression) { - UElement methodReference = ((UCallExpression) element).getMethodIdentifier(); - if (methodReference != null) { - return getUastLocation(methodReference); - } - } - - return getUastLocation(element); - } - - @NonNull - public Location getLocation(@NonNull PsiElement node) { - return mParser.getLocation(this, node); - } - - @NonNull - public Location getUastLocation(@Nullable UElement node) { - if (node == null) { - return Location.NONE; - } - - if (node instanceof UElementWithLocation) { - UFile file = UastUtils.getContainingFile(node); - if (file == null) { - return Location.NONE; - } - File ioFile = UastUtils.getIoFile(file); - if (ioFile == null) { - return Location.NONE; - } - UElementWithLocation segment = (UElementWithLocation) node; - return Location.create(ioFile, file.getPsi().getText(), - segment.getStartOffset(), segment.getEndOffset()); - } else { - PsiElement psiElement = node.getPsi(); - if (psiElement != null) { - TextRange range = psiElement.getTextRange(); - UFile containingFile = getUFile(); - if (containingFile == null) { - return Location.NONE; - } - File file = this.file; - if (!containingFile.equals(getUFile())) { - // Reporting an error in a different file. - if (getDriver().getScope().size() == 1) { - // Don't bother with this error if it's in a different file during single-file analysis - return Location.NONE; - } - VirtualFile virtualFile = containingFile.getPsi().getVirtualFile(); - if (virtualFile == null) { - return Location.NONE; - } - file = VfsUtilCore.virtualToIoFile(virtualFile); - } - return Location.create(file, getContents(), range.getStartOffset(), - range.getEndOffset()); - } - } - - return Location.NONE; - } - - @NonNull - public JavaParser getParser() { - return mParser; - } - - @NonNull - public JavaEvaluator getEvaluator() { - return mParser.getEvaluator(); - } - - @Nullable - public Node getCompilationUnit() { - return mCompilationUnit; - } - - /** - * Sets the compilation result. Not intended for client usage; the lint infrastructure - * will set this when a context has been processed - * - * @param compilationUnit the parse tree - */ - public void setCompilationUnit(@Nullable Node compilationUnit) { - mCompilationUnit = compilationUnit; - } - - /** - * Returns the {@link UFile}. - * - * @return the parsed UFile - */ - @Nullable - public UFile getUFile() { - return mUFile; - } - - /** - * Sets the compilation result. Not intended for client usage; the lint infrastructure - * will set this when a context has been processed - * - * @param javaFile the parse tree - */ - public void setJavaFile(@Nullable PsiJavaFile javaFile) { - mJavaFile = javaFile; - } - - public void setUFile(@Nullable UFile file) { - mUFile = file; - } - - @Override - public void report(@NonNull Issue issue, @NonNull Location location, - @NonNull String message) { - if (mDriver.isSuppressed(this, issue, mCompilationUnit)) { - return; - } - super.report(issue, location, message); - } - - /** - * Reports an issue applicable to a given AST node. The AST node is used as the - * scope to check for suppress lint annotations. - * - * @param issue the issue to report - * @param scope the AST node scope the error applies to. The lint infrastructure - * will check whether there are suppress annotations on this node (or its enclosing - * nodes) and if so suppress the warning without involving the client. - * @param location the location of the issue, or null if not known - * @param message the message for this warning - */ - public void report( - @NonNull Issue issue, - @Nullable Node scope, - @NonNull Location location, - @NonNull String message) { - if (scope != null && mDriver.isSuppressed(this, issue, scope)) { - return; - } - super.report(issue, location, message); - } - - public void report( - @NonNull Issue issue, - @Nullable PsiElement scope, - @NonNull Location location, - @NonNull String message) { - if (scope != null && mDriver.isSuppressed(this, issue, scope)) { - return; - } - super.report(issue, location, message); - } - - public void report( - @NonNull Issue issue, - @Nullable UElement scope, - @NonNull Location location, - @NonNull String message) { - if (scope != null && mDriver.isSuppressed(this, issue, scope)) { - return; - } - super.report(issue, location, message); - } - - /** UDeclaration is a PsiElement, so it's impossible to call report(Issue, UElement, ...) - * without an explicit cast. */ - public void reportUast( - @NonNull Issue issue, - @Nullable UElement scope, - @NonNull Location location, - @NonNull String message) { - report(issue, scope, location, message); - } - - /** - * Report an error. - * Like {@link #report(Issue, Node, Location, String)} but with - * a now-unused data parameter at the end. - * - * @deprecated Use {@link #report(Issue, Node, Location, String)} instead; - * this method is here for custom rule compatibility - */ - @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules - @Deprecated - public void report( - @NonNull Issue issue, - @Nullable Node scope, - @NonNull Location location, - @NonNull String message, - @SuppressWarnings("UnusedParameters") @Nullable Object data) { - report(issue, scope, location, message); - } - - /** - * @deprecated Use {@link PsiTreeUtil#getParentOfType(PsiElement, Class[])} - * with PsiMethod.class instead - */ - @Deprecated - @Nullable - public static Node findSurroundingMethod(Node scope) { - while (scope != null) { - Class<? extends Node> type = scope.getClass(); - // The Lombok AST uses a flat hierarchy of node type implementation classes - // so no need to do instanceof stuff here. - if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) { - return scope; - } - - scope = scope.getParent(); - } - - return null; - } - - /** - * @deprecated Use {@link PsiTreeUtil#getParentOfType(PsiElement, Class[])} - * with PsiMethod.class instead - */ - @Deprecated - @Nullable - public static ClassDeclaration findSurroundingClass(@Nullable Node scope) { - while (scope != null) { - Class<? extends Node> type = scope.getClass(); - // The Lombok AST uses a flat hierarchy of node type implementation classes - // so no need to do instanceof stuff here. - if (type == ClassDeclaration.class) { - return (ClassDeclaration) scope; - } - - scope = scope.getParent(); - } - - return null; - } - - @Override - @Nullable - protected String getSuppressCommentPrefix() { - return SUPPRESS_COMMENT_PREFIX; - } - - /** - * @deprecated Use {@link #isSuppressedWithComment(PsiElement, Issue)} instead - */ - @Deprecated - public boolean isSuppressedWithComment(@NonNull Node scope, @NonNull Issue issue) { - // Check whether there is a comment marker - String contents = getContents(); - assert contents != null; // otherwise we wouldn't be here - Position position = scope.getPosition(); - if (position == null) { - return false; - } - - int start = position.getStart(); - return isSuppressedWithComment(start, issue); - } - - public boolean isSuppressedWithComment(@NonNull UElement scope, @NonNull Issue issue) { - PsiElement psi = scope.getPsi(); - return psi != null && isSuppressedWithComment(psi, issue); - - } - - public boolean isSuppressedWithComment(@NonNull PsiElement scope, @NonNull Issue issue) { - // Check whether there is a comment marker - String contents = getContents(); - assert contents != null; // otherwise we wouldn't be here - TextRange textRange = scope.getTextRange(); - if (textRange == null) { - return false; - } - int start = textRange.getStartOffset(); - return isSuppressedWithComment(start, issue); - } - - /** - * @deprecated Location handles aren't needed for AST nodes anymore; just use the - * {@link PsiElement} from the AST - */ - @Deprecated - @NonNull - public Location.Handle createLocationHandle(@NonNull Node node) { - return mParser.createLocationHandle(this, node); - } - - /** - * @deprecated Use PsiElement resolve methods (varies by AST node type, e.g. - * {@link PsiMethodCallExpression#resolveMethod()} - */ - @Deprecated - @Nullable - public ResolvedNode resolve(@NonNull Node node) { - return mParser.resolve(this, node); - } - - /** - * @deprecated Use {@link JavaEvaluator#findClass(String)} instead - */ - @Deprecated - @Nullable - public ResolvedClass findClass(@NonNull String fullyQualifiedName) { - return mParser.findClass(this, fullyQualifiedName); - } - - /** - * @deprecated Use {@link PsiExpression#getType()} )} instead - */ - @Deprecated - @Nullable - public TypeDescriptor getType(@NonNull Node node) { - return mParser.getType(this, node); - } - - /** - * @deprecated Use {@link #getMethodName(PsiElement)} instead - */ - @Deprecated - @Nullable - public static String getMethodName(@NonNull Node call) { - if (call instanceof MethodInvocation) { - return ((MethodInvocation)call).astName().astValue(); - } else if (call instanceof ConstructorInvocation) { - return ((ConstructorInvocation)call).astTypeReference().getTypeName(); - } else if (call instanceof EnumConstant) { - return ((EnumConstant)call).astName().astValue(); - } else { - return null; - } - } - - @Nullable - public static String getMethodName(@NonNull PsiElement call) { - if (call instanceof PsiMethodCallExpression) { - return ((PsiMethodCallExpression)call).getMethodExpression().getReferenceName(); - } else if (call instanceof PsiNewExpression) { - PsiJavaCodeReferenceElement classReference = ((PsiNewExpression) call).getClassReference(); - if (classReference != null) { - return classReference.getReferenceName(); - } else { - return null; - } - } else if (call instanceof PsiEnumConstant) { - return ((PsiEnumConstant)call).getName(); - } else { - return null; - } - } - - @Nullable - public static String getMethodName(@NonNull UElement call) { - if (call instanceof UEnumConstant) { - return ((UEnumConstant)call).getName(); - } else if (call instanceof UCallExpression) { - String methodName = ((UCallExpression) call).getMethodName(); - if (methodName != null) { - return methodName; - } else { - return UastUtils.getQualifiedName(((UCallExpression) call).getClassReference()); - } - } else { - return null; - } - } - - /** - * Searches for a name node corresponding to the given node - * @return the name node to use, if applicable - * @deprecated Use {@link #findNameElement(PsiElement)} instead - */ - @Deprecated - @Nullable - public static Node findNameNode(@NonNull Node node) { - if (node instanceof TypeDeclaration) { - // ClassDeclaration, AnnotationDeclaration, EnumDeclaration, InterfaceDeclaration - return ((TypeDeclaration) node).astName(); - } else if (node instanceof MethodDeclaration) { - return ((MethodDeclaration)node).astMethodName(); - } else if (node instanceof ConstructorDeclaration) { - return ((ConstructorDeclaration)node).astTypeName(); - } else if (node instanceof MethodInvocation) { - return ((MethodInvocation)node).astName(); - } else if (node instanceof ConstructorInvocation) { - return ((ConstructorInvocation)node).astTypeReference(); - } else if (node instanceof EnumConstant) { - return ((EnumConstant)node).astName(); - } else if (node instanceof AnnotationElement) { - return ((AnnotationElement)node).astName(); - } else if (node instanceof AnnotationMethodDeclaration) { - return ((AnnotationMethodDeclaration)node).astMethodName(); - } else if (node instanceof VariableReference) { - return ((VariableReference)node).astIdentifier(); - } else if (node instanceof LabelledStatement) { - return ((LabelledStatement)node).astLabel(); - } - - return null; - } - - /** - * Searches for a name node corresponding to the given node - * @return the name node to use, if applicable - */ - @Nullable - public static PsiElement findNameElement(@NonNull PsiElement element) { - if (element instanceof PsiClass) { - if (element instanceof PsiAnonymousClass) { - return ((PsiAnonymousClass)element).getBaseClassReference(); - } - return ((PsiClass) element).getNameIdentifier(); - } else if (element instanceof PsiMethod) { - return ((PsiMethod) element).getNameIdentifier(); - } else if (element instanceof PsiMethodCallExpression) { - return ((PsiMethodCallExpression) element).getMethodExpression(). - getReferenceNameElement(); - } else if (element instanceof PsiNewExpression) { - return ((PsiNewExpression) element).getClassReference(); - } else if (element instanceof PsiField) { - return ((PsiField)element).getNameIdentifier(); - } else if (element instanceof PsiAnnotation) { - return ((PsiAnnotation)element).getNameReferenceElement(); - } else if (element instanceof PsiReferenceExpression) { - return ((PsiReferenceExpression) element).getReferenceNameElement(); - } else if (element instanceof PsiLabeledStatement) { - return ((PsiLabeledStatement)element).getLabelIdentifier(); - } - - return null; - } - - @Deprecated - @NonNull - public static Iterator<Expression> getParameters(@NonNull Node call) { - if (call instanceof MethodInvocation) { - return ((MethodInvocation) call).astArguments().iterator(); - } else if (call instanceof ConstructorInvocation) { - return ((ConstructorInvocation) call).astArguments().iterator(); - } else if (call instanceof EnumConstant) { - return ((EnumConstant) call).astArguments().iterator(); - } else { - return ContainerUtil.emptyIterator(); - } - } - - @Deprecated - @Nullable - public static Node getParameter(@NonNull Node call, int parameter) { - Iterator<Expression> iterator = getParameters(call); - - for (int i = 0; i < parameter - 1; i++) { - if (!iterator.hasNext()) { - return null; - } - iterator.next(); - } - return iterator.hasNext() ? iterator.next() : null; - } - - /** - * Returns true if the given method invocation node corresponds to a call on a - * {@code android.content.Context} - * - * @param node the method call node - * @return true iff the method call is on a class extending context - * @deprecated use {@link JavaEvaluator#isMemberInSubClassOf(PsiMember, String, boolean)} instead - */ - @Deprecated - public boolean isContextMethod(@NonNull MethodInvocation node) { - // Method name used in many other contexts where it doesn't have the - // same semantics; only use this one if we can resolve types - // and we're certain this is the Context method - ResolvedNode resolved = resolve(node); - if (resolved instanceof JavaParser.ResolvedMethod) { - JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved; - ResolvedClass containingClass = method.getContainingClass(); - if (containingClass.isSubclassOf(CLASS_CONTEXT, false)) { - return true; - } - } - return false; - } - - /** - * Returns the first ancestor node of the given type - * - * @param element the element to search from - * @param clz the target node type - * @param <T> the target node type - * @return the nearest ancestor node in the parent chain, or null if not found - * @deprecated Use {@link PsiTreeUtil#getParentOfType} instead - */ - @Deprecated - @Nullable - public static <T extends Node> T getParentOfType( - @Nullable Node element, - @NonNull Class<T> clz) { - return getParentOfType(element, clz, true); - } - - /** - * Returns the first ancestor node of the given type - * - * @param element the element to search from - * @param clz the target node type - * @param strict if true, do not consider the element itself, only its parents - * @param <T> the target node type - * @return the nearest ancestor node in the parent chain, or null if not found - * @deprecated Use {@link PsiTreeUtil#getParentOfType} instead - */ - @Deprecated - @Nullable - public static <T extends Node> T getParentOfType( - @Nullable Node element, - @NonNull Class<T> clz, - boolean strict) { - if (element == null) { - return null; - } - - if (strict) { - element = element.getParent(); - } - - while (element != null) { - if (clz.isInstance(element)) { - //noinspection unchecked - return (T) element; - } - element = element.getParent(); - } - - return null; - } - - /** - * Returns the first ancestor node of the given type, stopping at the given type - * - * @param element the element to search from - * @param clz the target node type - * @param strict if true, do not consider the element itself, only its parents - * @param terminators optional node types to terminate the search at - * @param <T> the target node type - * @return the nearest ancestor node in the parent chain, or null if not found - * @deprecated Use {@link PsiTreeUtil#getParentOfType} instead - */ - @Deprecated - @Nullable - public static <T extends Node> T getParentOfType(@Nullable Node element, - @NonNull Class<T> clz, - boolean strict, - @NonNull Class<? extends Node>... terminators) { - if (element == null) { - return null; - } - if (strict) { - element = element.getParent(); - } - - while (element != null && !clz.isInstance(element)) { - for (Class<?> terminator : terminators) { - if (terminator.isInstance(element)) { - return null; - } - } - element = element.getParent(); - } - - //noinspection unchecked - return (T) element; - } - - /** - * Returns the first sibling of the given node that is of the given class - * - * @param sibling the sibling to search from - * @param clz the type to look for - * @param <T> the type - * @return the first sibling of the given type, or null - * @deprecated Use {@link PsiTreeUtil#getNextSiblingOfType(PsiElement, Class)} instead - */ - @Deprecated - @Nullable - public static <T extends Node> T getNextSiblingOfType(@Nullable Node sibling, - @NonNull Class<T> clz) { - if (sibling == null) { - return null; - } - Node parent = sibling.getParent(); - if (parent == null) { - return null; - } - - Iterator<Node> iterator = parent.getChildren().iterator(); - while (iterator.hasNext()) { - if (iterator.next() == sibling) { - break; - } - } - - while (iterator.hasNext()) { - Node child = iterator.next(); - if (clz.isInstance(child)) { - //noinspection unchecked - return (T) child; - } - - } - - return null; - } - - - /** - * Returns the given argument of the given call - * - * @param call the call containing arguments - * @param index the index of the target argument - * @return the argument at the given index - * @throws IllegalArgumentException if index is outside the valid range - */ - @Deprecated - @NonNull - public static Node getArgumentNode(@NonNull MethodInvocation call, int index) { - int i = 0; - for (Expression parameter : call.astArguments()) { - if (i == index) { - return parameter; - } - i++; - } - throw new IllegalArgumentException(Integer.toString(index)); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LayoutDetector.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LayoutDetector.java deleted file mode 100644 index ce1b8b2..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LayoutDetector.java +++ /dev/null
@@ -1,70 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_PADDING; -import static com.android.SdkConstants.ATTR_PADDING_BOTTOM; -import static com.android.SdkConstants.ATTR_PADDING_LEFT; -import static com.android.SdkConstants.ATTR_PADDING_RIGHT; -import static com.android.SdkConstants.ATTR_PADDING_TOP; -import static com.android.SdkConstants.VALUE_FILL_PARENT; -import static com.android.SdkConstants.VALUE_MATCH_PARENT; - -import com.android.annotations.NonNull; -import com.android.resources.ResourceFolderType; -import com.google.common.annotations.Beta; - -import org.w3c.dom.Element; - -/** - * Abstract class specifically intended for layout detectors which provides some - * common utility methods shared by layout detectors. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class LayoutDetector extends ResourceXmlDetector { - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == ResourceFolderType.LAYOUT; - } - - private static boolean isFillParent(@NonNull Element element, @NonNull String dimension) { - String width = element.getAttributeNS(ANDROID_URI, dimension); - return width.equals(VALUE_MATCH_PARENT) || width.equals(VALUE_FILL_PARENT); - } - - protected static boolean isWidthFillParent(@NonNull Element element) { - return isFillParent(element, ATTR_LAYOUT_WIDTH); - } - - protected static boolean isHeightFillParent(@NonNull Element element) { - return isFillParent(element, ATTR_LAYOUT_HEIGHT); - } - - protected boolean hasPadding(@NonNull Element root) { - return root.hasAttributeNS(ANDROID_URI, ATTR_PADDING) - || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_LEFT) - || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_RIGHT) - || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_TOP) - || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_BOTTOM); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LintUtils.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LintUtils.java deleted file mode 100644 index 811c78f..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LintUtils.java +++ /dev/null
@@ -1,1343 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.builder.model.AndroidProject; -import com.android.builder.model.ApiVersion; -import com.android.ide.common.rendering.api.ItemResourceValue; -import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.rendering.api.StyleResourceValue; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceItem; -import com.android.ide.common.resources.ResourceUrl; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.common.resources.configuration.LocaleQualifier; -import com.android.resources.FolderTypeRelationship; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkVersionInfo; -import com.android.tools.klint.client.api.LintClient; -import com.android.utils.PositionXmlParser; -import com.android.utils.SdkUtils; -import com.google.common.annotations.Beta; -import com.google.common.base.Objects; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.intellij.psi.*; -import lombok.ast.ImportDeclaration; -import org.jetbrains.org.objectweb.asm.Opcodes; -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.jetbrains.org.objectweb.asm.tree.FieldNode; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UParenthesizedExpression; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import static com.android.SdkConstants.*; -import static com.android.ide.common.resources.configuration.FolderConfiguration.QUALIFIER_SPLITTER; -import static com.android.ide.common.resources.configuration.LocaleQualifier.BCP_47_PREFIX; -import static com.android.tools.klint.client.api.JavaParser.*; - - -/** - * Useful utility methods related to lint. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class LintUtils { - // Utility class, do not instantiate - private LintUtils() { - } - - /** - * Format a list of strings, and cut of the list at {@code maxItems} if the - * number of items are greater. - * - * @param strings the list of strings to print out as a comma separated list - * @param maxItems the maximum number of items to print - * @return a comma separated list - */ - @NonNull - public static String formatList(@NonNull List<String> strings, int maxItems) { - StringBuilder sb = new StringBuilder(20 * strings.size()); - - for (int i = 0, n = strings.size(); i < n; i++) { - if (sb.length() > 0) { - sb.append(", "); //$NON-NLS-1$ - } - sb.append(strings.get(i)); - - if (maxItems > 0 && i == maxItems - 1 && n > maxItems) { - sb.append(String.format("... (%1$d more)", n - i - 1)); - break; - } - } - - return sb.toString(); - } - - /** - * Determine if the given type corresponds to a resource that has a unique - * file - * - * @param type the resource type to check - * @return true if the given type corresponds to a file-type resource - */ - public static boolean isFileBasedResourceType(@NonNull ResourceType type) { - List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); - for (ResourceFolderType folderType : folderTypes) { - if (folderType != ResourceFolderType.VALUES) { - return type != ResourceType.ID; - } - } - return false; - } - - /** - * Returns true if the given file represents an XML file - * - * @param file the file to be checked - * @return true if the given file is an xml file - */ - public static boolean isXmlFile(@NonNull File file) { - return SdkUtils.endsWithIgnoreCase(file.getPath(), DOT_XML); - } - - /** - * Returns true if the given file represents a bitmap drawable file - * - * @param file the file to be checked - * @return true if the given file is an xml file - */ - public static boolean isBitmapFile(@NonNull File file) { - String path = file.getPath(); - // endsWith(name, DOT_PNG) is also true for endsWith(name, DOT_9PNG) - return endsWith(path, DOT_PNG) - || endsWith(path, DOT_JPG) - || endsWith(path, DOT_GIF) - || endsWith(path, DOT_JPEG) - || endsWith(path, DOT_WEBP); - } - - /** - * Case insensitive ends with - * - * @param string the string to be tested whether it ends with the given - * suffix - * @param suffix the suffix to check - * @return true if {@code string} ends with {@code suffix}, - * case-insensitively. - */ - public static boolean endsWith(@NonNull String string, @NonNull String suffix) { - return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(), - suffix, 0, suffix.length()); - } - - /** - * Case insensitive starts with - * - * @param string the string to be tested whether it starts with the given prefix - * @param prefix the prefix to check - * @param offset the offset to start checking with - * @return true if {@code string} starts with {@code prefix}, - * case-insensitively. - */ - public static boolean startsWith(@NonNull String string, @NonNull String prefix, int offset) { - return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length()); - } - - /** - * Returns the basename of the given filename, unless it's a dot-file such as ".svn". - * - * @param fileName the file name to extract the basename from - * @return the basename (the filename without the file extension) - */ - public static String getBaseName(@NonNull String fileName) { - int extension = fileName.indexOf('.'); - if (extension > 0) { - return fileName.substring(0, extension); - } else { - return fileName; - } - } - - /** - * Returns the children elements of the given node - * - * @param node the parent node - * @return a list of element children, never null - */ - @NonNull - public static List<Element> getChildren(@NonNull Node node) { - NodeList childNodes = node.getChildNodes(); - List<Element> children = new ArrayList<Element>(childNodes.getLength()); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - children.add((Element) child); - } - } - - return children; - } - - /** - * Returns the <b>number</b> of children of the given node - * - * @param node the parent node - * @return the count of element children - */ - public static int getChildCount(@NonNull Node node) { - NodeList childNodes = node.getChildNodes(); - int childCount = 0; - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - childCount++; - } - } - - return childCount; - } - - /** - * Returns true if the given element is the root element of its document - * - * @param element the element to test - * @return true if the element is the root element - */ - public static boolean isRootElement(Element element) { - return element == element.getOwnerDocument().getDocumentElement(); - } - - /** - * Returns the corresponding R field name for the given XML resource name - * @param styleName the XML name - * @return the corresponding R field name - */ - public static String getFieldName(@NonNull String styleName) { - for (int i = 0, n = styleName.length(); i < n; i++) { - char c = styleName.charAt(i); - if (c == '.' || c == '-' || c == ':') { - return styleName.replace('.', '_').replace('-', '_').replace(':', '_'); - } - } - - return styleName; - } - - /** - * Returns the given id without an {@code @id/} or {@code @+id} prefix - * - * @param id the id to strip - * @return the stripped id, never null - */ - @NonNull - public static String stripIdPrefix(@Nullable String id) { - if (id == null) { - return ""; - } else if (id.startsWith(NEW_ID_PREFIX)) { - return id.substring(NEW_ID_PREFIX.length()); - } else if (id.startsWith(ID_PREFIX)) { - return id.substring(ID_PREFIX.length()); - } - - return id; - } - - /** - * Returns true if the given two id references match. This is similar to - * String equality, but it also considers "{@code @+id/foo == @id/foo}. - * - * @param id1 the first id to compare - * @param id2 the second id to compare - * @return true if the two id references refer to the same id - */ - public static boolean idReferencesMatch(@Nullable String id1, @Nullable String id2) { - if (id1 == null || id2 == null || id1.isEmpty() || id2.isEmpty()) { - return false; - } - if (id1.startsWith(NEW_ID_PREFIX)) { - if (id2.startsWith(NEW_ID_PREFIX)) { - return id1.equals(id2); - } else { - assert id2.startsWith(ID_PREFIX) : id2; - return ((id1.length() - id2.length()) - == (NEW_ID_PREFIX.length() - ID_PREFIX.length())) - && id1.regionMatches(NEW_ID_PREFIX.length(), id2, - ID_PREFIX.length(), - id2.length() - ID_PREFIX.length()); - } - } else { - assert id1.startsWith(ID_PREFIX) : id1; - if (id2.startsWith(ID_PREFIX)) { - return id1.equals(id2); - } else { - assert id2.startsWith(NEW_ID_PREFIX); - return (id2.length() - id1.length() - == (NEW_ID_PREFIX.length() - ID_PREFIX.length())) - && id2.regionMatches(NEW_ID_PREFIX.length(), id1, - ID_PREFIX.length(), - id1.length() - ID_PREFIX.length()); - } - } - } - - /** - * Computes the edit distance (number of insertions, deletions or substitutions - * to edit one string into the other) between two strings. In particular, - * this will compute the Levenshtein distance. - * <p> - * See http://en.wikipedia.org/wiki/Levenshtein_distance for details. - * - * @param s the first string to compare - * @param t the second string to compare - * @return the edit distance between the two strings - */ - public static int editDistance(@NonNull String s, @NonNull String t) { - int m = s.length(); - int n = t.length(); - int[][] d = new int[m + 1][n + 1]; - for (int i = 0; i <= m; i++) { - d[i][0] = i; - } - for (int j = 0; j <= n; j++) { - d[0][j] = j; - } - for (int j = 1; j <= n; j++) { - for (int i = 1; i <= m; i++) { - if (s.charAt(i - 1) == t.charAt(j - 1)) { - d[i][j] = d[i - 1][j - 1]; - } else { - int deletion = d[i - 1][j] + 1; - int insertion = d[i][j - 1] + 1; - int substitution = d[i - 1][j - 1] + 1; - d[i][j] = Math.min(deletion, Math.min(insertion, substitution)); - } - } - } - - return d[m][n]; - } - - /** - * Returns true if assertions are enabled - * - * @return true if assertions are enabled - */ - @SuppressWarnings("all") - public static boolean assertionsEnabled() { - boolean assertionsEnabled = false; - assert assertionsEnabled = true; // Intentional side-effect - return assertionsEnabled; - } - - /** - * Returns the layout resource name for the given layout file - * - * @param layoutFile the file pointing to the layout - * @return the layout resource name, not including the {@code @layout} - * prefix - */ - public static String getLayoutName(File layoutFile) { - String name = layoutFile.getName(); - int dotIndex = name.indexOf('.'); - if (dotIndex != -1) { - name = name.substring(0, dotIndex); - } - return name; - } - - /** - * Splits the given path into its individual parts, attempting to be - * tolerant about path separators (: or ;). It can handle possibly ambiguous - * paths, such as {@code c:\foo\bar:\other}, though of course these are to - * be avoided if possible. - * - * @param path the path variable to split, which can use both : and ; as - * path separators. - * @return the individual path components as an Iterable of strings - */ - public static Iterable<String> splitPath(@NonNull String path) { - if (path.indexOf(';') != -1) { - return Splitter.on(';').omitEmptyStrings().trimResults().split(path); - } - - List<String> combined = new ArrayList<String>(); - Iterables.addAll(combined, Splitter.on(':').omitEmptyStrings().trimResults().split(path)); - for (int i = 0, n = combined.size(); i < n; i++) { - String p = combined.get(i); - if (p.length() == 1 && i < n - 1 && Character.isLetter(p.charAt(0)) - // Technically, Windows paths do not have to have a \ after the :, - // which means it would be using the current directory on that drive, - // but that's unlikely to be the case in a path since it would have - // unpredictable results - && !combined.get(i+1).isEmpty() && combined.get(i+1).charAt(0) == '\\') { - combined.set(i, p + ':' + combined.get(i+1)); - combined.remove(i+1); - n--; - continue; - } - } - - return combined; - } - - /** - * Computes the shared parent among a set of files (which may be null). - * - * @param files the set of files to be checked - * @return the closest common ancestor file, or null if none was found - */ - @Nullable - public static File getCommonParent(@NonNull List<File> files) { - int fileCount = files.size(); - if (fileCount == 0) { - return null; - } else if (fileCount == 1) { - return files.get(0); - } else if (fileCount == 2) { - return getCommonParent(files.get(0), files.get(1)); - } else { - File common = files.get(0); - for (int i = 1; i < fileCount; i++) { - common = getCommonParent(common, files.get(i)); - if (common == null) { - return null; - } - } - - return common; - } - } - - /** - * Computes the closest common parent path between two files. - * - * @param file1 the first file to be compared - * @param file2 the second file to be compared - * @return the closest common ancestor file, or null if the two files have - * no common parent - */ - @Nullable - public static File getCommonParent(@NonNull File file1, @NonNull File file2) { - if (file1.equals(file2)) { - return file1; - } else if (file1.getPath().startsWith(file2.getPath())) { - return file2; - } else if (file2.getPath().startsWith(file1.getPath())) { - return file1; - } else { - // Dumb and simple implementation - File first = file1.getParentFile(); - while (first != null) { - File second = file2.getParentFile(); - while (second != null) { - if (first.equals(second)) { - return first; - } - second = second.getParentFile(); - } - - first = first.getParentFile(); - } - } - return null; - } - - private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$ - private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$ - - /** - * Returns the encoded String for the given file. This is usually the - * same as {@code Files.toString(file, Charsets.UTF8}, but if there's a UTF byte order mark - * (for UTF8, UTF_16 or UTF_16LE), use that instead. - * - * @param client the client to use for I/O operations - * @param file the file to read from - * @return the string - * @throws IOException if the file cannot be read properly - */ - @NonNull - public static String getEncodedString( - @NonNull LintClient client, - @NonNull File file) throws IOException { - byte[] bytes = client.readBytes(file); - if (endsWith(file.getName(), DOT_XML)) { - return PositionXmlParser.getXmlString(bytes); - } - - return getEncodedString(bytes); - } - - /** - * Returns the String corresponding to the given data. This is usually the - * same as {@code new String(data)}, but if there's a UTF byte order mark - * (for UTF8, UTF_16 or UTF_16LE), use that instead. - * <p> - * NOTE: For XML files, there is the additional complication that there - * could be a {@code encoding=} attribute in the prologue. For those files, - * use {@link PositionXmlParser#getXmlString(byte[])} instead. - * - * @param data the byte array to construct the string from - * @return the string - */ - @NonNull - public static String getEncodedString(@Nullable byte[] data) { - if (data == null) { - return ""; - } - - int offset = 0; - String defaultCharset = UTF_8; - String charset = null; - // Look for the byte order mark, to see if we need to remove bytes from - // the input stream (and to determine whether files are big endian or little endian) etc - // for files which do not specify the encoding. - // See http://unicode.org/faq/utf_bom.html#BOM for more. - if (data.length > 4) { - if (data[0] == (byte)0xef && data[1] == (byte)0xbb && data[2] == (byte)0xbf) { - // UTF-8 - defaultCharset = charset = UTF_8; - offset += 3; - } else if (data[0] == (byte)0xfe && data[1] == (byte)0xff) { - // UTF-16, big-endian - defaultCharset = charset = UTF_16; - offset += 2; - } else if (data[0] == (byte)0x0 && data[1] == (byte)0x0 - && data[2] == (byte)0xfe && data[3] == (byte)0xff) { - // UTF-32, big-endian - defaultCharset = charset = "UTF_32"; //$NON-NLS-1$ - offset += 4; - } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe - && data[2] == (byte)0x0 && data[3] == (byte)0x0) { - // UTF-32, little-endian. We must check for this *before* looking for - // UTF_16LE since UTF_32LE has the same prefix! - defaultCharset = charset = "UTF_32LE"; //$NON-NLS-1$ - offset += 4; - } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe) { - // UTF-16, little-endian - defaultCharset = charset = UTF_16LE; - offset += 2; - } - } - int length = data.length - offset; - - // Guess encoding by searching for an encoding= entry in the first line. - boolean seenOddZero = false; - boolean seenEvenZero = false; - for (int lineEnd = offset; lineEnd < data.length; lineEnd++) { - if (data[lineEnd] == 0) { - if ((lineEnd - offset) % 2 == 0) { - seenEvenZero = true; - } else { - seenOddZero = true; - } - } else if (data[lineEnd] == '\n' || data[lineEnd] == '\r') { - break; - } - } - - if (charset == null) { - charset = seenOddZero ? UTF_16LE : seenEvenZero ? UTF_16 : UTF_8; - } - - String text = null; - try { - text = new String(data, offset, length, charset); - } catch (UnsupportedEncodingException e) { - try { - if (!charset.equals(defaultCharset)) { - text = new String(data, offset, length, defaultCharset); - } - } catch (UnsupportedEncodingException u) { - // Just use the default encoding below - } - } - if (text == null) { - text = new String(data, offset, length); - } - return text; - } - - /** - * Returns true if the given class node represents a static inner class. - * - * @param classNode the inner class to be checked - * @return true if the class node represents an inner class that is static - */ - public static boolean isStaticInnerClass(@NonNull ClassNode classNode) { - // Note: We can't just filter out static inner classes like this: - // (classNode.access & Opcodes.ACC_STATIC) != 0 - // because the static flag only appears on methods and fields in the class - // file. Instead, look for the synthetic this pointer. - - @SuppressWarnings("rawtypes") // ASM API - List fieldList = classNode.fields; - for (Object f : fieldList) { - FieldNode field = (FieldNode) f; - if (field.name.startsWith("this$") && (field.access & Opcodes.ACC_SYNTHETIC) != 0) { - return false; - } - } - - return true; - } - - /** - * Returns true if the given class node represents an anonymous inner class - * - * @param classNode the class to be checked - * @return true if the class appears to be an anonymous class - */ - public static boolean isAnonymousClass(@NonNull ClassNode classNode) { - if (classNode.outerClass == null) { - return false; - } - - String name = classNode.name; - int index = name.lastIndexOf('$'); - if (index == -1 || index == name.length() - 1) { - return false; - } - - return Character.isDigit(name.charAt(index + 1)); - } - - /** - * Returns the previous opcode prior to the given node, ignoring label and - * line number nodes - * - * @param node the node to look up the previous opcode for - * @return the previous opcode, or {@link Opcodes#NOP} if no previous node - * was found - */ - public static int getPrevOpcode(@NonNull AbstractInsnNode node) { - AbstractInsnNode prev = getPrevInstruction(node); - if (prev != null) { - return prev.getOpcode(); - } else { - return Opcodes.NOP; - } - } - - /** - * Returns the previous instruction prior to the given node, ignoring label - * and line number nodes. - * - * @param node the node to look up the previous instruction for - * @return the previous instruction, or null if no previous node was found - */ - @Nullable - public static AbstractInsnNode getPrevInstruction(@NonNull AbstractInsnNode node) { - AbstractInsnNode prev = node; - while (true) { - prev = prev.getPrevious(); - if (prev == null) { - return null; - } else { - int type = prev.getType(); - if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL - && type != AbstractInsnNode.FRAME) { - return prev; - } - } - } - } - - /** - * Returns the next opcode after to the given node, ignoring label and line - * number nodes - * - * @param node the node to look up the next opcode for - * @return the next opcode, or {@link Opcodes#NOP} if no next node was found - */ - public static int getNextOpcode(@NonNull AbstractInsnNode node) { - AbstractInsnNode next = getNextInstruction(node); - if (next != null) { - return next.getOpcode(); - } else { - return Opcodes.NOP; - } - } - - /** - * Returns the next instruction after to the given node, ignoring label and - * line number nodes. - * - * @param node the node to look up the next node for - * @return the next instruction, or null if no next node was found - */ - @Nullable - public static AbstractInsnNode getNextInstruction(@NonNull AbstractInsnNode node) { - AbstractInsnNode next = node; - while (true) { - next = next.getNext(); - if (next == null) { - return null; - } else { - int type = next.getType(); - if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL - && type != AbstractInsnNode.FRAME) { - return next; - } - } - } - } - - /** - * Returns true if the given directory is a lint manifest file directory. - * - * @param dir the directory to check - * @return true if the directory contains a manifest file - */ - public static boolean isManifestFolder(File dir) { - boolean hasManifest = new File(dir, ANDROID_MANIFEST_XML).exists(); - if (hasManifest) { - // Special case: the bin/ folder can also contain a copy of the - // manifest file, but this is *not* a project directory - if (dir.getName().equals(BIN_FOLDER)) { - // ...unless of course it just *happens* to be a project named bin, in - // which case we peek at its parent to see if this is the case - dir = dir.getParentFile(); - //noinspection ConstantConditions - if (dir != null && isManifestFolder(dir)) { - // Yes, it's a bin/ directory inside a real project: ignore this dir - return false; - } - } - } - - return hasManifest; - } - - /** - * Look up the locale and region from the given parent folder name and - * return it as a combined string, such as "en", "en-rUS", b+eng-US, etc, or null if - * no language is specified. - * - * @param folderName the folder name - * @return the locale+region string or null - */ - @Nullable - public static String getLocaleAndRegion(@NonNull String folderName) { - if (folderName.indexOf('-') == -1) { - return null; - } - - String locale = null; - - for (String qualifier : QUALIFIER_SPLITTER.split(folderName)) { - int qualifierLength = qualifier.length(); - if (qualifierLength == 2) { - char first = qualifier.charAt(0); - char second = qualifier.charAt(1); - if (first >= 'a' && first <= 'z' && second >= 'a' && second <= 'z') { - locale = qualifier; - } - } else if (qualifierLength == 3 && qualifier.charAt(0) == 'r' && locale != null) { - char first = qualifier.charAt(1); - char second = qualifier.charAt(2); - if (first >= 'A' && first <= 'Z' && second >= 'A' && second <= 'Z') { - return locale + '-' + qualifier; - } - break; - } else if (qualifier.startsWith(BCP_47_PREFIX)) { - return qualifier; - } - } - - return locale; - } - - /** - * Returns true if the given class (specified by a fully qualified class - * name) name is imported in the given compilation unit either through a fully qualified - * import or by a wildcard import. - * - * @param compilationUnit the compilation unit - * @param fullyQualifiedName the fully qualified class name - * @return true if the given imported name refers to the given fully - * qualified name - * @deprecated Use PSI element hierarchies instead where type resolution is more directly - * available (call {@link PsiImportStatement#resolve()}) - */ - @Deprecated - public static boolean isImported( - @Nullable lombok.ast.Node compilationUnit, - @NonNull String fullyQualifiedName) { - if (compilationUnit == null) { - return false; - } - int dotIndex = fullyQualifiedName.lastIndexOf('.'); - int dotLength = fullyQualifiedName.length() - dotIndex; - - boolean imported = false; - for (lombok.ast.Node rootNode : compilationUnit.getChildren()) { - if (rootNode instanceof ImportDeclaration) { - ImportDeclaration importDeclaration = (ImportDeclaration) rootNode; - String fqn = importDeclaration.asFullyQualifiedName(); - if (fqn.equals(fullyQualifiedName)) { - return true; - } else if (fullyQualifiedName.regionMatches(dotIndex, fqn, - fqn.length() - dotLength, dotLength)) { - // This import is importing the class name using some other prefix, so there - // fully qualified class name cannot be imported under that name - return false; - } else if (importDeclaration.astStarImport() - && fqn.regionMatches(0, fqn, 0, dotIndex + 1)) { - imported = true; - // but don't break -- keep searching in case there's a non-wildcard - // import of the specific class name, e.g. if we're looking for - // android.content.SharedPreferences.Editor, don't match on the following: - // import android.content.SharedPreferences.*; - // import foo.bar.Editor; - } - } - } - - return imported; - } - - /** - * Looks up the resource values for the given attribute given a style. Note that - * this only looks project-level style values, it does not resume into the framework - * styles. - */ - @Nullable - public static List<ResourceValue> getStyleAttributes( - @NonNull Project project, @NonNull LintClient client, - @NonNull String styleUrl, @NonNull String namespace, @NonNull String attribute) { - if (!client.supportsProjectResources()) { - return null; - } - - AbstractResourceRepository resources = client.getProjectResources(project, true); - if (resources == null) { - return null; - } - - ResourceUrl style = ResourceUrl.parse(styleUrl); - if (style == null || style.framework) { - return null; - } - - List<ResourceValue> result = null; - - Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>(); - queue.add(new ResourceValue(style.type, style.name, false)); - Set<String> seen = Sets.newHashSet(); - int count = 0; - boolean isFrameworkAttribute = ANDROID_URI.equals(namespace); - while (count < 30 && !queue.isEmpty()) { - ResourceValue front = queue.remove(); - String name = front.getName(); - seen.add(name); - List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name); - if (items != null) { - for (ResourceItem item : items) { - ResourceValue rv = item.getResourceValue(false); - if (rv instanceof StyleResourceValue) { - StyleResourceValue srv = (StyleResourceValue) rv; - ItemResourceValue value = srv.getItem(attribute, isFrameworkAttribute); - if (value != null) { - if (result == null) { - result = Lists.newArrayList(); - } - if (!result.contains(value)) { - result.add(value); - } - } - - String parent = srv.getParentStyle(); - if (parent != null && !parent.startsWith(ANDROID_PREFIX)) { - ResourceUrl p = ResourceUrl.parse(parent); - if (p != null && !p.framework && !seen.contains(p.name)) { - seen.add(p.name); - queue.add(new ResourceValue(ResourceType.STYLE, p.name, - false)); - } - } - - int index = name.lastIndexOf('.'); - if (index > 0) { - String parentName = name.substring(0, index); - if (!seen.contains(parentName)) { - seen.add(parentName); - queue.add(new ResourceValue(ResourceType.STYLE, parentName, - false)); - } - } - } - } - } - - count++; - } - - return result; - } - - @Nullable - public static List<StyleResourceValue> getInheritedStyles( - @NonNull Project project, @NonNull LintClient client, - @NonNull String styleUrl) { - if (!client.supportsProjectResources()) { - return null; - } - - AbstractResourceRepository resources = client.getProjectResources(project, true); - if (resources == null) { - return null; - } - - ResourceUrl style = ResourceUrl.parse(styleUrl); - if (style == null || style.framework) { - return null; - } - - List<StyleResourceValue> result = null; - - Queue<ResourceValue> queue = new ArrayDeque<ResourceValue>(); - queue.add(new ResourceValue(style.type, style.name, false)); - Set<String> seen = Sets.newHashSet(); - int count = 0; - while (count < 30 && !queue.isEmpty()) { - ResourceValue front = queue.remove(); - String name = front.getName(); - seen.add(name); - List<ResourceItem> items = resources.getResourceItem(front.getResourceType(), name); - if (items != null) { - for (ResourceItem item : items) { - ResourceValue rv = item.getResourceValue(false); - if (rv instanceof StyleResourceValue) { - StyleResourceValue srv = (StyleResourceValue) rv; - if (result == null) { - result = Lists.newArrayList(); - } - result.add(srv); - - String parent = srv.getParentStyle(); - if (parent != null && !parent.startsWith(ANDROID_PREFIX)) { - ResourceUrl p = ResourceUrl.parse(parent); - if (p != null && !p.framework && !seen.contains(p.name)) { - seen.add(p.name); - queue.add(new ResourceValue(ResourceType.STYLE, p.name, - false)); - } - } - - int index = name.lastIndexOf('.'); - if (index > 0) { - String parentName = name.substring(0, index); - if (!seen.contains(parentName)) { - seen.add(parentName); - queue.add(new ResourceValue(ResourceType.STYLE, parentName, - false)); - } - } - } - } - } - - count++; - } - - return result; - } - - /** Returns true if the given two paths point to the same logical resource file within - * a source set. This means that it only checks the parent folder name and individual - * file name, not the path outside the parent folder. - * - * @param file1 the first file to compare - * @param file2 the second file to compare - * @return true if the two files have the same parent and file names - */ - public static boolean isSameResourceFile(@Nullable File file1, @Nullable File file2) { - if (file1 != null && file2 != null - && file1.getName().equals(file2.getName())) { - File parent1 = file1.getParentFile(); - File parent2 = file2.getParentFile(); - if (parent1 != null && parent2 != null && - parent1.getName().equals(parent2.getName())) { - return true; - } - } - - return false; - } - - /** - * Whether we should attempt to look up the prefix from the model. Set to false - * if we encounter a model which is too old. - * <p> - * This is public such that code which for example syncs to a new gradle model - * can reset it. - */ - public static boolean sTryPrefixLookup = true; - - /** Looks up the resource prefix for the given Gradle project, if possible */ - @Nullable - public static String computeResourcePrefix(@Nullable AndroidProject project) { - try { - if (sTryPrefixLookup && project != null) { - return project.getResourcePrefix(); - } - } catch (Exception e) { - // This happens if we're talking to an older model than 0.10 - // Ignore; fall through to normal handling and never try again. - //noinspection AssignmentToStaticFieldFromInstanceMethod - sTryPrefixLookup = false; - } - - return null; - } - - /** Computes a suggested name given a resource prefix and resource name */ - public static String computeResourceName(@NonNull String prefix, @NonNull String name) { - if (prefix.isEmpty()) { - return name; - } else if (name.isEmpty()) { - return prefix; - } else if (prefix.endsWith("_")) { - return prefix + name; - } else { - return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1); - } - } - - - /** - * Convert an {@link com.android.builder.model.ApiVersion} to a {@link - * com.android.sdklib.AndroidVersion}. The chief problem here is that the {@link - * com.android.builder.model.ApiVersion}, when using a codename, will not encode the - * corresponding API level (it just reflects the string entered by the user in the gradle file) - * so we perform a search here (since lint really wants to know the actual numeric API level) - * - * @param api the api version to convert - * @param targets if known, the installed targets (used to resolve platform codenames, only - * needed to resolve platforms newer than the tools since {@link - * com.android.sdklib.SdkVersionInfo} knows the rest) - * @return the corresponding version - */ - @NonNull - public static AndroidVersion convertVersion( - @NonNull ApiVersion api, - @Nullable IAndroidTarget[] targets) { - String codename = api.getCodename(); - if (codename != null) { - AndroidVersion version = SdkVersionInfo.getVersion(codename, targets); - if (version != null) { - return version; - } - return new AndroidVersion(api.getApiLevel(), codename); - } - return new AndroidVersion(api.getApiLevel(), null); - } - - /** - * Looks for a certain string within a larger string, which should immediately follow - * the given prefix and immediately precede the given suffix. - * - * @param string the full string to search - * @param prefix the optional prefix to follow - * @param suffix the optional suffix to precede - * @return the corresponding substring, if present - */ - @Nullable - public static String findSubstring(@NonNull String string, @Nullable String prefix, - @Nullable String suffix) { - int start = 0; - if (prefix != null) { - start = string.indexOf(prefix); - if (start == -1) { - return null; - } - start += prefix.length(); - } - - if (suffix != null) { - int end = string.indexOf(suffix, start); - if (end == -1) { - return null; - } - return string.substring(start, end); - } - - return string.substring(start); - } - - /** - * Splits up the given message coming from a given string format (where the string - * format follows the very specific convention of having only strings formatted exactly - * with the format %n$s where n is between 1 and 9 inclusive, and each formatting parameter - * appears exactly once, and in increasing order. - * - * @param format the format string responsible for creating the error message - * @param errorMessage an error message formatted with the format string - * @return the specific values inserted into the format - */ - @NonNull - public static List<String> getFormattedParameters( - @NonNull String format, - @NonNull String errorMessage) { - StringBuilder pattern = new StringBuilder(format.length()); - int parameter = 1; - for (int i = 0, n = format.length(); i < n; i++) { - char c = format.charAt(i); - if (c == '%') { - // Only support formats of the form %n$s where n is 1 <= n <=9 - assert i < format.length() - 4 : format; - assert format.charAt(i + 1) == ('0' + parameter) : format; - assert Character.isDigit(format.charAt(i + 1)) : format; - assert format.charAt(i + 2) == '$' : format; - assert format.charAt(i + 3) == 's' : format; - parameter++; - i += 3; - pattern.append("(.*)"); - } else { - pattern.append(c); - } - } - try { - Pattern compile = Pattern.compile(pattern.toString()); - Matcher matcher = compile.matcher(errorMessage); - if (matcher.find()) { - int groupCount = matcher.groupCount(); - List<String> parameters = Lists.newArrayListWithExpectedSize(groupCount); - for (int i = 1; i <= groupCount; i++) { - parameters.add(matcher.group(i)); - } - - return parameters; - } - - } catch (PatternSyntaxException pse) { - // Internal error: string format is not valid. Should be caught by unit tests - // as a failure to return the formatted parameters. - } - return Collections.emptyList(); - } - - /** - * Returns the locale for the given parent folder. - * - * @param parent the name of the parent folder - * @return null if the locale is not known, or a locale qualifier providing the language - * and possibly region - */ - @Nullable - public static LocaleQualifier getLocale(@NonNull String parent) { - if (parent.indexOf('-') != -1) { - FolderConfiguration config = FolderConfiguration.getConfigForFolder(parent); - if (config != null) { - return config.getLocaleQualifier(); - } - } - return null; - } - - /** - * Returns the locale for the given context. - * - * @param context the context to look up the locale for - * @return null if the locale is not known, or a locale qualifier providing the language - * and possibly region - */ - @Nullable - public static LocaleQualifier getLocale(@NonNull XmlContext context) { - Element root = context.document.getDocumentElement(); - if (root != null) { - String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE); - if (locale != null && !locale.isEmpty()) { - return getLocale(locale); - } - } - - return getLocale(context.file.getParentFile().getName()); - } - - /** - * Check whether the given resource file is in an English locale - * @param context the XML context for the resource file - * @param assumeForBase whether the base folder (e.g. no locale specified) should be - * treated as English - */ - public static boolean isEnglishResource(@NonNull XmlContext context, boolean assumeForBase) { - LocaleQualifier locale = LintUtils.getLocale(context); - if (locale == null) { - return assumeForBase; - } else { - return "en".equals(locale.getLanguage()); //$NON-NLS-1$ - } - } - - /** - * Create a {@link Location} for an error in the top level build.gradle file. - * This is necessary when we're doing an analysis based on the Gradle interpreted model, - * not from parsing Gradle files - and the model doesn't provide source positions. - * @param project the project containing the gradle file being analyzed - * @return location for the top level gradle file if it exists, otherwise fall back to - * the project directory. - */ - public static Location guessGradleLocation(@NonNull Project project) { - File dir = project.getDir(); - Location location; - File topLevel = new File(dir, FN_BUILD_GRADLE); - if (topLevel.exists()) { - location = Location.create(topLevel); - } else { - location = Location.create(dir); - } - return location; - } - - /** - * Returns true if the given element is the null literal - * - * @param element the element to check - * @return true if the element is "null" - */ - public static boolean isNullLiteral(@Nullable PsiElement element) { - return element instanceof PsiLiteral && "null".equals(element.getText()); - } - - public static boolean isTrueLiteral(@Nullable PsiElement element) { - return element instanceof PsiLiteral && "true".equals(element.getText()); - } - - public static boolean isFalseLiteral(@Nullable PsiElement element) { - return element instanceof PsiLiteral && "false".equals(element.getText()); - } - - @Nullable - public static PsiElement skipParentheses(@Nullable PsiElement element) { - while (element instanceof PsiParenthesizedExpression) { - element = element.getParent(); - } - - return element; - } - - @Nullable - public static UElement skipParentheses(@Nullable UElement element) { - while (element instanceof UParenthesizedExpression) { - element = element.getUastParent(); - } - - return element; - } - - @Nullable - public static PsiElement nextNonWhitespace(@Nullable PsiElement element) { - if (element != null) { - element = element.getNextSibling(); - while (element instanceof PsiWhiteSpace) { - element = element.getNextSibling(); - } - } - - return element; - } - - @Nullable - public static PsiElement prevNonWhitespace(@Nullable PsiElement element) { - if (element != null) { - element = element.getPrevSibling(); - while (element instanceof PsiWhiteSpace) { - element = element.getPrevSibling(); - } - } - - return element; - } - - public static boolean isString(@NonNull PsiType type) { - if (type instanceof PsiClassType) { - final String shortName = ((PsiClassType)type).getClassName(); - if (!Objects.equal(shortName, CommonClassNames.JAVA_LANG_STRING_SHORT)) { - return false; - } - } - return CommonClassNames.JAVA_LANG_STRING.equals(type.getCanonicalText()); - } - - @Nullable - public static String getAutoBoxedType(@NonNull String primitive) { - if (TYPE_INT.equals(primitive)) { - return TYPE_INTEGER_WRAPPER; - } else if (TYPE_LONG.equals(primitive)) { - return TYPE_LONG_WRAPPER; - } else if (TYPE_CHAR.equals(primitive)) { - return TYPE_CHARACTER_WRAPPER; - } else if (TYPE_FLOAT.equals(primitive)) { - return TYPE_FLOAT_WRAPPER; - } else if (TYPE_DOUBLE.equals(primitive)) { - return TYPE_DOUBLE_WRAPPER; - } else if (TYPE_BOOLEAN.equals(primitive)) { - return TYPE_BOOLEAN_WRAPPER; - } else if (TYPE_SHORT.equals(primitive)) { - return TYPE_SHORT_WRAPPER; - } else if (TYPE_BYTE.equals(primitive)) { - return TYPE_BYTE_WRAPPER; - } - - return null; - } - - @Nullable - public static String getPrimitiveType(@NonNull String autoBoxedType) { - if (TYPE_INTEGER_WRAPPER.equals(autoBoxedType)) { - return TYPE_INT; - } else if (TYPE_LONG_WRAPPER.equals(autoBoxedType)) { - return TYPE_LONG; - } else if (TYPE_CHARACTER_WRAPPER.equals(autoBoxedType)) { - return TYPE_CHAR; - } else if (TYPE_FLOAT_WRAPPER.equals(autoBoxedType)) { - return TYPE_FLOAT; - } else if (TYPE_DOUBLE_WRAPPER.equals(autoBoxedType)) { - return TYPE_DOUBLE; - } else if (TYPE_BOOLEAN_WRAPPER.equals(autoBoxedType)) { - return TYPE_BOOLEAN; - } else if (TYPE_SHORT_WRAPPER.equals(autoBoxedType)) { - return TYPE_SHORT; - } else if (TYPE_BYTE_WRAPPER.equals(autoBoxedType)) { - return TYPE_BYTE; - } - - return null; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Location.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Location.java deleted file mode 100644 index bb80f41..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Location.java +++ /dev/null
@@ -1,814 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.blame.SourcePosition; -import com.android.ide.common.res2.ResourceFile; -import com.android.ide.common.res2.ResourceItem; -import com.android.tools.klint.client.api.JavaParser; -import com.google.common.annotations.Beta; -import com.intellij.psi.PsiElement; - -import java.io.File; - -/** - * Location information for a warning - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class Location { - private static final String SUPER_KEYWORD = "super"; //$NON-NLS-1$ - - private final File mFile; - private final Position mStart; - private final Position mEnd; - private String mMessage; - private Location mSecondary; - private Object mClientData; - - /** - * Special marker location which means location not available, or not applicable, or filtered out, etc. - * For example, the infrastructure may return {@link #NONE} if you ask {@link JavaParser#getLocation(JavaContext, PsiElement)} - * for an element which is not in the current file during an incremental lint run in a single file. - */ - public static final Location NONE = new Location(new File("NONE"), null, null); - - /** - * (Private constructor, use one of the factory methods - * {@link Location#create(File)}, - * {@link Location#create(File, Position, Position)}, or - * {@link Location#create(File, String, int, int)}. - * <p> - * Constructs a new location range for the given file, from start to end. If - * the length of the range is not known, end may be null. - * - * @param file the associated file (but see the documentation for - * {@link #getFile()} for more information on what the file - * represents) - * @param start the starting position, or null - * @param end the ending position, or null - */ - protected Location(@NonNull File file, @Nullable Position start, @Nullable Position end) { - super(); - mFile = file; - mStart = start; - mEnd = end; - } - - /** - * Returns the file containing the warning. Note that the file *itself* may - * not yet contain the error. When editing a file in the IDE for example, - * the tool could generate warnings in the background even before the - * document is saved. However, the file is used as a identifying token for - * the document being edited, and the IDE integration can map this back to - * error locations in the editor source code. - * - * @return the file handle for the location - */ - @NonNull - public File getFile() { - return mFile; - } - - /** - * The start position of the range - * - * @return the start position of the range, or null - */ - @Nullable - public Position getStart() { - return mStart; - } - - /** - * The end position of the range - * - * @return the start position of the range, may be null for an empty range - */ - @Nullable - public Position getEnd() { - return mEnd; - } - - /** - * Returns a secondary location associated with this location (if - * applicable), or null. - * - * @return a secondary location or null - */ - @Nullable - public Location getSecondary() { - return mSecondary; - } - - /** - * Sets a secondary location for this location. - * - * @param secondary a secondary location associated with this location - */ - public void setSecondary(@Nullable Location secondary) { - mSecondary = secondary; - } - - /** - * Sets a custom message for this location. This is typically used for - * secondary locations, to describe the significance of this alternate - * location. For example, for a duplicate id warning, the primary location - * might say "This is a duplicate id", pointing to the second occurrence of - * id declaration, and then the secondary location could point to the - * original declaration with the custom message "Originally defined here". - * - * @param message the message to apply to this location - */ - public void setMessage(@NonNull String message) { - mMessage = message; - } - - /** - * Returns the custom message for this location, if any. This is typically - * used for secondary locations, to describe the significance of this - * alternate location. For example, for a duplicate id warning, the primary - * location might say "This is a duplicate id", pointing to the second - * occurrence of id declaration, and then the secondary location could point - * to the original declaration with the custom message - * "Originally defined here". - * - * @return the custom message for this location, or null - */ - @Nullable - public String getMessage() { - return mMessage; - } - - /** - * Sets the client data associated with this location. This is an optional - * field which can be used by the creator of the {@link Location} to store - * temporary state associated with the location. - * - * @param clientData the data to store with this location - */ - public void setClientData(@Nullable Object clientData) { - mClientData = clientData; - } - - /** - * Returns the client data associated with this location - an optional field - * which can be used by the creator of the {@link Location} to store - * temporary state associated with the location. - * - * @return the data associated with this location - */ - @Nullable - public Object getClientData() { - return mClientData; - } - - @Override - public String toString() { - return "Location [file=" + mFile + ", start=" + mStart + ", end=" + mEnd + ", message=" - + mMessage + ']'; - } - - /** - * Creates a new location for the given file - * - * @param file the file to create a location for - * @return a new location - */ - @NonNull - public static Location create(@NonNull File file) { - return new Location(file, null /*start*/, null /*end*/); - } - - /** - * Creates a new location for the given file and SourcePosition. - * - * @param file the file containing the positions - * @param position the source position - * @return a new location - */ - @NonNull - public static Location create( - @NonNull File file, - @NonNull SourcePosition position) { - if (position.equals(SourcePosition.UNKNOWN)) { - return new Location(file, null /*start*/, null /*end*/); - } - return new Location(file, - new DefaultPosition( - position.getStartLine(), - position.getStartColumn(), - position.getStartOffset()), - new DefaultPosition( - position.getEndLine(), - position.getEndColumn(), - position.getEndOffset())); - } - - /** - * Creates a new location for the given file and starting and ending - * positions. - * - * @param file the file containing the positions - * @param start the starting position - * @param end the ending position - * @return a new location - */ - @NonNull - public static Location create( - @NonNull File file, - @NonNull Position start, - @Nullable Position end) { - return new Location(file, start, end); - } - - /** - * Creates a new location for the given file, with the given contents, for - * the given offset range. - * - * @param file the file containing the location - * @param contents the current contents of the file - * @param startOffset the starting offset - * @param endOffset the ending offset - * @return a new location - */ - @NonNull - public static Location create( - @NonNull File file, - @Nullable String contents, - int startOffset, - int endOffset) { - if (startOffset < 0 || endOffset < startOffset) { - throw new IllegalArgumentException("Invalid offsets"); - } - - if (contents == null) { - return new Location(file, - new DefaultPosition(-1, -1, startOffset), - new DefaultPosition(-1, -1, endOffset)); - } - - int size = contents.length(); - endOffset = Math.min(endOffset, size); - startOffset = Math.min(startOffset, endOffset); - Position start = null; - int line = 0; - int lineOffset = 0; - char prev = 0; - for (int offset = 0; offset <= size; offset++) { - if (offset == startOffset) { - start = new DefaultPosition(line, offset - lineOffset, offset); - } - if (offset == endOffset) { - Position end = new DefaultPosition(line, offset - lineOffset, offset); - return new Location(file, start, end); - } - char c = contents.charAt(offset); - if (c == '\n') { - lineOffset = offset + 1; - if (prev != '\r') { - line++; - } - } else if (c == '\r') { - line++; - lineOffset = offset + 1; - } - prev = c; - } - return create(file); - } - - /** - * Creates a new location for the given file, with the given contents, for - * the given line number. - * - * @param file the file containing the location - * @param contents the current contents of the file - * @param line the line number (0-based) for the position - * @return a new location - */ - @NonNull - public static Location create(@NonNull File file, @NonNull String contents, int line) { - return create(file, contents, line, null, null, null); - } - - /** - * Creates a new location for the given file, with the given contents, for - * the given line number. - * - * @param file the file containing the location - * @param contents the current contents of the file - * @param line the line number (0-based) for the position - * @param patternStart an optional pattern to search for from the line - * match; if found, adjust the column and offsets to begin at the - * pattern start - * @param patternEnd an optional pattern to search for behind the start - * pattern; if found, adjust the end offset to match the end of - * the pattern - * @param hints optional additional information regarding the pattern search - * @return a new location - */ - @NonNull - public static Location create(@NonNull File file, @NonNull String contents, int line, - @Nullable String patternStart, @Nullable String patternEnd, - @Nullable SearchHints hints) { - int currentLine = 0; - int offset = 0; - while (currentLine < line) { - offset = contents.indexOf('\n', offset); - if (offset == -1) { - return create(file); - } - currentLine++; - offset++; - } - - if (line == currentLine) { - if (patternStart != null) { - SearchDirection direction = SearchDirection.NEAREST; - if (hints != null) { - direction = hints.mDirection; - } - - int index; - if (direction == SearchDirection.BACKWARD) { - index = findPreviousMatch(contents, offset, patternStart, hints); - line = adjustLine(contents, line, offset, index); - } else if (direction == SearchDirection.EOL_BACKWARD) { - int lineEnd = contents.indexOf('\n', offset); - if (lineEnd == -1) { - lineEnd = contents.length(); - } - - index = findPreviousMatch(contents, lineEnd, patternStart, hints); - line = adjustLine(contents, line, offset, index); - } else if (direction == SearchDirection.FORWARD) { - index = findNextMatch(contents, offset, patternStart, hints); - line = adjustLine(contents, line, offset, index); - } else { - assert direction == SearchDirection.NEAREST || - direction == SearchDirection.EOL_NEAREST; - - int lineEnd = contents.indexOf('\n', offset); - if (lineEnd == -1) { - lineEnd = contents.length(); - } - offset = lineEnd; - - int before = findPreviousMatch(contents, offset, patternStart, hints); - int after = findNextMatch(contents, offset, patternStart, hints); - - if (before == -1) { - index = after; - line = adjustLine(contents, line, offset, index); - } else if (after == -1) { - index = before; - line = adjustLine(contents, line, offset, index); - } else { - int newLinesBefore = 0; - for (int i = before; i < offset; i++) { - if (contents.charAt(i) == '\n') { - newLinesBefore++; - } - } - int newLinesAfter = 0; - for (int i = offset; i < after; i++) { - if (contents.charAt(i) == '\n') { - newLinesAfter++; - } - } - if (newLinesBefore < newLinesAfter || newLinesBefore == newLinesAfter - && offset - before < after - offset) { - index = before; - line = adjustLine(contents, line, offset, index); - } else { - index = after; - line = adjustLine(contents, line, offset, index); - } - } - } - - if (index != -1) { - int lineStart = contents.lastIndexOf('\n', index); - if (lineStart == -1) { - lineStart = 0; - } else { - lineStart++; // was pointing to the previous line's CR, not line start - } - int column = index - lineStart; - if (patternEnd != null) { - int end = contents.indexOf(patternEnd, offset + patternStart.length()); - if (end != -1) { - return new Location(file, new DefaultPosition(line, column, index), - new DefaultPosition(line, -1, end + patternEnd.length())); - } - } else if (hints != null && (hints.isJavaSymbol() || hints.isWholeWord())) { - if (hints.isConstructor() && contents.startsWith(SUPER_KEYWORD, index)) { - patternStart = SUPER_KEYWORD; - } - return new Location(file, new DefaultPosition(line, column, index), - new DefaultPosition(line, column + patternStart.length(), - index + patternStart.length())); - } - return new Location(file, new DefaultPosition(line, column, index), - new DefaultPosition(line, column, index + patternStart.length())); - } - } - - Position position = new DefaultPosition(line, -1, offset); - return new Location(file, position, position); - } - - return create(file); - } - - private static int findPreviousMatch(@NonNull String contents, int offset, String pattern, - @Nullable SearchHints hints) { - while (true) { - int index = contents.lastIndexOf(pattern, offset); - if (index == -1) { - return -1; - } else { - if (isMatch(contents, index, pattern, hints)) { - return index; - } else { - offset = index - pattern.length(); - } - } - } - } - - private static int findNextMatch(@NonNull String contents, int offset, String pattern, - @Nullable SearchHints hints) { - int constructorIndex = -1; - if (hints != null && hints.isConstructor()) { - // Special condition: See if the call is referenced as "super" instead. - assert hints.isWholeWord(); - int index = contents.indexOf(SUPER_KEYWORD, offset); - if (index != -1 && isMatch(contents, index, SUPER_KEYWORD, hints)) { - constructorIndex = index; - } - } - - while (true) { - int index = contents.indexOf(pattern, offset); - if (index == -1) { - return constructorIndex; - } else { - if (isMatch(contents, index, pattern, hints)) { - if (constructorIndex != -1) { - return Math.min(constructorIndex, index); - } - return index; - } else { - offset = index + pattern.length(); - } - } - } - } - - private static boolean isMatch(@NonNull String contents, int offset, String pattern, - @Nullable SearchHints hints) { - if (!contents.startsWith(pattern, offset)) { - return false; - } - - if (hints != null) { - char prevChar = offset > 0 ? contents.charAt(offset - 1) : 0; - int lastIndex = offset + pattern.length() - 1; - char nextChar = lastIndex < contents.length() - 1 ? contents.charAt(lastIndex + 1) : 0; - - if (hints.isWholeWord() && (Character.isLetter(prevChar) - || Character.isLetter(nextChar))) { - return false; - - } - - if (hints.isJavaSymbol()) { - if (Character.isJavaIdentifierPart(prevChar) - || Character.isJavaIdentifierPart(nextChar)) { - return false; - } - - if (prevChar == '"') { - return false; - } - - // TODO: Additional validation to see if we're in a comment, string, etc. - // This will require lexing from the beginning of the buffer. - } - - if (hints.isConstructor() && SUPER_KEYWORD.equals(pattern)) { - // Only looking for super(), not super.x, so assert that the next - // non-space character is ( - int index = lastIndex + 1; - while (index < contents.length() - 1) { - char c = contents.charAt(index); - if (c == '(') { - break; - } else if (!Character.isWhitespace(c)) { - return false; - } - index++; - } - } - } - - return true; - } - - private static int adjustLine(String doc, int line, int offset, int newOffset) { - if (newOffset == -1) { - return line; - } - - if (newOffset < offset) { - return line - countLines(doc, newOffset, offset); - } else { - return line + countLines(doc, offset, newOffset); - } - } - - private static int countLines(String doc, int start, int end) { - int lines = 0; - for (int offset = start; offset < end; offset++) { - char c = doc.charAt(offset); - if (c == '\n') { - lines++; - } - } - - return lines; - } - - /** - * Reverses the secondary location list initiated by the given location - * - * @param location the first location in the list - * @return the first location in the reversed list - */ - public static Location reverse(@NonNull Location location) { - Location next = location.getSecondary(); - location.setSecondary(null); - while (next != null) { - Location nextNext = next.getSecondary(); - next.setSecondary(location); - location = next; - next = nextNext; - } - - return location; - } - - /** - * A {@link Handle} is a reference to a location. The point of a location - * handle is to be able to create them cheaply, and then resolve them into - * actual locations later (if needed). This makes it possible to for example - * delay looking up line numbers, for locations that are offset based. - */ - public interface Handle { - /** - * Compute a full location for the given handle - * - * @return create a location for this handle - */ - @NonNull - Location resolve(); - - /** - * Sets the client data associated with this location. This is an optional - * field which can be used by the creator of the {@link Location} to store - * temporary state associated with the location. - * - * @param clientData the data to store with this location - */ - void setClientData(@Nullable Object clientData); - - /** - * Returns the client data associated with this location - an optional field - * which can be used by the creator of the {@link Location} to store - * temporary state associated with the location. - * - * @return the data associated with this location - */ - @Nullable - Object getClientData(); - } - - /** A default {@link Handle} implementation for simple file offsets */ - public static class DefaultLocationHandle implements Handle { - private final File mFile; - private final String mContents; - private final int mStartOffset; - private final int mEndOffset; - private Object mClientData; - - /** - * Constructs a new {@link DefaultLocationHandle} - * - * @param context the context pointing to the file and its contents - * @param startOffset the start offset within the file - * @param endOffset the end offset within the file - */ - public DefaultLocationHandle(@NonNull Context context, int startOffset, int endOffset) { - mFile = context.file; - mContents = context.getContents(); - mStartOffset = startOffset; - mEndOffset = endOffset; - } - - @Override - @NonNull - public Location resolve() { - return create(mFile, mContents, mStartOffset, mEndOffset); - } - - @Override - public void setClientData(@Nullable Object clientData) { - mClientData = clientData; - } - - @Override - @Nullable - public Object getClientData() { - return mClientData; - } - } - - public static class ResourceItemHandle implements Handle { - private final ResourceItem mItem; - - public ResourceItemHandle(@NonNull ResourceItem item) { - mItem = item; - } - @NonNull - @Override - public Location resolve() { - // TODO: Look up the exact item location more - // closely - ResourceFile source = mItem.getSource(); - assert source != null : mItem; - return create(source.getFile()); - } - - @Override - public void setClientData(@Nullable Object clientData) { - } - - @Nullable - @Override - public Object getClientData() { - return null; - } - } - - /** - * Whether to look forwards, or backwards, or in both directions, when - * searching for a pattern in the source code to determine the right - * position range for a given symbol. - * <p> - * When dealing with bytecode for example, there are only line number entries - * within method bodies, so when searching for the method declaration, we should only - * search backwards from the first line entry in the method. - */ - public enum SearchDirection { - /** Only search forwards */ - FORWARD, - - /** Only search backwards */ - BACKWARD, - - /** Search backwards from the current end of line (normally it's the beginning of - * the current line) */ - EOL_BACKWARD, - - /** - * Search both forwards and backwards from the given line, and prefer - * the match that is closest - */ - NEAREST, - - /** - * Search both forwards and backwards from the end of the given line, and prefer - * the match that is closest - */ - EOL_NEAREST, - } - - /** - * Extra information pertaining to finding a symbol in a source buffer, - * used by {@link Location#create(File, String, int, String, String, SearchHints)} - */ - public static class SearchHints { - /** - * the direction to search for the nearest match in (provided - * {@code patternStart} is non null) - */ - @NonNull - private final SearchDirection mDirection; - - /** Whether the matched pattern should be a whole word */ - private boolean mWholeWord; - - /** - * Whether the matched pattern should be a Java symbol (so for example, - * a match inside a comment or string literal should not be used) - */ - private boolean mJavaSymbol; - - /** - * Whether the matched pattern corresponds to a constructor; if so, look for - * some other possible source aliases too, such as "super". - */ - private boolean mConstructor; - - private SearchHints(@NonNull SearchDirection direction) { - super(); - mDirection = direction; - } - - /** - * Constructs a new {@link SearchHints} object - * - * @param direction the direction to search in for the pattern - * @return a new @link SearchHints} object - */ - @NonNull - public static SearchHints create(@NonNull SearchDirection direction) { - return new SearchHints(direction); - } - - /** - * Indicates that pattern matches should apply to whole words only - - * @return this, for constructor chaining - */ - @NonNull - public SearchHints matchWholeWord() { - mWholeWord = true; - - return this; - } - - /** @return true if the pattern match should be for whole words only */ - public boolean isWholeWord() { - return mWholeWord; - } - - /** - * Indicates that pattern matches should apply to Java symbols only - * - * @return this, for constructor chaining - */ - @NonNull - public SearchHints matchJavaSymbol() { - mJavaSymbol = true; - mWholeWord = true; - - return this; - } - - /** @return true if the pattern match should be for Java symbols only */ - public boolean isJavaSymbol() { - return mJavaSymbol; - } - - /** - * Indicates that pattern matches should apply to constructors. If so, look for - * some other possible source aliases too, such as "super". - * - * @return this, for constructor chaining - */ - @NonNull - public SearchHints matchConstructor() { - mConstructor = true; - mWholeWord = true; - mJavaSymbol = true; - - return this; - } - - /** @return true if the pattern match should be for a constructor */ - public boolean isConstructor() { - return mConstructor; - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Position.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Position.java deleted file mode 100644 index 02756d7..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Position.java +++ /dev/null
@@ -1,50 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.google.common.annotations.Beta; - -/** - * Information about a position in a file/document. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class Position { - /** - * Returns the line number (0-based where the first line is line 0) - * - * @return the 0-based line number - */ - public abstract int getLine(); - - /** - * The character offset - * - * @return the 0-based character offset - */ - public abstract int getOffset(); - - /** - * Returns the column number (where the first character on the line is 0), - * or -1 if unknown - * - * @return the 0-based column number - */ - public abstract int getColumn(); -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Project.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Project.java deleted file mode 100644 index ba3221c..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Project.java +++ /dev/null
@@ -1,1491 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import static com.android.SdkConstants.ANDROID_LIBRARY; -import static com.android.SdkConstants.ANDROID_LIBRARY_REFERENCE_FORMAT; -import static com.android.SdkConstants.ANDROID_MANIFEST_XML; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT; -import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION; -import static com.android.SdkConstants.ATTR_PACKAGE; -import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION; -import static com.android.SdkConstants.FN_ANDROID_MANIFEST_XML; -import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE; -import static com.android.SdkConstants.OLD_PROGUARD_FILE; -import static com.android.SdkConstants.PROGUARD_CONFIG; -import static com.android.SdkConstants.PROJECT_PROPERTIES; -import static com.android.SdkConstants.RES_FOLDER; -import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT; -import static com.android.SdkConstants.TAG_USES_SDK; -import static com.android.SdkConstants.VALUE_TRUE; -import static com.android.sdklib.SdkVersionInfo.HIGHEST_KNOWN_API; -import static com.android.sdklib.SdkVersionInfo.LOWEST_ACTIVE_API; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.build.FilterData; -import com.android.build.OutputFile; -import com.android.builder.model.AndroidArtifact; -import com.android.builder.model.AndroidArtifactOutput; -import com.android.builder.model.AndroidLibrary; -import com.android.builder.model.AndroidProject; -import com.android.builder.model.ProductFlavor; -import com.android.builder.model.ProductFlavorContainer; -import com.android.builder.model.Variant; -import com.android.ide.common.repository.GradleVersion; -import com.android.ide.common.repository.ResourceVisibilityLookup; -import com.android.resources.Density; -import com.android.resources.ResourceFolderType; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.BuildToolInfo; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkVersionInfo; -import com.android.tools.klint.client.api.CircularDependencyException; -import com.android.tools.klint.client.api.Configuration; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.client.api.SdkInfo; -import com.google.common.annotations.Beta; -import com.google.common.base.CharMatcher; -import com.google.common.base.Charsets; -import com.google.common.base.Splitter; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.common.io.Closeables; -import com.google.common.io.Files; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A project contains information about an Android project being scanned for - * Lint errors. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class Project { - protected final LintClient mClient; - protected final File mDir; - protected final File mReferenceDir; - protected Configuration mConfiguration; - protected String mPackage; - protected int mBuildSdk = -1; - protected IAndroidTarget mTarget; - - protected AndroidVersion mManifestMinSdk = AndroidVersion.DEFAULT; - protected AndroidVersion mManifestTargetSdk = AndroidVersion.DEFAULT; - - protected boolean mLibrary; - protected String mName; - protected String mProguardPath; - protected boolean mMergeManifests; - - /** The SDK info, if any */ - protected SdkInfo mSdkInfo; - - /** - * If non null, specifies a non-empty list of specific files under this - * project which should be checked. - */ - protected List<File> mFiles; - protected List<File> mProguardFiles; - protected List<File> mGradleFiles; - protected List<File> mManifestFiles; - protected List<File> mJavaSourceFolders; - protected List<File> mJavaClassFolders; - protected List<File> mNonProvidedJavaLibraries; - protected List<File> mJavaLibraries; - protected List<File> mTestSourceFolders; - protected List<File> mResourceFolders; - protected List<File> mAssetFolders; - protected List<Project> mDirectLibraries; - protected List<Project> mAllLibraries; - protected boolean mReportIssues = true; - protected Boolean mGradleProject; - protected Boolean mSupportLib; - protected Boolean mAppCompat; - protected GradleVersion mGradleVersion; - private Map<String, String> mSuperClassMap; - private ResourceVisibilityLookup mResourceVisibility; - private BuildToolInfo mBuildTools; - - /** - * Creates a new {@link Project} for the given directory. - * - * @param client the tool running the lint check - * @param dir the root directory of the project - * @param referenceDir See {@link #getReferenceDir()}. - * @return a new {@link Project} - */ - @NonNull - public static Project create( - @NonNull LintClient client, - @NonNull File dir, - @NonNull File referenceDir) { - return new Project(client, dir, referenceDir); - } - - /** - * Returns true if this project is a Gradle-based Android project - * - * @return true if this is a Gradle-based project - */ - public boolean isGradleProject() { - if (mGradleProject == null) { - mGradleProject = mClient.isGradleProject(this); - } - - return mGradleProject; - } - - /** - * Returns true if this project is an Android project. - * - * @return true if this project is an Android project. - */ - @SuppressWarnings("MethodMayBeStatic") - public boolean isAndroidProject() { - return true; - } - - /** - * Returns the project model for this project if it corresponds to - * a Gradle project. This is the case if {@link #isGradleProject()} - * is true and {@link #isLibrary()} is false. - * - * @return the project model, or null - */ - @Nullable - public AndroidProject getGradleProjectModel() { - return null; - } - - /** - * If this is a Gradle project with a valid Gradle model, return the version - * of the model/plugin. - * - * @return the Gradle plugin version, or null if invalid or not a Gradle project - */ - @Nullable - public GradleVersion getGradleModelVersion() { - if (mGradleVersion == null && isGradleProject()) { - AndroidProject gradleProjectModel = getGradleProjectModel(); - if (gradleProjectModel != null) { - mGradleVersion = GradleVersion.tryParse(gradleProjectModel.getModelVersion()); - } - } - - return mGradleVersion; - } - - /** - * Returns the project model for this project if it corresponds to - * a Gradle library. This is the case if both - * {@link #isGradleProject()} and {@link #isLibrary()} return true. - * - * @return the project model, or null - */ - @SuppressWarnings("UnusedDeclaration") - @Nullable - public AndroidLibrary getGradleLibraryModel() { - return null; - } - - /** - * Returns the current selected variant, if any (and if the current project is a Gradle - * project). This can be used by incremental lint rules to warn about problems in the current - * context. Lint rules should however strive to perform cross variant analysis and warn about - * problems in any configuration. - * - * @return the select variant, or null - */ - @Nullable - public Variant getCurrentVariant() { - return null; - } - - /** Creates a new Project. Use one of the factory methods to create. */ - protected Project( - @NonNull LintClient client, - @NonNull File dir, - @NonNull File referenceDir) { - mClient = client; - mDir = dir; - mReferenceDir = referenceDir; - initialize(); - } - - protected void initialize() { - // Default initialization: Use ADT/ant style project.properties file - try { - // Read properties file and initialize library state - Properties properties = new Properties(); - File propFile = new File(mDir, PROJECT_PROPERTIES); - if (propFile.exists()) { - BufferedInputStream is = new BufferedInputStream(new FileInputStream(propFile)); - try { - properties.load(is); - String value = properties.getProperty(ANDROID_LIBRARY); - mLibrary = VALUE_TRUE.equals(value); - String proguardPath = properties.getProperty(PROGUARD_CONFIG); - if (proguardPath != null) { - mProguardPath = proguardPath; - } - mMergeManifests = VALUE_TRUE.equals(properties.getProperty( - "manifestmerger.enabled")); //$NON-NLS-1$ - String target = properties.getProperty("target"); //$NON-NLS-1$ - if (target != null) { - int index = target.lastIndexOf('-'); - if (index == -1) { - index = target.lastIndexOf(':'); - } - if (index != -1) { - String versionString = target.substring(index + 1); - try { - mBuildSdk = Integer.parseInt(versionString); - } catch (NumberFormatException nufe) { - mClient.log(Severity.WARNING, null, - "Unexpected build target format: %1$s", target); - } - } - } - - for (int i = 1; i < 1000; i++) { - String key = String.format(ANDROID_LIBRARY_REFERENCE_FORMAT, i); - String library = properties.getProperty(key); - if (library == null || library.isEmpty()) { - // No holes in the numbering sequence is allowed - break; - } - - File libraryDir = new File(mDir, library).getCanonicalFile(); - - if (mDirectLibraries == null) { - mDirectLibraries = new ArrayList<Project>(); - } - - // Adjust the reference dir to be a proper prefix path of the - // library dir - File libraryReferenceDir = mReferenceDir; - if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) { - // Symlinks etc might have been resolved, so do those to - // the reference dir as well - libraryReferenceDir = libraryReferenceDir.getCanonicalFile(); - if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) { - File file = libraryReferenceDir; - while (file != null && !file.getPath().isEmpty()) { - if (libraryDir.getPath().startsWith(file.getPath())) { - libraryReferenceDir = file; - break; - } - file = file.getParentFile(); - } - } - } - - try { - Project libraryPrj = mClient.getProject(libraryDir, - libraryReferenceDir); - mDirectLibraries.add(libraryPrj); - // By default, we don't report issues in inferred library projects. - // The driver will set report = true for those library explicitly - // requested. - libraryPrj.setReportIssues(false); - } catch (CircularDependencyException e) { - e.setProject(this); - e.setLocation(Location.create(propFile)); - throw e; - } - } - } finally { - try { - Closeables.close(is, true /* swallowIOException */); - } catch (IOException e) { - // cannot happen - } - } - } - } catch (IOException ioe) { - mClient.log(ioe, "Initializing project state"); - } - - if (mDirectLibraries != null) { - mDirectLibraries = Collections.unmodifiableList(mDirectLibraries); - } else { - mDirectLibraries = Collections.emptyList(); - } - - if (isAospBuildEnvironment()) { - if (isAospFrameworksRelatedProject(mDir)) { - // No manifest file for this project: just init the manifest values here - mManifestMinSdk = mManifestTargetSdk = new AndroidVersion(HIGHEST_KNOWN_API, null); - } else if (mBuildSdk == -1) { - // only set BuildSdk for projects other than frameworks and - // the ones that don't have one set in project.properties. - mBuildSdk = getClient().getHighestKnownApiLevel(); - } - - } - } - - @Override - public String toString() { - return "Project [dir=" + mDir + ']'; - } - - @Override - public int hashCode() { - return mDir.hashCode(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Project other = (Project) obj; - return mDir.equals(other.mDir); - } - - /** - * Adds the given file to the list of files which should be checked in this - * project. If no files are added, the whole project will be checked. - * - * @param file the file to be checked - */ - public void addFile(@NonNull File file) { - if (mFiles == null) { - mFiles = new ArrayList<File>(); - } - mFiles.add(file); - } - - /** - * The list of files to be checked in this project. If null, the whole - * project should be checked. - * - * @return the subset of files to be checked, or null for the whole project - */ - @Nullable - public List<File> getSubset() { - return mFiles; - } - - /** - * Returns the list of source folders for Java source files - * - * @return a list of source folders to search for .java files - */ - @NonNull - public List<File> getJavaSourceFolders() { - if (mJavaSourceFolders == null) { - if (isAospFrameworksRelatedProject(mDir)) { - return Collections.singletonList(new File(mDir, "java")); //$NON-NLS-1$ - } - if (isAospBuildEnvironment()) { - String top = getAospTop(); - if (mDir.getAbsolutePath().startsWith(top)) { - mJavaSourceFolders = getAospJavaSourcePath(); - return mJavaSourceFolders; - } - } - - mJavaSourceFolders = mClient.getJavaSourceFolders(this); - } - - return mJavaSourceFolders; - } - - /** - * Returns the list of output folders for class files - * @return a list of output folders to search for .class files - */ - @NonNull - public List<File> getJavaClassFolders() { - if (mJavaClassFolders == null) { - if (isAospFrameworksProject(mDir)) { - String top = getAospTop(); - if (top != null) { - File out = new File(top, "out"); //$NON-NLS-1$ - if (out.exists()) { - String relative = - "target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar"; - File jar = new File(out, relative.replace('/', File.separatorChar)); - if (jar.exists()) { - mJavaClassFolders = Collections.singletonList(jar); - return mJavaClassFolders; - } - } - } - } - if (isAospBuildEnvironment()) { - String top = getAospTop(); - if (mDir.getAbsolutePath().startsWith(top)) { - mJavaClassFolders = getAospJavaClassPath(); - return mJavaClassFolders; - } - } - - mJavaClassFolders = mClient.getJavaClassFolders(this); - } - return mJavaClassFolders; - } - - /** - * Returns the list of Java libraries (typically .jar files) that this - * project depends on. Note that this refers to jar libraries, not Android - * library projects which are processed in a separate pass with their own - * source and class folders. - * - * @param includeProvided If true, included provided libraries too (libraries - * that are not packaged with the app, but are provided - * for compilation purposes and are assumed to be present - * in the running environment) - * @return a list of .jar files (or class folders) that this project depends - * on. - */ - @NonNull - public List<File> getJavaLibraries(boolean includeProvided) { - if (includeProvided) { - if (mJavaLibraries == null) { - // AOSP builds already merge libraries and class folders into - // the single classes.jar file, so these have already been processed - // in getJavaClassFolders. - mJavaLibraries = mClient.getJavaLibraries(this, true); - if (isAospBuildEnvironment()) { - // We still need to add the support-annotations library in the case of AOSP - File out = new File(getAospTop(), "out"); - String relative = "target/common/obj/JAVA_LIBRARIES/" - + "android-support-annotations_intermediates/classes"; - File annotationsDir = new File(out, relative.replace('/', File.separatorChar)); - if (annotationsDir.exists()) { - mJavaLibraries.add(annotationsDir); - } - } - } - return mJavaLibraries; - } else { - if (mNonProvidedJavaLibraries == null) { - mNonProvidedJavaLibraries = mClient.getJavaLibraries(this, false); - } - return mNonProvidedJavaLibraries; - } - } - - /** - * Returns the list of source folders for Java test source files - * - * @return a list of source folders to search for .java files - */ - @NonNull - public List<File> getTestSourceFolders() { - if (mTestSourceFolders == null) { - mTestSourceFolders = mClient.getTestSourceFolders(this); - } - - return mTestSourceFolders; - } - - /** - * Returns the resource folders. - * - * @return a list of files pointing to the resource folders, which might be empty if the project - * does not provide any resources. - */ - @NonNull - public List<File> getResourceFolders() { - if (mResourceFolders == null) { - List<File> folders = mClient.getResourceFolders(this); - - if (folders.size() == 1 && isAospFrameworksRelatedProject(mDir)) { - // No manifest file for this project: just init the manifest values here - mManifestMinSdk = mManifestTargetSdk = new AndroidVersion(HIGHEST_KNOWN_API, null); - File folder = new File(folders.get(0), RES_FOLDER); - if (!folder.exists()) { - folders = Collections.emptyList(); - } - } - - mResourceFolders = folders; - } - - return mResourceFolders; - } - - /** - * Returns the asset folders. - * - * @return a list of files pointing to the asset folders, which might be empty if the project - * does not provide any resources. - */ - @NonNull - public List<File> getAssetFolders() { - if (mAssetFolders == null) { - mAssetFolders = mClient.getAssetFolders(this); - } - - return mAssetFolders; - } - - /** - * Returns the relative path of a given file relative to the user specified - * directory (which is often the project directory but sometimes a higher up - * directory when a directory tree is being scanned - * - * @param file the file under this project to check - * @return the path relative to the reference directory (often the project directory) - */ - @NonNull - public String getDisplayPath(@NonNull File file) { - String path = file.getPath(); - String referencePath = mReferenceDir.getPath(); - if (path.startsWith(referencePath)) { - int length = referencePath.length(); - if (path.length() > length && path.charAt(length) == File.separatorChar) { - length++; - } - - return path.substring(length); - } - - return path; - } - - /** - * Returns the relative path of a given file within the current project. - * - * @param file the file under this project to check - * @return the path relative to the project - */ - @NonNull - public String getRelativePath(@NonNull File file) { - String path = file.getPath(); - String referencePath = mDir.getPath(); - if (path.startsWith(referencePath)) { - int length = referencePath.length(); - if (path.length() > length && path.charAt(length) == File.separatorChar) { - length++; - } - - return path.substring(length); - } - - return path; - } - - /** - * Returns the project root directory - * - * @return the dir - */ - @NonNull - public File getDir() { - return mDir; - } - - /** - * Returns the original user supplied directory where the lint search - * started. For example, if you run lint against {@code /tmp/foo}, and it - * finds a project to lint in {@code /tmp/foo/dev/src/project1}, then the - * {@code dir} is {@code /tmp/foo/dev/src/project1} and the - * {@code referenceDir} is {@code /tmp/foo/}. - * - * @return the reference directory, never null - */ - @NonNull - public File getReferenceDir() { - return mReferenceDir; - } - - /** - * Gets the configuration associated with this project - * - * @param driver the current driver, if any - * @return the configuration associated with this project - */ - @NonNull - public Configuration getConfiguration(@Nullable LintDriver driver) { - if (mConfiguration == null) { - mConfiguration = mClient.getConfiguration(this, driver); - } - return mConfiguration; - } - - /** - * Returns the application package specified by the manifest - * - * @return the application package, or null if unknown - */ - @Nullable - public String getPackage() { - return mPackage; - } - - /** - * Returns the minimum API level for the project - * - * @return the minimum API level or {@link AndroidVersion#DEFAULT} if unknown - */ - @NonNull - public AndroidVersion getMinSdkVersion() { - return mManifestMinSdk == null ? AndroidVersion.DEFAULT : mManifestMinSdk; - } - - /** - * Returns the minimum API <b>level</b> requested by the manifest, or -1 if not - * specified. Use {@link #getMinSdkVersion()} to get a full version if you need - * to check if the platform is a preview platform etc. - * - * @return the minimum API level or -1 if unknown - */ - public int getMinSdk() { - AndroidVersion version = getMinSdkVersion(); - return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel(); - } - - /** - * Returns the target API level for the project - * - * @return the target API level or {@link AndroidVersion#DEFAULT} if unknown - */ - @NonNull - public AndroidVersion getTargetSdkVersion() { - return mManifestTargetSdk == AndroidVersion.DEFAULT - ? getMinSdkVersion() : mManifestTargetSdk; - } - - /** - * Returns the target API <b>level</b> specified by the manifest, or -1 if not - * specified. Use {@link #getTargetSdkVersion()} to get a full version if you need - * to check if the platform is a preview platform etc. - * - * @return the target API level or -1 if unknown - */ - public int getTargetSdk() { - AndroidVersion version = getTargetSdkVersion(); - return version == AndroidVersion.DEFAULT ? -1 : version.getApiLevel(); - } - - /** - * Returns the target API used to build the project, or -1 if not known - * - * @return the build target API or -1 if unknown - */ - public int getBuildSdk() { - return mBuildSdk; - } - - /** - * Returns the specific version of the build tools being used, if known - * - * @return the build tools version in use, or null if not known - */ - @Nullable - public BuildToolInfo getBuildTools() { - if (mBuildTools == null) { - mBuildTools = mClient.getBuildTools(this); - } - - return mBuildTools; - } - - /** - * Returns the target used to build the project, or null if not known - * - * @return the build target, or null - */ - @Nullable - public IAndroidTarget getBuildTarget() { - if (mTarget == null) { - mTarget = mClient.getCompileTarget(this); - } - - return mTarget; - } - - /** - * Initialized the manifest state from the given manifest model - * - * @param document the DOM document for the manifest XML document - */ - public void readManifest(@NonNull Document document) { - Element root = document.getDocumentElement(); - if (root == null) { - return; - } - - mPackage = root.getAttribute(ATTR_PACKAGE); - - // Treat support libraries as non-reportable (in Eclipse where we don't - // have binary libraries, the support libraries have to be source-copied into - // the workspace which then triggers warnings in these libraries that users - // shouldn't have to investigate) - if (mPackage != null && mPackage.startsWith("android.support.")) { - mReportIssues = false; - } - - // Initialize minSdk and targetSdk - mManifestMinSdk = AndroidVersion.DEFAULT; - mManifestTargetSdk = AndroidVersion.DEFAULT; - NodeList usesSdks = root.getElementsByTagName(TAG_USES_SDK); - if (usesSdks.getLength() > 0) { - Element element = (Element) usesSdks.item(0); - - String minSdk = null; - if (element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) { - minSdk = element.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION); - } - if (minSdk != null) { - IAndroidTarget[] targets = mClient.getTargets(); - mManifestMinSdk = SdkVersionInfo.getVersion(minSdk, targets); - } - - if (element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) { - String targetSdk = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION); - if (targetSdk != null) { - IAndroidTarget[] targets = mClient.getTargets(); - mManifestTargetSdk = SdkVersionInfo.getVersion(targetSdk, targets); - } - } else { - mManifestTargetSdk = mManifestMinSdk; - } - } else if (isAospBuildEnvironment()) { - extractAospMinSdkVersion(); - mManifestTargetSdk = mManifestMinSdk; - } - } - - /** - * Returns true if this project is an Android library project - * - * @return true if this project is an Android library project - */ - public boolean isLibrary() { - return mLibrary; - } - - /** - * Returns the list of library projects referenced by this project - * - * @return the list of library projects referenced by this project, never - * null - */ - @NonNull - public List<Project> getDirectLibraries() { - return mDirectLibraries != null ? mDirectLibraries : Collections.<Project>emptyList(); - } - - /** - * Returns the transitive closure of the library projects for this project - * - * @return the transitive closure of the library projects for this project - */ - @NonNull - public List<Project> getAllLibraries() { - if (mAllLibraries == null) { - if (mDirectLibraries.isEmpty()) { - return mDirectLibraries; - } - - List<Project> all = new ArrayList<Project>(); - Set<Project> seen = Sets.newHashSet(); - Set<Project> path = Sets.newHashSet(); - seen.add(this); - path.add(this); - addLibraryProjects(all, seen, path); - mAllLibraries = all; - } - - return mAllLibraries; - } - - /** - * Adds this project's library project and their library projects - * recursively into the given collection of projects - * - * @param collection the collection to add the projects into - * @param seen full set of projects we've processed - * @param path the current path of library dependencies followed - */ - private void addLibraryProjects(@NonNull Collection<Project> collection, - @NonNull Set<Project> seen, @NonNull Set<Project> path) { - for (Project library : mDirectLibraries) { - if (seen.contains(library)) { - if (path.contains(library)) { - mClient.log(Severity.WARNING, null, - "Internal lint error: cyclic library dependency for %1$s", library); - } - continue; - } - collection.add(library); - seen.add(library); - path.add(library); - // Recurse - library.addLibraryProjects(collection, seen, path); - path.remove(library); - } - } - - /** - * Gets the SDK info for the current project. - * - * @return the SDK info for the current project, never null - */ - @NonNull - public SdkInfo getSdkInfo() { - if (mSdkInfo == null) { - mSdkInfo = mClient.getSdkInfo(this); - } - - return mSdkInfo; - } - - /** - * Gets the paths to the manifest files in this project, if any exists. The manifests - * should be provided such that the main manifest comes first, then any flavor versions, - * then any build types. - * - * @return the path to the manifest file, or null if it does not exist - */ - @NonNull - public List<File> getManifestFiles() { - if (mManifestFiles == null) { - File manifestFile = new File(mDir, ANDROID_MANIFEST_XML); - if (manifestFile.exists()) { - mManifestFiles = Collections.singletonList(manifestFile); - } else { - mManifestFiles = Collections.emptyList(); - } - } - - return mManifestFiles; - } - - /** - * Returns the proguard files configured for this project, if any - * - * @return the proguard files, if any - */ - @NonNull - public List<File> getProguardFiles() { - if (mProguardFiles == null) { - List<File> files = new ArrayList<File>(); - if (mProguardPath != null) { - Splitter splitter = Splitter.on(CharMatcher.anyOf(":;")); //$NON-NLS-1$ - for (String path : splitter.split(mProguardPath)) { - if (path.contains("${")) { //$NON-NLS-1$ - // Don't analyze the global/user proguard files - continue; - } - File file = new File(path); - if (!file.isAbsolute()) { - file = new File(getDir(), path); - } - if (file.exists()) { - files.add(file); - } - } - } - if (files.isEmpty()) { - File file = new File(getDir(), OLD_PROGUARD_FILE); - if (file.exists()) { - files.add(file); - } - file = new File(getDir(), FN_PROJECT_PROGUARD_FILE); - if (file.exists()) { - files.add(file); - } - } - mProguardFiles = files; - } - return mProguardFiles; - } - - /** - * Returns the Gradle build script files configured for this project, if any - * - * @return the Gradle files, if any - */ - @NonNull - public List<File> getGradleBuildScripts() { - if (mGradleFiles == null) { - if (isGradleProject()) { - mGradleFiles = Lists.newArrayListWithExpectedSize(2); - File build = new File(mDir, SdkConstants.FN_BUILD_GRADLE); - if (build.exists()) { - mGradleFiles.add(build); - } - File settings = new File(mDir, SdkConstants.FN_SETTINGS_GRADLE); - if (settings.exists()) { - mGradleFiles.add(settings); - } - } else { - mGradleFiles = Collections.emptyList(); - } - } - - return mGradleFiles; - } - - /** - * Returns the name of the project - * - * @return the name of the project, never null - */ - @NonNull - public String getName() { - if (mName == null) { - mName = mClient.getProjectName(this); - } - - return mName; - } - - /** - * Sets the name of the project - * - * @param name the name of the project, never null - */ - public void setName(@NonNull String name) { - assert !name.isEmpty(); - mName = name; - } - - /** - * Sets whether lint should report issues in this project. See - * {@link #getReportIssues()} for a full description of what that means. - * - * @param reportIssues whether lint should report issues in this project - */ - public void setReportIssues(boolean reportIssues) { - mReportIssues = reportIssues; - } - - /** - * Returns whether lint should report issues in this project. - * <p> - * If a user specifies a project and its library projects for analysis, then - * those library projects are all "included", and all errors found in all - * the projects are reported. But if the user is only running lint on the - * main project, we shouldn't report errors in any of the library projects. - * We still need to <b>consider</b> them for certain types of checks, such - * as determining whether resources found in the main project are unused, so - * the detectors must still get a chance to look at these projects. The - * {@code #getReportIssues()} attribute is used for this purpose. - * - * @return whether lint should report issues in this project - */ - public boolean getReportIssues() { - return mReportIssues; - } - - /** - * Returns whether manifest merging is in effect - * - * @return true if manifests in library projects should be merged into main projects - */ - public boolean isMergingManifests() { - return mMergeManifests; - } - - - // --------------------------------------------------------------------------- - // Support for running lint on the AOSP source tree itself - - private static Boolean sAospBuild; - - /** Is lint running in an AOSP build environment */ - public static boolean isAospBuildEnvironment() { - if (sAospBuild == null) { - sAospBuild = getAospTop() != null; - } - - return sAospBuild; - } - - /** - * Is this the frameworks or related AOSP project? Needs some hardcoded support since - * it doesn't have a manifest file, etc. - * - * A frameworks AOSP projects can be any directory under "frameworks" that - * 1. Is not the "support" directory (which uses the public support annotations) - * 2. Doesn't have an AndroidManifest.xml (it's an app instead) - * - * @param dir the project directory to check - * @return true if this looks like the frameworks/dir project and does not have - * an AndroidManifest.xml - */ - public static boolean isAospFrameworksRelatedProject(@NonNull File dir) { - if (isAospBuildEnvironment()) { - File frameworks = new File(getAospTop(), "frameworks"); //$NON-NLS-1$ - String frameworksDir = frameworks.getAbsolutePath(); - String supportDir = new File(frameworks, "support").getAbsolutePath(); //$NON-NLS-1$ - if (dir.exists() - && !dir.getAbsolutePath().startsWith(supportDir) - && dir.getAbsolutePath().startsWith(frameworksDir) - && !(new File(dir, FN_ANDROID_MANIFEST_XML).exists())) { - return true; - } - } - return false; - } - - /** - * Is this the actual frameworks project. - * @param dir the project directory to check. - * @return true if this is the frameworks project. - */ - public static boolean isAospFrameworksProject(@NonNull File dir) { - String top = getAospTop(); - if (top != null) { - File toCompare = new File(top, "frameworks" //$NON-NLS-1$ - + File.separator + "base" //$NON-NLS-1$ - + File.separator + "core"); //$NON-NLS-1$ - try { - return dir.getCanonicalFile().equals(toCompare) && dir.exists(); - } catch (IOException e) { - return false; - } - } else { - return false; - } - } - - /** Get the root AOSP dir, if any */ - private static String getAospTop() { - return System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$ - } - - /** Get the host out directory in AOSP, if any */ - private static String getAospHostOut() { - return System.getenv("ANDROID_HOST_OUT"); //$NON-NLS-1$ - } - - /** Get the product out directory in AOSP, if any */ - private static String getAospProductOut() { - return System.getenv("ANDROID_PRODUCT_OUT"); //$NON-NLS-1$ - } - - private List<File> getAospJavaSourcePath() { - List<File> sources = new ArrayList<File>(2); - // Normal sources - File src = new File(mDir, "src"); //$NON-NLS-1$ - if (src.exists()) { - sources.add(src); - } - - // Generates sources - for (File dir : getIntermediateDirs()) { - File classes = new File(dir, "src"); //$NON-NLS-1$ - if (classes.exists()) { - sources.add(classes); - } - } - - if (sources.isEmpty()) { - mClient.log(null, - "Warning: Could not find sources or generated sources for project %1$s", - getName()); - } - - return sources; - } - - private List<File> getAospJavaClassPath() { - List<File> classDirs = new ArrayList<File>(1); - - for (File dir : getIntermediateDirs()) { - File classes = new File(dir, "classes"); //$NON-NLS-1$ - if (classes.exists()) { - classDirs.add(classes); - } else { - classes = new File(dir, "classes.jar"); //$NON-NLS-1$ - if (classes.exists()) { - classDirs.add(classes); - } - } - } - - if (classDirs.isEmpty()) { - mClient.log(null, - "No bytecode found: Has the project been built? (%1$s)", getName()); - } - - return classDirs; - } - - /** Find the _intermediates directories for a given module name */ - private List<File> getIntermediateDirs() { - // See build/core/definitions.mk and in particular the "intermediates-dir-for" definition - List<File> intermediates = new ArrayList<File>(); - - // TODO: Look up the module name, e.g. LOCAL_MODULE. However, - // some Android.mk files do some complicated things with it - and most - // projects use the same module name as the directory name. - String moduleName = mDir.getName(); - try { - // Get the actual directory name instead of '.' that's possible - // when using this via CLI. - moduleName = mDir.getCanonicalFile().getName(); - } catch (IOException ioe) { - // pass - } - - String top = getAospTop(); - final String[] outFolders = new String[] { - top + "/out/host/common/obj", //$NON-NLS-1$ - top + "/out/target/common/obj", //$NON-NLS-1$ - getAospHostOut() + "/obj", //$NON-NLS-1$ - getAospProductOut() + "/obj" //$NON-NLS-1$ - }; - final String[] moduleClasses = new String[] { - "APPS", //$NON-NLS-1$ - "JAVA_LIBRARIES", //$NON-NLS-1$ - }; - - for (String out : outFolders) { - assert new File(out.replace('/', File.separatorChar)).exists() : out; - for (String moduleClass : moduleClasses) { - String path = out + '/' + moduleClass + '/' + moduleName - + "_intermediates"; //$NON-NLS-1$ - File file = new File(path.replace('/', File.separatorChar)); - if (file.exists()) { - intermediates.add(file); - } - } - } - - return intermediates; - } - - private void extractAospMinSdkVersion() { - // Is the SDK level specified by a Makefile? - boolean found = false; - File makefile = new File(mDir, "Android.mk"); //$NON-NLS-1$ - if (makefile.exists()) { - try { - List<String> lines = Files.readLines(makefile, Charsets.UTF_8); - Pattern p = Pattern.compile("LOCAL_SDK_VERSION\\s*:=\\s*(.*)"); //$NON-NLS-1$ - for (String line : lines) { - line = line.trim(); - Matcher matcher = p.matcher(line); - if (matcher.matches()) { - found = true; - String version = matcher.group(1); - if (version.equals("current")) { //$NON-NLS-1$ - mManifestMinSdk = findCurrentAospVersion(); - } else { - mManifestMinSdk = SdkVersionInfo.getVersion(version, - mClient.getTargets()); - } - break; - } - } - } catch (IOException ioe) { - mClient.log(ioe, null); - } - } - - if (!found) { - mManifestMinSdk = findCurrentAospVersion(); - } - } - - /** Cache for {@link #findCurrentAospVersion()} */ - private static AndroidVersion sCurrentVersion; - - /** In an AOSP build environment, identify the currently built image version, if available */ - private static AndroidVersion findCurrentAospVersion() { - if (sCurrentVersion == null) { - File versionMk = new File(getAospTop(), "build/core/version_defaults.mk" //$NON-NLS-1$ - .replace('/', File.separatorChar)); - - if (!versionMk.exists()) { - sCurrentVersion = AndroidVersion.DEFAULT; - return sCurrentVersion; - } - int sdkVersion = LOWEST_ACTIVE_API; - try { - Pattern p = Pattern.compile("PLATFORM_SDK_VERSION\\s*:=\\s*(.*)"); - List<String> lines = Files.readLines(versionMk, Charsets.UTF_8); - for (String line : lines) { - line = line.trim(); - Matcher matcher = p.matcher(line); - if (matcher.matches()) { - String version = matcher.group(1); - try { - sdkVersion = Integer.parseInt(version); - } catch (NumberFormatException nfe) { - // pass - } - break; - } - } - } catch (IOException io) { - // pass - } - sCurrentVersion = new AndroidVersion(sdkVersion, null); - } - - return sCurrentVersion; - } - - /** - * Returns true if this project depends on the given artifact. Note that - * the project doesn't have to be a Gradle project; the artifact is just - * an identifier for name a specific library, such as com.android.support:support-v4 - * to identify the support library - * - * @param artifact the Gradle/Maven name of a library - * @return true if the library is installed, false if it is not, and null if - * we're not sure - */ - @Nullable - public Boolean dependsOn(@NonNull String artifact) { - if (SUPPORT_LIB_ARTIFACT.equals(artifact)) { - if (mSupportLib == null) { - for (File file : getJavaLibraries(true)) { - String name = file.getName(); - if (name.equals("android-support-v4.jar") //$NON-NLS-1$ - || name.startsWith("support-v4-")) { //$NON-NLS-1$ - mSupportLib = true; - break; - } - } - if (mSupportLib == null) { - for (Project dependency : getDirectLibraries()) { - Boolean b = dependency.dependsOn(artifact); - if (b != null && b) { - mSupportLib = true; - break; - } - } - } - if (mSupportLib == null) { - mSupportLib = false; - } - } - - return mSupportLib; - } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) { - if (mAppCompat == null) { - for (File file : getJavaLibraries(true)) { - String name = file.getName(); - if (name.startsWith("appcompat-v7-")) { //$NON-NLS-1$ - mAppCompat = true; - break; - } - } - if (mAppCompat == null) { - for (Project dependency : getDirectLibraries()) { - Boolean b = dependency.dependsOn(artifact); - if (b != null && b) { - mAppCompat = true; - break; - } - } - } - if (mAppCompat == null) { - mAppCompat = false; - } - } - - return mAppCompat; - } - - return null; - } - - private List<String> mCachedApplicableDensities; - - /** - * Returns the set of applicable densities for this project. If null, there are no density - * restrictions and all densities apply. - * - * @return the list of specific densities that apply in this project, or null if all densities - * apply - */ - @Nullable - public List<String> getApplicableDensities() { - if (mCachedApplicableDensities == null) { - // Use the gradle API to set up relevant densities. For example, if the - // build.gradle file contains this: - // android { - // defaultConfig { - // resConfigs "nodpi", "hdpi" - // } - // } - // ...then we should only enforce hdpi densities, not all these others! - if (isGradleProject() && getGradleProjectModel() != null && - getCurrentVariant() != null) { - Set<String> relevantDensities = Sets.newHashSet(); - Variant variant = getCurrentVariant(); - List<String> variantFlavors = variant.getProductFlavors(); - AndroidProject gradleProjectModel = getGradleProjectModel(); - - addResConfigsFromFlavor(relevantDensities, null, - getGradleProjectModel().getDefaultConfig()); - for (ProductFlavorContainer container : gradleProjectModel.getProductFlavors()) { - addResConfigsFromFlavor(relevantDensities, variantFlavors, container); - } - - // Are there any splits that specify densities? - if (relevantDensities.isEmpty()) { - AndroidArtifact mainArtifact = variant.getMainArtifact(); - Collection<AndroidArtifactOutput> outputs = mainArtifact.getOutputs(); - for (AndroidArtifactOutput output : outputs) { - for (OutputFile file : output.getOutputs()) { - final String DENSITY_NAME = OutputFile.FilterType.DENSITY.name(); - if (file.getFilterTypes().contains(DENSITY_NAME)) { - for (FilterData data : file.getFilters()) { - if (DENSITY_NAME.equals(data.getFilterType())) { - relevantDensities.add(data.getIdentifier()); - } - } - } - } - } - } - - if (!relevantDensities.isEmpty()) { - mCachedApplicableDensities = Lists.newArrayListWithExpectedSize(10); - for (String density : relevantDensities) { - String folder = ResourceFolderType.DRAWABLE.getName() + '-' + density; - mCachedApplicableDensities.add(folder); - } - Collections.sort(mCachedApplicableDensities); - } else { - mCachedApplicableDensities = Collections.emptyList(); - } - } else { - mCachedApplicableDensities = Collections.emptyList(); - } - } - - return mCachedApplicableDensities.isEmpty() ? null : mCachedApplicableDensities; - } - - /** - * Returns a super class map for this project. The keys and values are internal - * class names (e.g. java/lang/Integer, not java.lang.Integer). - * @return a map, possibly empty but never null - */ - @NonNull - public Map<String, String> getSuperClassMap() { - if (mSuperClassMap == null) { - mSuperClassMap = mClient.createSuperClassMap(this); - } - - return mSuperClassMap; - } - - /** - * Adds in the resConfig values specified by the given flavor container, assuming - * it's in one of the relevant variantFlavors, into the given set - */ - private static void addResConfigsFromFlavor(@NonNull Set<String> relevantDensities, - @Nullable List<String> variantFlavors, - @NonNull ProductFlavorContainer container) { - ProductFlavor flavor = container.getProductFlavor(); - if (variantFlavors == null || variantFlavors.contains(flavor.getName())) { - if (!flavor.getResourceConfigurations().isEmpty()) { - for (String densityName : flavor.getResourceConfigurations()) { - Density density = Density.getEnum(densityName); - if (density != null && density.isRecommended() - && density != Density.NODPI && density != Density.ANYDPI) { - relevantDensities.add(densityName); - } - } - } - } - } - - /** - * Returns a shared {@link ResourceVisibilityLookup} - * - * @return a shared provider for looking up resource visibility - */ - @NonNull - public ResourceVisibilityLookup getResourceVisibility() { - if (mResourceVisibility == null) { - if (isGradleProject()) { - AndroidProject project = getGradleProjectModel(); - Variant variant = getCurrentVariant(); - if (project != null && variant != null) { - mResourceVisibility = mClient.getResourceVisibilityProvider().get(project, - variant); - - } else if (getGradleLibraryModel() != null) { - try { - mResourceVisibility = mClient.getResourceVisibilityProvider() - .get(getGradleLibraryModel()); - } catch (Exception ignore) { - // Handle talking to older Gradle plugins (where we don't - // have access to the model version to check up front - } - } - } - if (mResourceVisibility == null) { - mResourceVisibility = ResourceVisibilityLookup.NONE; - } - } - - return mResourceVisibility; - } - - /** - * Returns the associated client - * - * @return the client - */ - @NonNull - public LintClient getClient() { - return mClient; - } - - /** - * Returns the compile target to use for this project - * - * @return the compile target to use to build this project - */ - @Nullable - public IAndroidTarget getCompileTarget() { - return mClient.getCompileTarget(this); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceContext.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceContext.java deleted file mode 100644 index 33fd684..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceContext.java +++ /dev/null
@@ -1,80 +0,0 @@ -/* - * 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 com.android.tools.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceFolderType; -import com.android.tools.klint.client.api.LintDriver; -import com.google.common.annotations.Beta; - -import java.io.File; - -/** - * A {@link com.android.tools.lint.detector.api.Context} used when checking resource files - * (both bitmaps and XML files; for XML files a subclass of this context is used: - * {@link com.android.tools.lint.detector.api.XmlContext}.) - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class ResourceContext extends Context { - private final ResourceFolderType mFolderType; - - /** - * Construct a new {@link com.android.tools.lint.detector.api.ResourceContext} - * - * @param driver the driver running through the checks - * @param project the project containing the file being checked - * @param main the main project if this project is a library project, or - * null if this is not a library project. The main project is - * the root project of all library projects, not necessarily the - * directly including project. - * @param file the file being checked - * @param folderType the {@link com.android.resources.ResourceFolderType} of this file, if any - */ - public ResourceContext( - @NonNull LintDriver driver, - @NonNull Project project, - @Nullable Project main, - @NonNull File file, - @Nullable ResourceFolderType folderType) { - super(driver, project, main, file); - mFolderType = folderType; - } - - /** - * Returns the resource folder type of this XML file, if any. - * - * @return the resource folder type or null - */ - @Nullable - public ResourceFolderType getResourceFolderType() { - return mFolderType; - } - - /** - * Returns the folder version. For example, for the file values-v14/foo.xml, - * it returns 14. - * - * @return the folder version, or -1 if no specific version was specified - */ - public int getFolderVersion() { - return mDriver.getResourceFolderVersion(file); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceEvaluator.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceEvaluator.java deleted file mode 100644 index 447731c..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceEvaluator.java +++ /dev/null
@@ -1,690 +0,0 @@ -/* - * 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.klint.detector.api; - -import static com.android.SdkConstants.ANDROID_PKG; -import static com.android.SdkConstants.ANDROID_PKG_PREFIX; -import static com.android.SdkConstants.CLASS_CONTEXT; -import static com.android.SdkConstants.CLASS_FRAGMENT; -import static com.android.SdkConstants.CLASS_RESOURCES; -import static com.android.SdkConstants.CLASS_V4_FRAGMENT; -import static com.android.SdkConstants.R_CLASS; -import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX; -import static com.android.tools.klint.client.api.UastLintUtils.toAndroidReferenceViaResolve; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.resources.ResourceUrl; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.AndroidReference; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.UastLintUtils; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAssignmentExpression; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiConditionalExpression; -import com.intellij.psi.PsiDeclarationStatement; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiExpressionStatement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiLocalVariable; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiModifierListOwner; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiParenthesizedExpression; -import com.intellij.psi.PsiReference; -import com.intellij.psi.PsiReferenceExpression; -import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiVariable; -import com.intellij.psi.util.PsiTreeUtil; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UIfExpression; -import org.jetbrains.uast.UParenthesizedExpression; -import org.jetbrains.uast.UQualifiedReferenceExpression; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.UReferenceExpression; - -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; - -/** Evaluates constant expressions */ -public class ResourceEvaluator { - - /** - * Marker ResourceType used to signify that an expression is of type {@code @ColorInt}, - * which isn't actually a ResourceType but one we want to specifically compare with. - * We're using {@link ResourceType#PUBLIC} because that one won't appear in the R - * class (and ResourceType is an enum we can't just create new constants for.) - */ - public static final ResourceType COLOR_INT_MARKER_TYPE = ResourceType.PUBLIC; - /** - * Marker ResourceType used to signify that an expression is of type {@code @Px}, - * which isn't actually a ResourceType but one we want to specifically compare with. - * We're using {@link ResourceType#DECLARE_STYLEABLE} because that one doesn't - * have a corresponding {@code *Res} constant (and ResourceType is an enum we can't - * just create new constants for.) - */ - public static final ResourceType PX_MARKER_TYPE = ResourceType.DECLARE_STYLEABLE; - - public static final String COLOR_INT_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "ColorInt"; //$NON-NLS-1$ - public static final String PX_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "Px"; //$NON-NLS-1$ - public static final String RES_SUFFIX = "Res"; - - public static final String CLS_TYPED_ARRAY = "android.content.res.TypedArray"; - - private final JavaContext mContext; - private final JavaEvaluator mEvaluator; - - private boolean mAllowDereference = true; - - /** - * Creates a new resource evaluator - * - * @param context Java context - */ - public ResourceEvaluator(JavaContext context) { - mContext = context; - mEvaluator = context.getEvaluator(); - } - - /** - * Whether we allow dereferencing resources when computing constants; - * e.g. if we ask for the resource for {@code x} when the code is - * {@code x = getString(R.string.name)}, if {@code allowDereference} is - * true we'll return R.string.name, otherwise we'll return null. - * - * @return this for constructor chaining - */ - public ResourceEvaluator allowDereference(boolean allow) { - mAllowDereference = allow; - return this; - } - - /** - * Evaluates the given node and returns the resource reference (type and name) it - * points to, if any - * - * @param context Java context - * @param element the node to compute the constant value for - * @return the corresponding resource url (type and name) - */ - @Nullable - public static ResourceUrl getResource( - @NonNull JavaContext context, - @NonNull PsiElement element) { - return new ResourceEvaluator(context).getResource(element); - } - - /** - * Evaluates the given node and returns the resource reference (type and name) it - * points to, if any - * - * @param context Java context - * @param element the node to compute the constant value for - * @return the corresponding resource url (type and name) - */ - @Nullable - public static ResourceUrl getResource( - @NonNull JavaContext context, - @NonNull UElement element) { - return new ResourceEvaluator(context).getResource(element); - } - - /** - * Evaluates the given node and returns the resource types implied by the given element, - * if any. - * - * @param context Java context - * @param element the node to compute the constant value for - * @return the corresponding resource types - */ - @Nullable - public static EnumSet<ResourceType> getResourceTypes( - @NonNull JavaContext context, - @NonNull PsiElement element) { - return new ResourceEvaluator(context).getResourceTypes(element); - } - - /** - * Evaluates the given node and returns the resource types implied by the given element, - * if any. - * - * @param context Java context - * @param element the node to compute the constant value for - * @return the corresponding resource types - */ - @Nullable - public static EnumSet<ResourceType> getResourceTypes( - @NonNull JavaContext context, - @NonNull UElement element) { - return new ResourceEvaluator(context).getResourceTypes(element); - } - - /** - * Evaluates the given node and returns the resource reference (type and name) it - * points to, if any - * - * @param element the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - */ - @Nullable - public ResourceUrl getResource(@Nullable UElement element) { - if (element == null) { - return null; - } - - if (element instanceof UIfExpression) { - UIfExpression expression = (UIfExpression) element; - Object known = ConstantEvaluator.evaluate(null, expression.getCondition()); - if (known == Boolean.TRUE && expression.getThenExpression() != null) { - return getResource(expression.getThenExpression()); - } else if (known == Boolean.FALSE && expression.getElseExpression() != null) { - return getResource(expression.getElseExpression()); - } - } else if (element instanceof UParenthesizedExpression) { - UParenthesizedExpression parenthesizedExpression = (UParenthesizedExpression) element; - return getResource(parenthesizedExpression.getExpression()); - } else if (mAllowDereference && element instanceof UQualifiedReferenceExpression) { - UQualifiedReferenceExpression qualifiedExpression = (UQualifiedReferenceExpression) element; - UExpression selector = qualifiedExpression.getSelector(); - if ((selector instanceof UCallExpression)) { - UCallExpression call = (UCallExpression) selector; - PsiMethod function = call.resolve(); - PsiClass containingClass = UastUtils.getContainingClass(function); - if (function != null && containingClass != null) { - String qualifiedName = containingClass.getQualifiedName(); - String name = call.getMethodName(); - if ((CLASS_RESOURCES.equals(qualifiedName) - || CLASS_CONTEXT.equals(qualifiedName) - || CLASS_FRAGMENT.equals(qualifiedName) - || CLASS_V4_FRAGMENT.equals(qualifiedName) - || CLS_TYPED_ARRAY.equals(qualifiedName)) - && name != null - && name.startsWith("get")) { - List<UExpression> args = call.getValueArguments(); - if (!args.isEmpty()) { - return getResource(args.get(0)); - } - } - } - } - } - - if (element instanceof UReferenceExpression) { - ResourceUrl url = getResourceConstant(element); - if (url != null) { - return url; - } - PsiElement resolved = ((UReferenceExpression) element).resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - UElement lastAssignment = - UastLintUtils.findLastAssignment( - variable, element, mContext); - - if (lastAssignment != null) { - return getResource(lastAssignment); - } - - return null; - } - } - - return null; - } - - /** - * Evaluates the given node and returns the resource reference (type and name) it - * points to, if any - * - * @param element the node to compute the constant value for - * @return the corresponding constant value - a String, an Integer, a Float, and so on - */ - - @Nullable - public ResourceUrl getResource(@Nullable PsiElement element) { - if (element == null) { - return null; - } - if (element instanceof PsiConditionalExpression) { - PsiConditionalExpression expression = (PsiConditionalExpression) element; - Object known = ConstantEvaluator.evaluate(null, expression.getCondition()); - if (known == Boolean.TRUE && expression.getThenExpression() != null) { - return getResource(expression.getThenExpression()); - } else if (known == Boolean.FALSE && expression.getElseExpression() != null) { - return getResource(expression.getElseExpression()); - } - } else if (element instanceof PsiParenthesizedExpression) { - PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) element; - return getResource(parenthesizedExpression.getExpression()); - } else if (element instanceof PsiMethodCallExpression && mAllowDereference) { - PsiMethodCallExpression call = (PsiMethodCallExpression) element; - PsiReferenceExpression expression = call.getMethodExpression(); - PsiMethod method = call.resolveMethod(); - if (method != null && method.getContainingClass() != null) { - String qualifiedName = method.getContainingClass().getQualifiedName(); - String name = expression.getReferenceName(); - if ((CLASS_RESOURCES.equals(qualifiedName) - || CLASS_CONTEXT.equals(qualifiedName) - || CLASS_FRAGMENT.equals(qualifiedName) - || CLASS_V4_FRAGMENT.equals(qualifiedName) - || CLS_TYPED_ARRAY.equals(qualifiedName)) - && name != null - && name.startsWith("get")) { - PsiExpression[] args = call.getArgumentList().getExpressions(); - if (args.length > 0) { - return getResource(args[0]); - } - } - } - } else if (element instanceof PsiReference) { - ResourceUrl url = getResourceConstant(element); - if (url != null) { - return url; - } - PsiElement resolved = ((PsiReference) element).resolve(); - if (resolved instanceof PsiField) { - url = getResourceConstant(resolved); - if (url != null) { - return url; - } - PsiField field = (PsiField) resolved; - if (field.getInitializer() != null) { - return getResource(field.getInitializer()); - } - return null; - } else if (resolved instanceof PsiLocalVariable) { - PsiLocalVariable variable = (PsiLocalVariable) resolved; - PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class, - false); - if (statement != null) { - PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, - PsiStatement.class); - String targetName = variable.getName(); - if (targetName == null) { - return null; - } - while (prev != null) { - if (prev instanceof PsiDeclarationStatement) { - PsiDeclarationStatement prevStatement = (PsiDeclarationStatement) prev; - for (PsiElement e : prevStatement.getDeclaredElements()) { - if (variable.equals(e)) { - return getResource(variable.getInitializer()); - } - } - } else if (prev instanceof PsiExpressionStatement) { - PsiExpression expression = ((PsiExpressionStatement) prev) - .getExpression(); - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assign - = (PsiAssignmentExpression) expression; - PsiExpression lhs = assign.getLExpression(); - if (lhs instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) lhs; - if (targetName.equals(reference.getReferenceName()) && - reference.getQualifier() == null) { - return getResource(assign.getRExpression()); - } - } - } - } - prev = PsiTreeUtil.getPrevSiblingOfType(prev, - PsiStatement.class); - } - } - } - } - - return null; - } - - /** - * Evaluates the given node and returns the resource types applicable to the - * node, if any. - * - * @param element the element to compute the types for - * @return the corresponding resource types - */ - @Nullable - public EnumSet<ResourceType> getResourceTypes(@Nullable UElement element) { - if (element == null) { - return null; - } - if (element instanceof UIfExpression) { - UIfExpression expression = (UIfExpression) element; - Object known = ConstantEvaluator.evaluate(null, expression.getCondition()); - if (known == Boolean.TRUE && expression.getThenExpression() != null) { - return getResourceTypes(expression.getThenExpression()); - } else if (known == Boolean.FALSE && expression.getElseExpression() != null) { - return getResourceTypes(expression.getElseExpression()); - } else { - EnumSet<ResourceType> left = getResourceTypes( - expression.getThenExpression()); - EnumSet<ResourceType> right = getResourceTypes( - expression.getElseExpression()); - if (left == null) { - return right; - } else if (right == null) { - return left; - } else { - EnumSet<ResourceType> copy = EnumSet.copyOf(left); - copy.addAll(right); - return copy; - } - } - } else if (element instanceof UParenthesizedExpression) { - UParenthesizedExpression parenthesizedExpression = (UParenthesizedExpression) element; - return getResourceTypes(parenthesizedExpression.getExpression()); - } else if ((element instanceof UQualifiedReferenceExpression && mAllowDereference) - || element instanceof UCallExpression) { - UElement probablyCallExpression = element; - if (element instanceof UQualifiedReferenceExpression) { - UQualifiedReferenceExpression qualifiedExpression = - (UQualifiedReferenceExpression) element; - probablyCallExpression = qualifiedExpression.getSelector(); - } - if ((probablyCallExpression instanceof UCallExpression)) { - UCallExpression call = (UCallExpression) probablyCallExpression; - PsiMethod method = call.resolve(); - PsiClass containingClass = UastUtils.getContainingClass(method); - if (method != null && containingClass != null) { - EnumSet<ResourceType> types = getTypesFromAnnotations(method); - if (types != null) { - return types; - } - - String qualifiedName = containingClass.getQualifiedName(); - String name = call.getMethodName(); - if ((CLASS_RESOURCES.equals(qualifiedName) - || CLASS_CONTEXT.equals(qualifiedName) - || CLASS_FRAGMENT.equals(qualifiedName) - || CLASS_V4_FRAGMENT.equals(qualifiedName) - || CLS_TYPED_ARRAY.equals(qualifiedName)) - && name != null - && name.startsWith("get")) { - List<UExpression> args = call.getValueArguments(); - if (!args.isEmpty()) { - types = getResourceTypes(args.get(0)); - if (types != null) { - return types; - } - } - } - } - } - } - - if (element instanceof UReferenceExpression) { - ResourceUrl url = getResourceConstant(element); - if (url != null) { - return EnumSet.of(url.type); - } - - PsiElement resolved = ((UReferenceExpression) element).resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - UElement lastAssignment = - UastLintUtils.findLastAssignment(variable, element, mContext); - - if (lastAssignment != null) { - return getResourceTypes(lastAssignment); - } - - return null; - } - } - - return null; - } - - /** - * Evaluates the given node and returns the resource types applicable to the - * node, if any. - * - * @param element the element to compute the types for - * @return the corresponding resource types - */ - @Nullable - public EnumSet<ResourceType> getResourceTypes(@Nullable PsiElement element) { - if (element == null) { - return null; - } - if (element instanceof PsiConditionalExpression) { - PsiConditionalExpression expression = (PsiConditionalExpression) element; - Object known = ConstantEvaluator.evaluate(null, expression.getCondition()); - if (known == Boolean.TRUE && expression.getThenExpression() != null) { - return getResourceTypes(expression.getThenExpression()); - } else if (known == Boolean.FALSE && expression.getElseExpression() != null) { - return getResourceTypes(expression.getElseExpression()); - } else { - EnumSet<ResourceType> left = getResourceTypes( - expression.getThenExpression()); - EnumSet<ResourceType> right = getResourceTypes( - expression.getElseExpression()); - if (left == null) { - return right; - } else if (right == null) { - return left; - } else { - EnumSet<ResourceType> copy = EnumSet.copyOf(left); - copy.addAll(right); - return copy; - } - } - } else if (element instanceof PsiParenthesizedExpression) { - PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) element; - return getResourceTypes(parenthesizedExpression.getExpression()); - } else if (element instanceof PsiMethodCallExpression && mAllowDereference) { - PsiMethodCallExpression call = (PsiMethodCallExpression) element; - PsiReferenceExpression expression = call.getMethodExpression(); - PsiMethod method = call.resolveMethod(); - if (method != null && method.getContainingClass() != null) { - EnumSet<ResourceType> types = getTypesFromAnnotations(method); - if (types != null) { - return types; - } - - String qualifiedName = method.getContainingClass().getQualifiedName(); - String name = expression.getReferenceName(); - if ((CLASS_RESOURCES.equals(qualifiedName) - || CLASS_CONTEXT.equals(qualifiedName) - || CLASS_FRAGMENT.equals(qualifiedName) - || CLASS_V4_FRAGMENT.equals(qualifiedName) - || CLS_TYPED_ARRAY.equals(qualifiedName)) - && name != null - && name.startsWith("get")) { - PsiExpression[] args = call.getArgumentList().getExpressions(); - if (args.length > 0) { - types = getResourceTypes(args[0]); - if (types != null) { - return types; - } - } - } - } - } else if (element instanceof PsiReference) { - ResourceUrl url = getResourceConstant(element); - if (url != null) { - return EnumSet.of(url.type); - } - PsiElement resolved = ((PsiReference) element).resolve(); - if (resolved instanceof PsiField) { - url = getResourceConstant(resolved); - if (url != null) { - return EnumSet.of(url.type); - } - PsiField field = (PsiField) resolved; - if (field.getInitializer() != null) { - return getResourceTypes(field.getInitializer()); - } - return null; - } else if (resolved instanceof PsiParameter) { - return getTypesFromAnnotations((PsiParameter)resolved); - } else if (resolved instanceof PsiLocalVariable) { - PsiLocalVariable variable = (PsiLocalVariable) resolved; - PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class, - false); - if (statement != null) { - PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, - PsiStatement.class); - String targetName = variable.getName(); - if (targetName == null) { - return null; - } - while (prev != null) { - if (prev instanceof PsiDeclarationStatement) { - PsiDeclarationStatement prevStatement = (PsiDeclarationStatement) prev; - for (PsiElement e : prevStatement.getDeclaredElements()) { - if (variable.equals(e)) { - return getResourceTypes(variable.getInitializer()); - } - } - } else if (prev instanceof PsiExpressionStatement) { - PsiExpression expression = ((PsiExpressionStatement) prev) - .getExpression(); - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assign - = (PsiAssignmentExpression) expression; - PsiExpression lhs = assign.getLExpression(); - if (lhs instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) lhs; - if (targetName.equals(reference.getReferenceName()) && - reference.getQualifier() == null) { - return getResourceTypes(assign.getRExpression()); - } - } - } - } - prev = PsiTreeUtil.getPrevSiblingOfType(prev, - PsiStatement.class); - } - } - } - } - - return null; - } - - @Nullable - private EnumSet<ResourceType> getTypesFromAnnotations(PsiModifierListOwner owner) { - if (mEvaluator == null) { - return null; - } - for (PsiAnnotation annotation : mEvaluator.getAllAnnotations(owner)) { - String signature = annotation.getQualifiedName(); - if (signature == null) { - continue; - } - if (signature.equals(COLOR_INT_ANNOTATION)) { - return EnumSet.of(COLOR_INT_MARKER_TYPE); - } - if (signature.equals(PX_ANNOTATION)) { - return EnumSet.of(PX_MARKER_TYPE); - } - if (signature.endsWith(RES_SUFFIX) - && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) { - String typeString = signature - .substring(SUPPORT_ANNOTATIONS_PREFIX.length(), - signature.length() - RES_SUFFIX.length()) - .toLowerCase(Locale.US); - ResourceType type = ResourceType.getEnum(typeString); - if (type != null) { - return EnumSet.of(type); - } else if (typeString.equals("any")) { // @AnyRes - return getAnyRes(); - } - } - } - - return null; - } - - /** Returns a resource URL based on the field reference in the code */ - @Nullable - public static ResourceUrl getResourceConstant(@NonNull PsiElement node) { - // R.type.name - if (node instanceof PsiReferenceExpression) { - PsiReferenceExpression expression = (PsiReferenceExpression) node; - if (expression.getQualifier() instanceof PsiReferenceExpression) { - PsiReferenceExpression select = (PsiReferenceExpression) expression.getQualifier(); - if (select.getQualifier() instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) select - .getQualifier(); - if (R_CLASS.equals(reference.getReferenceName())) { - String typeName = select.getReferenceName(); - String name = expression.getReferenceName(); - - ResourceType type = ResourceType.getEnum(typeName); - if (type != null && name != null) { - boolean isFramework = - reference.getQualifier() instanceof PsiReferenceExpression - && ANDROID_PKG - .equals(((PsiReferenceExpression) reference. - getQualifier()).getReferenceName()); - - return ResourceUrl.create(type, name, isFramework, false); - } - } - } - } - } else if (node instanceof PsiField) { - PsiField field = (PsiField) node; - PsiClass typeClass = field.getContainingClass(); - if (typeClass != null) { - PsiClass rClass = typeClass.getContainingClass(); - if (rClass != null && R_CLASS.equals(rClass.getName())) { - String name = field.getName(); - ResourceType type = ResourceType.getEnum(typeClass.getName()); - if (type != null && name != null) { - String qualifiedName = rClass.getQualifiedName(); - boolean isFramework = qualifiedName != null - && qualifiedName.startsWith(ANDROID_PKG_PREFIX); - return ResourceUrl.create(type, name, isFramework, false); - } - } - } - } - return null; - } - - /** Returns a resource URL based on the field reference in the code */ - @Nullable - public static ResourceUrl getResourceConstant(@NonNull UElement node) { - AndroidReference androidReference = toAndroidReferenceViaResolve(node); - if (androidReference == null) { - return null; - } - - String name = androidReference.getName(); - ResourceType type = androidReference.getType(); - boolean isFramework = androidReference.getPackage().equals("android"); - - return ResourceUrl.create(type, name, isFramework, false); - } - - private static EnumSet<ResourceType> getAnyRes() { - EnumSet<ResourceType> types = EnumSet.allOf(ResourceType.class); - types.remove(ResourceEvaluator.COLOR_INT_MARKER_TYPE); - types.remove(ResourceEvaluator.PX_MARKER_TYPE); - return types; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceXmlDetector.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceXmlDetector.java deleted file mode 100644 index 85fdcfc..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceXmlDetector.java +++ /dev/null
@@ -1,61 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.resources.ResourceFolderType; -import com.google.common.annotations.Beta; - -import java.io.File; - -/** - * Specialized detector intended for XML resources. Detectors that apply to XML - * resources should extend this detector instead since it provides special - * iteration hooks that are more efficient. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public abstract class ResourceXmlDetector extends Detector implements Detector.XmlScanner { - @Override - public boolean appliesTo(@NonNull Context context, @NonNull File file) { - return LintUtils.isXmlFile(file); - } - - /** - * Returns whether this detector applies to the given folder type. This - * allows the detectors to be pruned from iteration, so for example when we - * are analyzing a string value file we don't need to look up detectors - * related to layout. - * - * @param folderType the folder type to be visited - * @return true if this detector can apply to resources in folders of the - * given type - */ - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return true; - } - - @Override - public void run(@NonNull Context context) { - // The infrastructure should never call this method on an xml detector since - // it will run the various visitors instead - assert false; - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Scope.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Scope.java deleted file mode 100644 index e8201cf..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Scope.java +++ /dev/null
@@ -1,265 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import static com.android.SdkConstants.ANDROID_MANIFEST_XML; -import static com.android.SdkConstants.DOT_CLASS; -import static com.android.SdkConstants.DOT_GRADLE; -import static com.android.SdkConstants.DOT_JAVA; -import static com.android.SdkConstants.DOT_PNG; -import static com.android.SdkConstants.DOT_PROPERTIES; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE; -import static com.android.SdkConstants.OLD_PROGUARD_FILE; -import static com.android.SdkConstants.RES_FOLDER; - -import com.android.annotations.NonNull; -import com.google.common.annotations.Beta; - -import java.io.File; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; - -/** - * The scope of a detector is the set of files a detector must consider when - * performing its analysis. This can be used to determine when issues are - * potentially obsolete, whether a detector should re-run on a file save, etc. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public enum Scope { - /** - * The analysis only considers a single XML resource file at a time. - * <p> - * Issues which are only affected by a single resource file can be checked - * for incrementally when a file is edited. - */ - RESOURCE_FILE, - - /** - * The analysis only considers a single binary (typically a bitmap) resource file at a time. - * <p> - * Issues which are only affected by a single resource file can be checked - * for incrementally when a file is edited. - */ - BINARY_RESOURCE_FILE, - - /** - * The analysis considers the resource folders (which also includes asset folders) - */ - RESOURCE_FOLDER, - - /** - * The analysis considers <b>all</b> the resource file. This scope must not - * be used in conjunction with {@link #RESOURCE_FILE}; an issue scope is - * either considering just a single resource file or all the resources, not - * both. - */ - ALL_RESOURCE_FILES, - - /** - * The analysis only considers a single Java source file at a time. - * <p> - * Issues which are only affected by a single Java source file can be - * checked for incrementally when a Java source file is edited. - */ - JAVA_FILE, - - /** - * The analysis considers <b>all</b> the Java source files together. - * <p> - * This flag is mutually exclusive with {@link #JAVA_FILE}. - */ - ALL_JAVA_FILES, - - /** - * The analysis only considers a single Java class file at a time. - * <p> - * Issues which are only affected by a single Java class file can be checked - * for incrementally when a Java source file is edited and then recompiled. - */ - CLASS_FILE, - - /** - * The analysis considers <b>all</b> the Java class files together. - * <p> - * This flag is mutually exclusive with {@link #CLASS_FILE}. - */ - ALL_CLASS_FILES, - - /** The analysis considers the manifest file */ - MANIFEST, - - /** The analysis considers the Proguard configuration file */ - PROGUARD_FILE, - - /** - * The analysis considers classes in the libraries for this project. These - * will be analyzed before the classes themselves. NOTE: This excludes - * provided libraries. - */ - JAVA_LIBRARIES, - - /** The analysis considers a Gradle build file */ - GRADLE_FILE, - - /** The analysis considers Java property files */ - PROPERTY_FILE, - - /** The analysis considers test sources as well */ - TEST_SOURCES, - - /** - * Scope for other files. Issues that specify a custom scope will be called unconditionally. - * This will call {@link Detector#run(Context)}} on the detectors unconditionally. - */ - OTHER; - - /** - * Returns true if the given scope set corresponds to scanning a single file - * rather than a whole project - * - * @param scopes the scope set to check - * @return true if the scope set references a single file - */ - public static boolean checkSingleFile(@NonNull EnumSet<Scope> scopes) { - int size = scopes.size(); - if (size == 2) { - // When single checking a Java source file, we check both its Java source - // and the associated class files - return scopes.contains(JAVA_FILE) && scopes.contains(CLASS_FILE); - } else { - return size == 1 && - (scopes.contains(JAVA_FILE) - || scopes.contains(CLASS_FILE) - || scopes.contains(RESOURCE_FILE) - || scopes.contains(PROGUARD_FILE) - || scopes.contains(PROPERTY_FILE) - || scopes.contains(GRADLE_FILE) - || scopes.contains(MANIFEST)); - } - } - - /** - * Returns the intersection of two scope sets - * - * @param scope1 the first set to intersect - * @param scope2 the second set to intersect - * @return the intersection of the two sets - */ - @NonNull - public static EnumSet<Scope> intersect( - @NonNull EnumSet<Scope> scope1, - @NonNull EnumSet<Scope> scope2) { - EnumSet<Scope> scope = EnumSet.copyOf(scope1); - scope.retainAll(scope2); - - return scope; - } - - /** - * Infers a suitable scope to use from the given projects to be analyzed - * @param projects the projects to find a suitable scope for - * @return the scope to use - */ - @NonNull - public static EnumSet<Scope> infer(@NonNull Collection<Project> projects) { - // Infer the scope - EnumSet<Scope> scope = EnumSet.noneOf(Scope.class); - for (Project project : projects) { - List<File> subset = project.getSubset(); - if (subset != null) { - for (File file : subset) { - String name = file.getName(); - if (name.equals(ANDROID_MANIFEST_XML)) { - scope.add(MANIFEST); - } else if (name.endsWith(DOT_XML)) { - scope.add(RESOURCE_FILE); - } else if (name.endsWith(".kt")) { - scope.add(JAVA_FILE); - } else if (name.endsWith(DOT_CLASS)) { - scope.add(CLASS_FILE); - } else if (name.endsWith(DOT_GRADLE)) { - scope.add(GRADLE_FILE); - } else if (name.equals(OLD_PROGUARD_FILE) - || name.equals(FN_PROJECT_PROGUARD_FILE)) { - scope.add(PROGUARD_FILE); - } else if (name.endsWith(DOT_PROPERTIES)) { - scope.add(PROPERTY_FILE); - } else if (name.endsWith(DOT_PNG)) { - scope.add(BINARY_RESOURCE_FILE); - } else if (name.equals(RES_FOLDER) - || file.getParent().equals(RES_FOLDER)) { - scope.add(ALL_RESOURCE_FILES); - scope.add(RESOURCE_FILE); - scope.add(BINARY_RESOURCE_FILE); - scope.add(RESOURCE_FOLDER); - } - } - } else { - // Specified a full project: just use the full project scope - scope = Scope.ALL; - break; - } - } - - return scope; - } - - /** All scopes: running lint on a project will check these scopes */ - public static final EnumSet<Scope> ALL = EnumSet.allOf(Scope.class); - /** Scope-set used for detectors which are affected by a single resource file */ - public static final EnumSet<Scope> RESOURCE_FILE_SCOPE = EnumSet.of(RESOURCE_FILE); - /** Scope-set used for detectors which are affected by a single resource folder */ - public static final EnumSet<Scope> RESOURCE_FOLDER_SCOPE = EnumSet.of(RESOURCE_FOLDER); - /** Scope-set used for detectors which scan all resources */ - public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES); - /** Scope-set used for detectors which are affected by a single Java source file */ - public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE); - /** Scope-set used for detectors which are affected by a single Java class file */ - public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE); - /** Scope-set used for detectors which are affected by a single Gradle build file */ - public static final EnumSet<Scope> GRADLE_SCOPE = EnumSet.of(GRADLE_FILE); - /** Scope-set used for detectors which are affected by the manifest only */ - public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST); - /** Scope-set used for detectors which correspond to some other context */ - public static final EnumSet<Scope> OTHER_SCOPE = EnumSet.of(OTHER); - /** Scope-set used for detectors which are affected by a single ProGuard class file */ - public static final EnumSet<Scope> PROGUARD_SCOPE = EnumSet.of(PROGUARD_FILE); - /** Scope-set used for detectors which correspond to property files */ - public static final EnumSet<Scope> PROPERTY_SCOPE = EnumSet.of(PROPERTY_FILE); - /** Resource XML files and manifest files */ - public static final EnumSet<Scope> MANIFEST_AND_RESOURCE_SCOPE = - EnumSet.of(Scope.MANIFEST, Scope.RESOURCE_FILE); - /** Scope-set used for detectors which are affected by single XML and Java source files */ - public static final EnumSet<Scope> JAVA_AND_RESOURCE_FILES = - EnumSet.of(RESOURCE_FILE, JAVA_FILE); - /** Scope-set used for analyzing individual class files and all resource files */ - public static final EnumSet<Scope> CLASS_AND_ALL_RESOURCE_FILES = - EnumSet.of(ALL_RESOURCE_FILES, CLASS_FILE); - /** Scope-set used for analyzing all class files, including those in libraries */ - public static final EnumSet<Scope> ALL_CLASSES_AND_LIBRARIES = - EnumSet.of(Scope.ALL_CLASS_FILES, Scope.JAVA_LIBRARIES); - /** Scope-set used for detectors which are affected by Java libraries */ - public static final EnumSet<Scope> JAVA_LIBRARY_SCOPE = EnumSet.of(JAVA_LIBRARIES); - /** Scope-set used for detectors which are affected by a single binary resource file */ - public static final EnumSet<Scope> BINARY_RESOURCE_FILE_SCOPE = - EnumSet.of(BINARY_RESOURCE_FILE); -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Severity.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Severity.java deleted file mode 100644 index c1de962..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Severity.java +++ /dev/null
@@ -1,103 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.google.common.annotations.Beta; - -/** - * Severity of an issue found by lint - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public enum Severity { - /** - * Fatal: Use sparingly because a warning marked as fatal will be - * considered critical and will abort Export APK etc in ADT - */ - @NonNull - FATAL("Fatal"), - - /** - * Errors: The issue is known to be a real error that must be addressed. - */ - @NonNull - ERROR("Error"), - - /** - * Warning: Probably a problem. - */ - @NonNull - WARNING("Warning"), - - /** - * Information only: Might not be a problem, but the check has found - * something interesting to say about the code. - */ - @NonNull - INFORMATIONAL("Information"), - - /** - * Ignore: The user doesn't want to see this issue - */ - @NonNull - IGNORE("Ignore"); - - @NonNull - private final String mDisplay; - - Severity(@NonNull String display) { - mDisplay = display; - } - - /** - * Returns a description of this severity suitable for display to the user - * - * @return a description of the severity - */ - @NonNull - public String getDescription() { - return mDisplay; - } - - /** Returns the name of this severity */ - @NonNull - public String getName() { - return name(); - } - - /** - * Looks up the severity corresponding to a given named severity. The severity - * string should be one returned by {@link #toString()} - * - * @param name the name to look up - * @return the corresponding severity, or null if it is not a valid severity name - */ - @Nullable - public static Severity fromName(@NonNull String name) { - for (Severity severity : values()) { - if (severity.name().equalsIgnoreCase(name)) { - return severity; - } - } - - return null; - } -} \ No newline at end of file
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Speed.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Speed.java deleted file mode 100644 index b790a1a..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Speed.java +++ /dev/null
@@ -1,58 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.google.common.annotations.Beta; - -/** - * Enum which describes the different computation speeds of various detectors - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public enum Speed { - /** The detector can run very quickly */ - FAST("Fast"), - - /** The detector runs reasonably fast */ - NORMAL("Normal"), - - /** The detector might take a long time to run */ - SLOW("Slow"), - - /** The detector might take a huge amount of time to run */ - REALLY_SLOW("Really Slow"); - - private final String mDisplayName; - - Speed(@NonNull String displayName) { - mDisplayName = displayName; - } - - /** - * Returns the user-visible description of the speed of the given - * detector - * - * @return the description of the speed to display to the user - */ - @NonNull - public String getDisplayName() { - return mDisplayName; - } -} \ No newline at end of file
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TextFormat.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TextFormat.java deleted file mode 100644 index c081583..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TextFormat.java +++ /dev/null
@@ -1,405 +0,0 @@ -/* - * 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 com.android.tools.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.utils.SdkUtils; -import com.android.utils.XmlUtils; - -/** - * Lint error message, issue explanations and location descriptions - * are described in a {@link #RAW} format which looks similar to text - * but which can contain bold, symbols and links. These issues can - * also be converted to plain text and to HTML markup, using the - * {@link #convertTo(String, TextFormat)} method. - * - * @see Issue#getExplanation(TextFormat) - * @see Issue#getBriefDescription(TextFormat) - */ -public enum TextFormat { - /** - * Raw output format which is similar to text but allows some markup: - * <ul> - * <li>HTTP urls (http://...) - * <li>Sentences immediately surrounded by * will be shown as bold. - * <li>Sentences immediately surrounded by ` will be shown using monospace - * fonts - * </ul> - * Furthermore, newlines are converted to br's when converting newlines. - * Note: It does not insert {@code <html>} tags around the fragment for HTML output. - * <p> - * TODO: Consider switching to the restructured text format - - * http://docutils.sourceforge.net/docs/user/rst/quickstart.html - */ - RAW, - - /** - * Plain text output - */ - TEXT, - - /** - * HTML formatted output (note: does not include surrounding {@code <html></html>} tags) - */ - HTML, - - /** - * HTML formatted output (note: does not include surrounding {@code <html></html>} tags). - * This is like {@link #HTML}, but it does not escape unicode characters with entities. - * <p> - * (This is used for example in the IDE, where some partial HTML support in some - * label widgets support some HTML markup, but not numeric code character entities.) - */ - HTML_WITH_UNICODE; - - /** - * Converts the given text to HTML - * - * @param text the text to format - * @return the corresponding text formatted as HTML - */ - @NonNull - public String toHtml(@NonNull String text) { - return convertTo(text, HTML); - } - - /** - * Converts the given text to plain text - * - * @param text the tetx to format - * @return the corresponding text formatted as HTML - */ - @NonNull - public String toText(@NonNull String text) { - return convertTo(text, TEXT); - } - - /** - * Converts the given message to the given format. Note that some - * conversions are lossy; e.g. once converting away from the raw format - * (which contains all the markup) you can't convert back to it. - * Note that you can convert to the format it's already in; that just - * returns the same string. - * - * @param message the message to convert - * @param to the format to convert to - * @return a converted message - */ - public String convertTo(@NonNull String message, @NonNull TextFormat to) { - if (this == to) { - return message; - } - switch (this) { - case RAW: { - switch (to) { - case RAW: - return message; - case TEXT: - case HTML: - case HTML_WITH_UNICODE: - return to.fromRaw(message); - } - } - case TEXT: { - switch (to) { - case TEXT: - case RAW: - return message; - case HTML: - case HTML_WITH_UNICODE: - return XmlUtils.toXmlTextValue(message); - } - } - case HTML: { - switch (to) { - case HTML: - return message; - case HTML_WITH_UNICODE: - return removeNumericEntities(message); - case RAW: - case TEXT: { - return to.fromHtml(message); - - } - } - } - case HTML_WITH_UNICODE: { - switch (to) { - case HTML: - case HTML_WITH_UNICODE: - return message; - case RAW: - case TEXT: { - return to.fromHtml(message); - - } - } - } - } - return message; - } - - /** Converts to this output format from the given HTML-format text */ - @NonNull - private String fromHtml(@NonNull String html) { - assert this == RAW || this == TEXT : this; - - // Drop all tags; replace all entities, insert newlines - // (this won't do wrapping) - StringBuilder sb = new StringBuilder(html.length()); - boolean inPre = false; - for (int i = 0, n = html.length(); i < n; i++) { - char c = html.charAt(i); - if (c == '<') { - // Strip comments - if (html.startsWith("<!--", i)) { - int end = html.indexOf("-->", i); - if (end == -1) { - break; // Unclosed comment - } else { - i = end + 2; - } - continue; - } - // Tags: scan forward to the end - int begin; - boolean isEndTag = false; - if (html.startsWith("</", i)) { - begin = i + 2; - isEndTag = true; - } else { - begin = i + 1; - } - i = html.indexOf('>', i); - if (i == -1) { - // Unclosed tag - break; - } - int end = i; - if (html.charAt(i - 1) == '/') { - end--; - isEndTag = true; - } - // TODO: Handle <pre> such that we don't collapse spaces and reformat there! - // (We do need to strip out tags and expand entities) - String tag = html.substring(begin, end).trim(); - if (tag.equalsIgnoreCase("br")) { - sb.append('\n'); - } else if (tag.equalsIgnoreCase("p") // Most common block tags - || tag.equalsIgnoreCase("div") - || tag.equalsIgnoreCase("pre") - || tag.equalsIgnoreCase("blockquote") - || tag.equalsIgnoreCase("dl") - || tag.equalsIgnoreCase("dd") - || tag.equalsIgnoreCase("dt") - || tag.equalsIgnoreCase("ol") - || tag.equalsIgnoreCase("ul") - || tag.equalsIgnoreCase("li") - || tag.length() == 2 && tag.startsWith("h") - && Character.isDigit(tag.charAt(1))) { - // Block tag: ensure new line - if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '\n') { - sb.append('\n'); - } - if (tag.equals("li") && !isEndTag) { - sb.append("* "); - } - if (tag.equalsIgnoreCase("pre")) { - inPre = !isEndTag; - } - } - } else if (c == '&') { - int end = html.indexOf(';', i); - if (end > i) { - String entity = html.substring(i, end + 1); - String s = XmlUtils.fromXmlAttributeValue(entity); - if (s.startsWith("&")) { - // Not an XML entity; for example, - // Sadly Guava's HtmlEscapes don't handle this either. - if (entity.equalsIgnoreCase(" ")) { - s = " "; - } else if (entity.startsWith("&#")) { - try { - int value = Integer.parseInt(entity.substring(2)); - s = Character.toString((char)value); - } catch (NumberFormatException ignore) { - } - } - } - sb.append(s); - i = end; - } else { - sb.append(c); - } - } else if (Character.isWhitespace(c)) { - if (inPre) { - sb.append(c); - } else if (sb.length() == 0 - || !Character.isWhitespace(sb.charAt(sb.length() - 1))) { - sb.append(' '); - } - } else { - sb.append(c); - } - } - - String s = sb.toString(); - - // Line-wrap - s = SdkUtils.wrap(s, 60, null); - - return s; - } - - private static final String HTTP_PREFIX = "http://"; //$NON-NLS-1$ - - /** Converts to this output format from the given raw-format text */ - @NonNull - private String fromRaw(@NonNull String text) { - assert this == HTML || this == HTML_WITH_UNICODE || this == TEXT : this; - StringBuilder sb = new StringBuilder(3 * text.length() / 2); - boolean html = this == HTML || this == HTML_WITH_UNICODE; - boolean escapeUnicode = this == HTML; - - char prev = 0; - int flushIndex = 0; - int n = text.length(); - for (int i = 0; i < n; i++) { - char c = text.charAt(i); - if ((c == '*' || c == '`') && i < n - 1) { - // Scout ahead for range end - if (!Character.isLetterOrDigit(prev) - && !Character.isWhitespace(text.charAt(i + 1))) { - // Found * or ` immediately before a letter, and not in the middle of a word - // Find end - int end = text.indexOf(c, i + 1); - if (end != -1 && (end == n - 1 || !Character.isLetter(text.charAt(end + 1)))) { - if (i > flushIndex) { - appendEscapedText(sb, text, html, flushIndex, i, escapeUnicode); - } - if (html) { - String tag = c == '*' ? "b" : "code"; //$NON-NLS-1$ //$NON-NLS-2$ - sb.append('<').append(tag).append('>'); - appendEscapedText(sb, text, html, i + 1, end, escapeUnicode); - sb.append('<').append('/').append(tag).append('>'); - } else { - appendEscapedText(sb, text, html, i + 1, end, escapeUnicode); - } - flushIndex = end + 1; - i = flushIndex - 1; // -1: account for the i++ in the loop - } - } - } else if (html && c == 'h' && i < n - 1 && text.charAt(i + 1) == 't' - && text.startsWith(HTTP_PREFIX, i) && !Character.isLetterOrDigit(prev)) { - // Find url end - int end = i + HTTP_PREFIX.length(); - while (end < n) { - char d = text.charAt(end); - if (Character.isWhitespace(d)) { - break; - } - end++; - } - char last = text.charAt(end - 1); - if (last == '.' || last == ')' || last == '!') { - end--; - } - if (end > i + HTTP_PREFIX.length()) { - if (i > flushIndex) { - appendEscapedText(sb, text, html, flushIndex, i, escapeUnicode); - } - - String url = text.substring(i, end); - sb.append("<a href=\""); //$NON-NLS-1$ - sb.append(url); - sb.append('"').append('>'); - sb.append(url); - sb.append("</a>"); //$NON-NLS-1$ - - flushIndex = end; - i = flushIndex - 1; // -1: account for the i++ in the loop - } - } - prev = c; - } - - if (flushIndex < n) { - appendEscapedText(sb, text, html, flushIndex, n, escapeUnicode); - } - - return sb.toString(); - } - - private static String removeNumericEntities(@NonNull String html) { - if (!html.contains("&#")) { - return html; - } - - StringBuilder sb = new StringBuilder(html.length()); - for (int i = 0, n = html.length(); i < n; i++) { - char c = html.charAt(i); - if (c == '&' && i < n - 1 && html.charAt(i + 1) == '#') { - int end = html.indexOf(';', i + 2); - if (end != -1) { - String decimal = html.substring(i + 2, end); - try { - c = (char)Integer.parseInt(decimal); - sb.append(c); - i = end; - continue; - } catch (NumberFormatException ignore) { - // fall through to not escape this - } - } - } - sb.append(c); - } - - return sb.toString(); - } - - private static void appendEscapedText(@NonNull StringBuilder sb, @NonNull String text, - boolean html, int start, int end, boolean escapeUnicode) { - if (html) { - for (int i = start; i < end; i++) { - char c = text.charAt(i); - if (c == '<') { - sb.append("<"); //$NON-NLS-1$ - } else if (c == '&') { - sb.append("&"); //$NON-NLS-1$ - } else if (c == '\n') { - sb.append("<br/>\n"); - } else { - if (c > 255 && escapeUnicode) { - sb.append("&#"); //$NON-NLS-1$ - sb.append(Integer.toString(c)); - sb.append(';'); - } else if (c == '\u00a0') { - sb.append(" "); //$NON-NLS-1$ - } else { - sb.append(c); - } - } - } - } else { - for (int i = start; i < end; i++) { - char c = text.charAt(i); - sb.append(c); - } - } - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TypeEvaluator.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TypeEvaluator.java deleted file mode 100644 index 74afe26..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TypeEvaluator.java +++ /dev/null
@@ -1,417 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.detector.api; - -import static com.android.tools.klint.client.api.JavaParser.TYPE_BOOLEAN; -import static com.android.tools.klint.client.api.JavaParser.TYPE_CHAR; -import static com.android.tools.klint.client.api.JavaParser.TYPE_DOUBLE; -import static com.android.tools.klint.client.api.JavaParser.TYPE_FLOAT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_INT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_LONG; -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; -import static com.android.tools.klint.detector.api.JavaContext.getParentOfType; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaParser.DefaultTypeDescriptor; -import com.android.tools.klint.client.api.JavaParser.ResolvedClass; -import com.android.tools.klint.client.api.JavaParser.ResolvedField; -import com.android.tools.klint.client.api.JavaParser.ResolvedMethod; -import com.android.tools.klint.client.api.JavaParser.ResolvedNode; -import com.android.tools.klint.client.api.JavaParser.ResolvedVariable; -import com.android.tools.klint.client.api.JavaParser.TypeDescriptor; -import com.android.tools.klint.client.api.UastLintUtils; -import com.intellij.psi.PsiAssignmentExpression; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiDeclarationStatement; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiExpressionStatement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiLocalVariable; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiReference; -import com.intellij.psi.PsiReferenceExpression; -import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiType; -import com.intellij.psi.util.PsiTreeUtil; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UVariable; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.util.UastExpressionUtils; - -import java.util.ListIterator; - -import lombok.ast.BinaryExpression; -import lombok.ast.BinaryOperator; -import lombok.ast.BooleanLiteral; -import lombok.ast.Cast; -import lombok.ast.CharLiteral; -import lombok.ast.Expression; -import lombok.ast.ExpressionStatement; -import lombok.ast.FloatingPointLiteral; -import lombok.ast.InlineIfExpression; -import lombok.ast.IntegralLiteral; -import lombok.ast.Literal; -import lombok.ast.Node; -import lombok.ast.NullLiteral; -import lombok.ast.Statement; -import lombok.ast.StringLiteral; -import lombok.ast.UnaryExpression; -import lombok.ast.VariableDeclaration; -import lombok.ast.VariableDefinition; -import lombok.ast.VariableDefinitionEntry; -import lombok.ast.VariableReference; - -/** - * Evaluates the types of nodes. This goes deeper than - * {@link JavaContext#getType(Node)} in that it analyzes the - * flow and for example figures out that if you ask for the type of {@code var} - * in this code snippet: - * <pre> - * Object o = new StringBuilder(); - * Object var = o; - * </pre> - * it will return "java.lang.StringBuilder". - * <p> - * <b>NOTE:</b> This type evaluator does not (yet) compute the correct - * types when involving implicit type conversions, so be careful - * if using this for primitives; e.g. for "int * long" it might return - * the type "int". - */ -public class TypeEvaluator { - private final JavaContext mContext; - - /** - * Creates a new constant evaluator - * - * @param context the context to use to resolve field references, if any - */ - public TypeEvaluator(@Nullable JavaContext context) { - mContext = context; - } - - - /** - * Returns the inferred type of the given node - * @deprecated Use {@link #evaluate(PsiElement)} instead - */ - @Deprecated - @Nullable - public TypeDescriptor evaluate(@NonNull Node node) { - ResolvedNode resolved = null; - if (mContext != null) { - resolved = mContext.resolve(node); - } - if (resolved instanceof ResolvedMethod) { - TypeDescriptor type; - ResolvedMethod method = (ResolvedMethod) resolved; - if (method.isConstructor()) { - ResolvedClass containingClass = method.getContainingClass(); - type = containingClass.getType(); - } else { - type = method.getReturnType(); - } - return type; - } - if (resolved instanceof ResolvedField) { - ResolvedField field = (ResolvedField) resolved; - Node astNode = field.findAstNode(); - if (astNode instanceof VariableDeclaration) { - VariableDeclaration declaration = (VariableDeclaration)astNode; - VariableDefinition definition = declaration.astDefinition(); - if (definition != null) { - VariableDefinitionEntry first = definition.astVariables().first(); - if (first != null) { - Expression initializer = first.astInitializer(); - if (initializer != null) { - TypeDescriptor type = evaluate(initializer); - if (type != null) { - return type; - } - } - } - } - } - return field.getType(); - } - - if (node instanceof VariableReference) { - Statement statement = getParentOfType(node, Statement.class, false); - if (statement != null) { - ListIterator<Node> iterator = statement.getParent().getChildren().listIterator(); - while (iterator.hasNext()) { - if (iterator.next() == statement) { - if (iterator.hasPrevious()) { // should always be true - iterator.previous(); - } - break; - } - } - - String targetName = ((VariableReference) node).astIdentifier().astValue(); - while (iterator.hasPrevious()) { - Node previous = iterator.previous(); - if (previous instanceof VariableDeclaration) { - VariableDeclaration declaration = (VariableDeclaration) previous; - VariableDefinition definition = declaration.astDefinition(); - for (VariableDefinitionEntry entry : definition.astVariables()) { - if (entry.astInitializer() != null && entry.astName().astValue() - .equals(targetName)) { - return evaluate(entry.astInitializer()); - } - } - } else if (previous instanceof ExpressionStatement) { - ExpressionStatement expressionStatement = (ExpressionStatement) previous; - Expression expression = expressionStatement.astExpression(); - if (expression instanceof BinaryExpression && - ((BinaryExpression) expression).astOperator() - == BinaryOperator.ASSIGN) { - BinaryExpression binaryExpression = (BinaryExpression) expression; - if (targetName.equals(binaryExpression.astLeft().toString())) { - return evaluate(binaryExpression.astRight()); - } - } - } - } - } - } else if (node instanceof Cast) { - Cast cast = (Cast) node; - if (mContext != null) { - ResolvedNode typeReference = mContext.resolve(cast.astTypeReference()); - if (typeReference instanceof ResolvedClass) { - return ((ResolvedClass) typeReference).getType(); - } - } - TypeDescriptor viewType = evaluate(cast.astOperand()); - if (viewType != null) { - return viewType; - } - } else if (node instanceof Literal) { - if (node instanceof NullLiteral) { - return null; - } else if (node instanceof BooleanLiteral) { - return new DefaultTypeDescriptor(TYPE_BOOLEAN); - } else if (node instanceof StringLiteral) { - return new DefaultTypeDescriptor(TYPE_STRING); - } else if (node instanceof CharLiteral) { - return new DefaultTypeDescriptor(TYPE_CHAR); - } else if (node instanceof IntegralLiteral) { - IntegralLiteral literal = (IntegralLiteral) node; - // Don't combine to ?: since that will promote astIntValue to a long - if (literal.astMarkedAsLong()) { - return new DefaultTypeDescriptor(TYPE_LONG); - } else { - return new DefaultTypeDescriptor(TYPE_INT); - } - } else if (node instanceof FloatingPointLiteral) { - FloatingPointLiteral literal = (FloatingPointLiteral) node; - // Don't combine to ?: since that will promote astFloatValue to a double - if (literal.astMarkedAsFloat()) { - return new DefaultTypeDescriptor(TYPE_FLOAT); - } else { - return new DefaultTypeDescriptor(TYPE_DOUBLE); - } - } - } else if (node instanceof UnaryExpression) { - return evaluate(((UnaryExpression) node).astOperand()); - } else if (node instanceof InlineIfExpression) { - InlineIfExpression expression = (InlineIfExpression) node; - if (expression.astIfTrue() != null) { - return evaluate(expression.astIfTrue()); - } else if (expression.astIfFalse() != null) { - return evaluate(expression.astIfFalse()); - } - } else if (node instanceof BinaryExpression) { - BinaryExpression expression = (BinaryExpression) node; - BinaryOperator operator = expression.astOperator(); - switch (operator) { - case LOGICAL_OR: - case LOGICAL_AND: - case EQUALS: - case NOT_EQUALS: - case GREATER: - case GREATER_OR_EQUAL: - case LESS: - case LESS_OR_EQUAL: - return new DefaultTypeDescriptor(TYPE_BOOLEAN); - } - - TypeDescriptor type = evaluate(expression.astLeft()); - if (type != null) { - return type; - } - return evaluate(expression.astRight()); - } - - if (resolved instanceof ResolvedVariable) { - ResolvedVariable variable = (ResolvedVariable) resolved; - return variable.getType(); - } - - return null; - } - - /** - * Returns the inferred type of the given node - */ - @Nullable - public PsiType evaluate(@Nullable PsiElement node) { - if (node == null) { - return null; - } - - PsiElement resolved = null; - if (node instanceof PsiReference) { - resolved = ((PsiReference) node).resolve(); - } - if (resolved instanceof PsiMethod) { - PsiMethod method = (PsiMethod) resolved; - if (method.isConstructor()) { - PsiClass containingClass = method.getContainingClass(); - if (containingClass != null && mContext != null) { - return mContext.getEvaluator().getClassType(containingClass); - } - } else { - return method.getReturnType(); - } - } - - if (resolved instanceof PsiField) { - PsiField field = (PsiField) resolved; - if (field.getInitializer() != null) { - PsiType type = evaluate(field.getInitializer()); - if (type != null) { - return type; - } - } - return field.getType(); - } else if (resolved instanceof PsiLocalVariable) { - PsiLocalVariable variable = (PsiLocalVariable) resolved; - PsiStatement statement = PsiTreeUtil.getParentOfType(node, PsiStatement.class, - false); - if (statement != null) { - PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, - PsiStatement.class); - String targetName = variable.getName(); - if (targetName == null) { - return null; - } - while (prev != null) { - if (prev instanceof PsiDeclarationStatement) { - for (PsiElement element : ((PsiDeclarationStatement)prev).getDeclaredElements()) { - if (variable.equals(element)) { - return evaluate(variable.getInitializer()); - } - } - } else if (prev instanceof PsiExpressionStatement) { - PsiExpression expression = ((PsiExpressionStatement)prev).getExpression(); - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assign = (PsiAssignmentExpression) expression; - PsiExpression lhs = assign.getLExpression(); - if (lhs instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) lhs; - if (targetName.equals(reference.getReferenceName()) && - reference.getQualifier() == null) { - return evaluate(assign.getRExpression()); - } - } - } - } - prev = PsiTreeUtil.getPrevSiblingOfType(prev, - PsiStatement.class); - } - } - - return variable.getType(); - } else if (node instanceof PsiExpression) { - PsiExpression expression = (PsiExpression) node; - return expression.getType(); - } - - return null; - } - - @Nullable - public static PsiType evaluate(@NonNull JavaContext context, @Nullable UElement node) { - if (node == null) { - return null; - } - - UElement resolved = node; - if (resolved instanceof UReferenceExpression) { - resolved = UastUtils.tryResolveUDeclaration(resolved, context.getUastContext()); - } - - if (resolved instanceof UMethod) { - return ((UMethod) resolved).getPsi().getReturnType(); - } else if (resolved instanceof UVariable) { - UVariable variable = (UVariable) resolved; - UElement lastAssignment = UastLintUtils.findLastAssignment(variable, node, context); - if (lastAssignment != null) { - return evaluate(context, lastAssignment); - } - return variable.getType(); - } else if (resolved instanceof UCallExpression) { - if (UastExpressionUtils.isMethodCall(resolved)) { - PsiMethod resolvedMethod = ((UCallExpression) resolved).resolve(); - return resolvedMethod != null ? resolvedMethod.getReturnType() : null; - } else { - return ((UCallExpression) resolved).getExpressionType(); - } - } else if (resolved instanceof UExpression) { - return ((UExpression) resolved).getExpressionType(); - } - - return null; - } - - /** - * Evaluates the given node and returns the likely type of the instance. Convenience - * wrapper which creates a new {@linkplain TypeEvaluator}, evaluates the node and returns - * the result. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the type for - * @return the corresponding type descriptor, if found - * @deprecated Use {@link #evaluate(JavaContext, PsiElement)} instead - */ - @Deprecated - @Nullable - public static TypeDescriptor evaluate(@NonNull JavaContext context, @NonNull Node node) { - return new TypeEvaluator(context).evaluate(node); - } - - /** - * Evaluates the given node and returns the likely type of the instance. Convenience - * wrapper which creates a new {@linkplain TypeEvaluator}, evaluates the node and returns - * the result. - * - * @param context the context to use to resolve field references, if any - * @param node the node to compute the type for - * @return the corresponding type descriptor, if found - */ - @Nullable - public static PsiType evaluate(@NonNull JavaContext context, @NonNull PsiElement node) { - return new TypeEvaluator(context).evaluate(node); - } -}
diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/XmlContext.java b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/XmlContext.java deleted file mode 100644 index ee16bab..0000000 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/XmlContext.java +++ /dev/null
@@ -1,205 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.detector.api; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceFolderType; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.client.api.XmlParser; -import com.google.common.annotations.Beta; - -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import java.io.File; - -/** - * A {@link Context} used when checking XML files. - * <p> - * <b>NOTE: This is not a public or final API; if you rely on this be prepared - * to adjust your code for the next tools release.</b> - */ -@Beta -public class XmlContext extends ResourceContext { - static final String SUPPRESS_COMMENT_PREFIX = "<!--suppress "; //$NON-NLS-1$ - - /** The XML parser */ - private final XmlParser mParser; - /** The XML document */ - public Document document; - - /** - * Construct a new {@link XmlContext} - * - * @param driver the driver running through the checks - * @param project the project containing the file being checked - * @param main the main project if this project is a library project, or - * null if this is not a library project. The main project is - * the root project of all library projects, not necessarily the - * directly including project. - * @param file the file being checked - * @param folderType the {@link ResourceFolderType} of this file, if any - */ - public XmlContext( - @NonNull LintDriver driver, - @NonNull Project project, - @Nullable Project main, - @NonNull File file, - @Nullable ResourceFolderType folderType, - @NonNull XmlParser parser) { - super(driver, project, main, file, folderType); - mParser = parser; - } - - /** - * Returns the location for the given node, which may be an element or an attribute. - * - * @param node the node to look up the location for - * @return the location for the node - */ - @NonNull - public Location getLocation(@NonNull Node node) { - return mParser.getLocation(this, node); - } - - /** - * Returns the location for name-portion of the given element or attribute. - * - * @param node the node to look up the location for - * @return the location for the node - */ - @NonNull - public Location getNameLocation(@NonNull Node node) { - return mParser.getNameLocation(this, node); - } - - /** - * Returns the location for value-portion of the given attribute - * - * @param node the node to look up the location for - * @return the location for the node - */ - @NonNull - public Location getValueLocation(@NonNull Attr node) { - return mParser.getValueLocation(this, node); - } - - /** - * Creates a new location within an XML text node - * - * @param textNode the text node - * @param begin the start offset within the text node (inclusive) - * @param end the end offset within the text node (exclusive) - * @return a new location - */ - @NonNull - public Location getLocation(@NonNull Node textNode, int begin, int end) { - assert textNode.getNodeType() == Node.TEXT_NODE; - return mParser.getLocation(this, textNode, begin, end); - } - - @NonNull - public XmlParser getParser() { - return mParser; - } - - /** - * Reports an issue applicable to a given DOM node. The DOM node is used as the - * scope to check for suppress lint annotations. - * - * @param issue the issue to report - * @param scope the DOM node scope the error applies to. The lint infrastructure - * will check whether there are suppress directives on this node (or its enclosing - * nodes) and if so suppress the warning without involving the client. - * @param location the location of the issue, or null if not known - * @param message the message for this warning - */ - public void report( - @NonNull Issue issue, - @Nullable Node scope, - @NonNull Location location, - @NonNull String message) { - if (scope != null && mDriver.isSuppressed(this, issue, scope)) { - return; - } - super.report(issue, location, message); - } - - /** - * Report an error. - * Like {@link #report(Issue, org.w3c.dom.Node, Location, String)} but with - * a now-unused data parameter at the end. - * - * @deprecated Use {@link #report(Issue, org.w3c.dom.Node, Location, String)} instead; - * this method is here for custom rule compatibility - */ - @SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules - @Deprecated - public void report( - @NonNull Issue issue, - @Nullable Node scope, - @NonNull Location location, - @NonNull String message, - @SuppressWarnings("UnusedParameters") @Nullable Object data) { - report(issue, scope, location, message); - } - - @Override - public void report( - @NonNull Issue issue, - @NonNull Location location, - @NonNull String message) { - // Warn if clients use the non-scoped form? No, there are cases where an - // XML detector's error isn't applicable to one particular location (or it's - // not feasible to compute it cheaply) - //mDriver.getClient().log(null, "Warning: Issue " + issue - // + " was reported without a scope node: Can't be suppressed."); - - // For now just check the document root itself - if (document != null && mDriver.isSuppressed(this, issue, document)) { - return; - } - - super.report(issue, location, message); - } - - @Override - @Nullable - protected String getSuppressCommentPrefix() { - return SUPPRESS_COMMENT_PREFIX; - } - - public boolean isSuppressedWithComment(@NonNull Node node, @NonNull Issue issue) { - // Check whether there is a comment marker - String contents = getContents(); - assert contents != null; // otherwise we wouldn't be here - - int start = mParser.getNodeStartOffset(this, node); - if (start != -1) { - return isSuppressedWithComment(start, issue); - } - - return false; - } - - @NonNull - public Location.Handle createLocationHandle(@NonNull Node node) { - return mParser.createLocationHandle(this, node); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AddJavascriptInterfaceDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AddJavascriptInterfaceDetector.java deleted file mode 100644 index 2be12a36..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AddJavascriptInterfaceDetector.java +++ /dev/null
@@ -1,94 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - - -import static com.android.tools.klint.client.api.JavaParser.TYPE_OBJECT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Ensures that addJavascriptInterface is not called for API levels below 17. - */ -public class AddJavascriptInterfaceDetector extends Detector implements Detector.UastScanner { - public static final Issue ISSUE = Issue.create( - "AddJavascriptInterface", //$NON-NLS-1$ - "addJavascriptInterface Called", - "For applications built for API levels below 17, `WebView#addJavascriptInterface` " - + "presents a security hazard as JavaScript on the target web page has the " - + "ability to use reflection to access the injected object's public fields and " - + "thus manipulate the host application in unintended ways.", - Category.SECURITY, - 9, - Severity.WARNING, - new Implementation( - AddJavascriptInterfaceDetector.class, - Scope.JAVA_FILE_SCOPE)). - addMoreInfo( - "https://labs.mwrinfosecurity.com/blog/2013/09/24/webview-addjavascriptinterface-remote-code-execution/"); - - private static final String WEB_VIEW = "android.webkit.WebView"; //$NON-NLS-1$ - private static final String ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface"; //$NON-NLS-1$ - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList(ADD_JAVASCRIPT_INTERFACE); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - // Ignore the issue if we never build for any API less than 17. - if (context.getMainProject().getMinSdk() >= 17) { - return; - } - - JavaEvaluator evaluator = context.getEvaluator(); - if (!evaluator.methodMatches(method, WEB_VIEW, true, TYPE_OBJECT, TYPE_STRING)) { - return; - } - - String message = "`WebView.addJavascriptInterface` should not be called with minSdkVersion < 17 for security reasons: " + - "JavaScript can use reflection to manipulate application"; - UElement reportElement = call.getMethodIdentifier(); - if (reportElement == null) { - reportElement = call; - } - context.reportUast(ISSUE, reportElement, context.getUastNameLocation(reportElement), message); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlarmDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlarmDetector.java deleted file mode 100644 index 823b408..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlarmDetector.java +++ /dev/null
@@ -1,109 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Makes sure that alarms are handled correctly - */ -public class AlarmDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - AlarmDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Alarm set too soon/frequently */ - public static final Issue ISSUE = Issue.create( - "ShortAlarm", //$NON-NLS-1$ - "Short or Frequent Alarm", - - "Frequent alarms are bad for battery life. As of API 22, the `AlarmManager` " + - "will override near-future and high-frequency alarm requests, delaying the alarm " + - "at least 5 seconds into the future and ensuring that the repeat interval is at " + - "least 60 seconds.\n" + - "\n" + - "If you really need to do work sooner than 5 seconds, post a delayed message " + - "or runnable to a Handler.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - IMPLEMENTATION); - - /** Constructs a new {@link AlarmDetector} check */ - public AlarmDetector() { - } - - // ---- Implements JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("setRepeating"); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.isMemberInClass(method, "android.app.AlarmManager") && - evaluator.getParameterCount(method) == 4) { - ensureAtLeast(context, node, 1, 5000L); - ensureAtLeast(context, node, 2, 60000L); - } - } - - private static void ensureAtLeast(@NonNull JavaContext context, - @NonNull UCallExpression node, int parameter, long min) { - UExpression argument = node.getValueArguments().get(parameter); - long value = getLongValue(context, argument); - if (value < min) { - String message = String.format("Value will be forced up to %1$d as of Android 5.1; " - + "don't rely on this to be exact", min); - context.report(ISSUE, argument, context.getUastLocation(argument), message); - } - } - - private static long getLongValue( - @NonNull JavaContext context, - @NonNull UExpression argument) { - Object value = ConstantEvaluator.evaluate(context, argument); - if (value instanceof Number) { - return ((Number)value).longValue(); - } - - return Long.MAX_VALUE; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AllowAllHostnameVerifierDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AllowAllHostnameVerifierDetector.java deleted file mode 100644 index 6fe09fa..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AllowAllHostnameVerifierDetector.java +++ /dev/null
@@ -1,105 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class AllowAllHostnameVerifierDetector extends Detector implements Detector.UastScanner { - - @SuppressWarnings("unchecked") - private static final Implementation IMPLEMENTATION = - new Implementation(AllowAllHostnameVerifierDetector.class, - Scope.JAVA_FILE_SCOPE); - - public static final Issue ISSUE = Issue.create("AllowAllHostnameVerifier", - "Insecure HostnameVerifier", - "This check looks for use of HostnameVerifier implementations " + - "whose `verify` method always returns true (thus trusting any hostname) " + - "which could result in insecure network traffic caused by trusting arbitrary " + - "hostnames in TLS/SSL certificates presented by peers.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION); - - // ---- Implements JavaScanner ---- - - @Override - @Nullable @SuppressWarnings("javadoc") - public List<String> getApplicableConstructorTypes() { - return Collections.singletonList("org.apache.http.conn.ssl.AllowAllHostnameVerifier"); - } - - @Override - public void visitConstructor(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod constructor) { - Location location = context.getUastLocation(node); - context.report(ISSUE, node, location, - "Using the AllowAllHostnameVerifier HostnameVerifier is unsafe " + - "because it always returns true, which could cause insecure network " + - "traffic due to trusting TLS/SSL server certificates for wrong " + - "hostnames"); - } - - @Override - public List<String> getApplicableMethodNames() { - return Arrays.asList("setHostnameVerifier", "setDefaultHostnameVerifier"); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.methodMatches(method, null, false, "javax.net.ssl.HostnameVerifier")) { - UExpression argument = node.getValueArguments().get(0); - PsiElement resolvedArgument = UastUtils.tryResolve(argument); - if (resolvedArgument instanceof PsiField) { - PsiField field = (PsiField) resolvedArgument; - if ("ALLOW_ALL_HOSTNAME_VERIFIER".equals(field.getName())) { - Location location = context.getUastLocation(argument); - String message = "Using the ALLOW_ALL_HOSTNAME_VERIFIER HostnameVerifier " - + "is unsafe because it always returns true, which could cause " - + "insecure network traffic due to trusting TLS/SSL server " - + "certificates for wrong hostnames"; - context.report(ISSUE, argument, location, message); - } - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlwaysShowActionDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlwaysShowActionDetector.java deleted file mode 100644 index 7a67588..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlwaysShowActionDetector.java +++ /dev/null
@@ -1,221 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.ATTR_SHOW_AS_ACTION; -import static com.android.SdkConstants.VALUE_ALWAYS; -import static com.android.SdkConstants.VALUE_IF_ROOM; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceFolderType; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.ResourceXmlDetector; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; - -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Attr; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Check which looks for usage of showAsAction="always" in menus (or - * MenuItem.SHOW_AS_ACTION_ALWAYS in code), which is usually a style guide violation. - * (Use ifRoom instead). - */ -public class AlwaysShowActionDetector extends ResourceXmlDetector implements - Detector.UastScanner { - - /** The main issue discovered by this detector */ - @SuppressWarnings("unchecked") - public static final Issue ISSUE = Issue.create( - "AlwaysShowAction", //$NON-NLS-1$ - "Usage of `showAsAction=always`", - - "Using `showAsAction=\"always\"` in menu XML, or `MenuItem.SHOW_AS_ACTION_ALWAYS` in " + - "Java code is usually a deviation from the user interface style guide." + - "Use `ifRoom` or the corresponding `MenuItem.SHOW_AS_ACTION_IF_ROOM` instead.\n" + - "\n" + - "If `always` is used sparingly there are usually no problems and behavior is " + - "roughly equivalent to `ifRoom` but with preference over other `ifRoom` " + - "items. Using it more than twice in the same menu is a bad idea.\n" + - "\n" + - "This check looks for menu XML files that contain more than two `always` " + - "actions, or some `always` actions and no `ifRoom` actions. In Java code, " + - "it looks for projects that contain references to `MenuItem.SHOW_AS_ACTION_ALWAYS` " + - "and no references to `MenuItem.SHOW_AS_ACTION_IF_ROOM`.", - - Category.USABILITY, - 3, - Severity.WARNING, - new Implementation( - AlwaysShowActionDetector.class, - Scope.JAVA_AND_RESOURCE_FILES, - Scope.RESOURCE_FILE_SCOPE)) - .addMoreInfo("http://developer.android.com/design/patterns/actionbar.html"); //$NON-NLS-1$ - - /** List of showAsAction attributes appearing in the current menu XML file */ - private List<Attr> mFileAttributes; - /** If at least one location has been marked ignore in this file, ignore all */ - private boolean mIgnoreFile; - /** List of locations of MenuItem.SHOW_AS_ACTION_ALWAYS references in Java code */ - private List<Location> mAlwaysFields; - /** True if references to MenuItem.SHOW_AS_ACTION_IF_ROOM were found */ - private boolean mHasIfRoomRefs; - - /** Constructs a new {@link AlwaysShowActionDetector} */ - public AlwaysShowActionDetector() { - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == ResourceFolderType.MENU; - } - - @Override - public Collection<String> getApplicableAttributes() { - return Collections.singletonList(ATTR_SHOW_AS_ACTION); - } - - @Override - public void beforeCheckFile(@NonNull Context context) { - mFileAttributes = null; - } - - @Override - public void afterCheckFile(@NonNull Context context) { - if (mIgnoreFile) { - mFileAttributes = null; - return; - } - if (mFileAttributes != null) { - assert context instanceof XmlContext; // mFileAttributes is only set in XML files - - List<Attr> always = new ArrayList<Attr>(); - List<Attr> ifRoom = new ArrayList<Attr>(); - for (Attr attribute : mFileAttributes) { - String value = attribute.getValue(); - if (value.equals(VALUE_ALWAYS)) { - always.add(attribute); - } else if (value.equals(VALUE_IF_ROOM)) { - ifRoom.add(attribute); - } else if (value.indexOf('|') != -1) { - String[] flags = value.split("\\|"); //$NON-NLS-1$ - for (String flag : flags) { - if (flag.equals(VALUE_ALWAYS)) { - always.add(attribute); - break; - } else if (flag.equals(VALUE_IF_ROOM)) { - ifRoom.add(attribute); - break; - } - } - } - } - - if (!always.isEmpty() && mFileAttributes.size() > 1) { - // Complain if you're using more than one "always", or if you're - // using "always" and aren't using "ifRoom" at all (and provided you - // have more than a single item) - if (always.size() > 2 || ifRoom.isEmpty()) { - XmlContext xmlContext = (XmlContext) context; - Location location = null; - for (int i = always.size() - 1; i >= 0; i--) { - Location next = location; - location = xmlContext.getLocation(always.get(i)); - if (next != null) { - location.setSecondary(next); - } - } - if (location != null) { - context.report(ISSUE, location, - "Prefer \"`ifRoom`\" instead of \"`always`\""); - } - } - } - } - } - - @Override - public void afterCheckProject(@NonNull Context context) { - if (mAlwaysFields != null && !mHasIfRoomRefs) { - for (Location location : mAlwaysFields) { - context.report(ISSUE, location, - "Prefer \"`SHOW_AS_ACTION_IF_ROOM`\" instead of \"`SHOW_AS_ACTION_ALWAYS`\""); - } - } - } - - // ---- Implements XmlScanner ---- - - @Override - public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { - if (context.getDriver().isSuppressed(context, ISSUE, attribute)) { - mIgnoreFile = true; - return; - } - - if (mFileAttributes == null) { - mFileAttributes = new ArrayList<Attr>(); - } - mFileAttributes.add(attribute); - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableReferenceNames() { - return Arrays.asList("SHOW_AS_ACTION_IF_ROOM", "SHOW_AS_ACTION_ALWAYS"); - } - - @Override - public void visitReference(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UReferenceExpression reference, @NonNull PsiElement resolved) { - if (resolved instanceof PsiField - && JavaEvaluator.isMemberInClass((PsiField) resolved, - "android.view.MenuItem")) { - if ("SHOW_AS_ACTION_ALWAYS".equals(((PsiField) resolved).getName())) { - if (context.getDriver().isSuppressed(context, ISSUE, reference)) { - return; - } - if (mAlwaysFields == null) { - mAlwaysFields = new ArrayList<Location>(); - } - mAlwaysFields.add(context.getUastLocation(reference)); - } else { - mHasIfRoomRefs = true; - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AndroidAutoDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AndroidAutoDetector.java deleted file mode 100644 index 6a7cca0..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AndroidAutoDetector.java +++ /dev/null
@@ -1,399 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.TAG_INTENT_FILTER; -import static com.android.SdkConstants.TAG_SERVICE; -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; -import static com.android.xml.AndroidManifest.NODE_ACTION; -import static com.android.xml.AndroidManifest.NODE_APPLICATION; -import static com.android.xml.AndroidManifest.NODE_METADATA; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceFolderType; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.ResourceXmlDetector; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.intellij.psi.JavaRecursiveElementVisitor; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; - -import java.util.Arrays; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; - -/** - * Detector for Android Auto issues. - * <p> Uses a {@code <meta-data>} tag with a {@code name="com.google.android.gms.car.application"} - * as a trigger for validating Automotive specific issues. - */ -public class AndroidAutoDetector extends ResourceXmlDetector - implements XmlScanner, Detector.UastScanner { - - @SuppressWarnings("unchecked") - public static final Implementation IMPL = new Implementation( - AndroidAutoDetector.class, - EnumSet.of(Scope.RESOURCE_FILE, Scope.MANIFEST, Scope.JAVA_FILE), - Scope.RESOURCE_FILE_SCOPE); - - /** Invalid attribute for uses tag.*/ - public static final Issue INVALID_USES_TAG_ISSUE = Issue.create( - "InvalidUsesTagAttribute", //$NON-NLS-1$ - "Invalid `name` attribute for `uses` element.", - "The <uses> element in `<automotiveApp>` should contain a " + - "valid value for the `name` attribute.\n" + - "Valid values are `media` or `notification`.", - Category.CORRECTNESS, - 6, - Severity.ERROR, - IMPL).addMoreInfo( - "https://developer.android.com/training/auto/start/index.html#auto-metadata"); - - /** Missing MediaBrowserService action */ - public static final Issue MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE = Issue.create( - "MissingMediaBrowserServiceIntentFilter", //$NON-NLS-1$ - "Missing intent-filter with action `android.media.browse.MediaBrowserService`.", - "An Automotive Media App requires an exported service that extends " + - "`android.service.media.MediaBrowserService` with an " + - "`intent-filter` for the action `android.media.browse.MediaBrowserService` " + - "to be able to browse and play media.\n" + - "To do this, add\n" + - "`<intent-filter>`\n" + - " `<action android:name=\"android.media.browse.MediaBrowserService\" />`\n" + - "`</intent-filter>`\n to the service that extends " + - "`android.service.media.MediaBrowserService`", - Category.CORRECTNESS, - 6, - Severity.ERROR, - IMPL).addMoreInfo( - "https://developer.android.com/training/auto/audio/index.html#config_manifest"); - - /** Missing intent-filter for Media Search. */ - public static final Issue MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH = Issue.create( - "MissingIntentFilterForMediaSearch", //$NON-NLS-1$ - "Missing intent-filter with action `android.media.action.MEDIA_PLAY_FROM_SEARCH`", - "To support voice searches on Android Auto, you should also register an " + - "`intent-filter` for the action `android.media.action.MEDIA_PLAY_FROM_SEARCH`" + - ".\nTo do this, add\n" + - "`<intent-filter>`\n" + - " `<action android:name=\"android.media.action.MEDIA_PLAY_FROM_SEARCH\" />`\n" + - "`</intent-filter>`\n" + - "to your `<activity>` or `<service>`.", - Category.CORRECTNESS, - 6, - Severity.ERROR, - IMPL).addMoreInfo( - "https://developer.android.com/training/auto/audio/index.html#support_voice"); - - /** Missing implementation of MediaSession.Callback#onPlayFromSearch*/ - public static final Issue MISSING_ON_PLAY_FROM_SEARCH = Issue.create( - "MissingOnPlayFromSearch", //$NON-NLS-1$ - "Missing `onPlayFromSearch`.", - "To support voice searches on Android Auto, in addition to adding an " + - "`intent-filter` for the action `onPlayFromSearch`," + - " you also need to override and implement " + - "`onPlayFromSearch(String query, Bundle bundle)`", - Category.CORRECTNESS, - 6, - Severity.ERROR, - IMPL).addMoreInfo( - "https://developer.android.com/training/auto/audio/index.html#support_voice"); - - private static final String CAR_APPLICATION_METADATA_NAME = - "com.google.android.gms.car.application"; //$NON-NLS-1$ - private static final String VAL_NAME_MEDIA = "media"; //$NON-NLS-1$ - private static final String VAL_NAME_NOTIFICATION = "notification"; //$NON-NLS-1$ - private static final String TAG_AUTOMOTIVE_APP = "automotiveApp"; //$NON-NLS-1$ - private static final String ATTR_RESOURCE = "resource"; //$NON-NLS-1$ - private static final String TAG_USES = "uses"; //$NON-NLS-1$ - private static final String ACTION_MEDIA_BROWSER_SERVICE = - "android.media.browse.MediaBrowserService"; //$NON-NLS-1$ - private static final String ACTION_MEDIA_PLAY_FROM_SEARCH = - "android.media.action.MEDIA_PLAY_FROM_SEARCH"; //$NON-NLS-1$ - private static final String CLASS_MEDIA_SESSION_CALLBACK = - "android.media.session.MediaSession.Callback"; //$NON-NLS-1$ - private static final String CLASS_V4MEDIA_SESSION_COMPAT_CALLBACK = - "android.support.v4.media.session.MediaSessionCompat.Callback"; //$NON-NLS-1$ - private static final String METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH = - "onPlayFromSearch"; //$NON-NLS-1$ - private static final String BUNDLE_ARG = "android.os.Bundle"; //$NON-NLS-1$ - - /** - * Indicates whether we identified that the current app is an automotive app and - * that we should validate all the automotive specific issues. - */ - private boolean mDoAutomotiveAppCheck; - - /** Indicates that a {@link #ACTION_MEDIA_BROWSER_SERVICE} intent-filter action was found. */ - private boolean mMediaIntentFilterFound; - - /** Indicates that a {@link #ACTION_MEDIA_PLAY_FROM_SEARCH} intent-filter action was found. */ - private boolean mMediaSearchIntentFilterFound; - - /** The resource file name deduced by the meta-data resource value */ - private String mAutomotiveResourceFileName; - - /** Indicates whether this app is an automotive Media App. */ - private boolean mIsAutomotiveMediaApp; - - /** {@link Location.Handle} to the application element */ - private Location.Handle mMainApplicationHandle; - - /** Constructs a new {@link AndroidAutoDetector} check */ - public AndroidAutoDetector() { - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - // We only need to check the meta data resource file in res/xml if any. - return folderType == ResourceFolderType.XML; - } - - @Override - public Collection<String> getApplicableElements() { - return Arrays.asList( - TAG_AUTOMOTIVE_APP, // Root element of a declared automotive descriptor. - NODE_METADATA, // meta-data from AndroidManifest.xml - TAG_SERVICE, // service from AndroidManifest.xml - TAG_INTENT_FILTER, // Any declared intent-filter from AndroidManifest.xml - NODE_APPLICATION // Used for storing the application element/location. - ); - } - - @Override - public void beforeCheckProject(@NonNull Context context) { - mIsAutomotiveMediaApp = false; - mAutomotiveResourceFileName = null; - mMediaIntentFilterFound = false; - mMediaSearchIntentFilterFound = false; - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - String tagName = element.getTagName(); - if (NODE_METADATA.equals(tagName) && !mDoAutomotiveAppCheck) { - checkAutoMetadataTag(element); - } else if (TAG_AUTOMOTIVE_APP.equals(tagName)) { - checkAutomotiveAppElement(context, element); - } else if (NODE_APPLICATION.equals(tagName)) { - // Disable reporting the error if the Issue was suppressed at - // the application level. - if (context.getMainProject() == context.getProject() - && !context.getProject().isLibrary()) { - mMainApplicationHandle = context.createLocationHandle(element); - mMainApplicationHandle.setClientData(element); - } - } else if (TAG_SERVICE.equals(tagName)) { - checkServiceForBrowserServiceIntentFilter(element); - } else if (TAG_INTENT_FILTER.equals(tagName)) { - checkForMediaSearchIntentFilter(element); - } - } - - private void checkAutoMetadataTag(Element element) { - String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - - if (CAR_APPLICATION_METADATA_NAME.equals(name)) { - String autoFileName = element.getAttributeNS(ANDROID_URI, ATTR_RESOURCE); - - if (autoFileName != null && autoFileName.startsWith("@xml/")) { //$NON-NLS-1$ - // Store the fact that we need to check all the auto issues. - mDoAutomotiveAppCheck = true; - mAutomotiveResourceFileName = - autoFileName.substring("@xml/".length()) + DOT_XML; //$NON-NLS-1$ - } - } - } - - private void checkAutomotiveAppElement(XmlContext context, Element element) { - // Indicates whether the current file matches the resource that was registered - // in AndroidManifest.xml. - boolean isMetadataResource = - mAutomotiveResourceFileName != null - && mAutomotiveResourceFileName.equals(context.file.getName()); - - for (Element child : LintUtils.getChildren(element)) { - - if (TAG_USES.equals(child.getTagName())) { - String attrValue = child.getAttribute(ATTR_NAME); - if (VAL_NAME_MEDIA.equals(attrValue)) { - mIsAutomotiveMediaApp |= isMetadataResource; - } else if (!VAL_NAME_NOTIFICATION.equals(attrValue) - && context.isEnabled(INVALID_USES_TAG_ISSUE)) { - // Error invalid value for attribute. - Attr node = child.getAttributeNode(ATTR_NAME); - if (node == null) { - // no name specified - continue; - } - context.report(INVALID_USES_TAG_ISSUE, node, - context.getLocation(node), - "Expecting one of `" + VAL_NAME_MEDIA + "` or `" + - VAL_NAME_NOTIFICATION + "` for the name " + - "attribute in " + TAG_USES + " tag."); - } - } - } - // Report any errors that we have collected that can be shown to the user - // once we determine that this is an Automotive Media App. - if (mIsAutomotiveMediaApp - && !context.getProject().isLibrary() - && mMainApplicationHandle != null - && mDoAutomotiveAppCheck) { - - Element node = (Element) mMainApplicationHandle.getClientData(); - - if (!mMediaIntentFilterFound - && context.isEnabled(MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE)) { - context.report(MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE, node, - mMainApplicationHandle.resolve(), - "Missing `intent-filter` for action " + - "`android.media.browse.MediaBrowserService` that is required for " + - "android auto support"); - } - if (!mMediaSearchIntentFilterFound - && context.isEnabled(MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH)) { - context.report(MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH, node, - mMainApplicationHandle.resolve(), - "Missing `intent-filter` for action " + - "`android.media.action.MEDIA_PLAY_FROM_SEARCH`."); - } - } - } - - private void checkServiceForBrowserServiceIntentFilter(Element element) { - if (TAG_SERVICE.equals(element.getTagName()) - && !mMediaIntentFilterFound) { - - for (Element child : LintUtils.getChildren(element)) { - String tagName = child.getTagName(); - if (TAG_INTENT_FILTER.equals(tagName)) { - for (Element filterChild : LintUtils.getChildren(child)) { - if (NODE_ACTION.equals(filterChild.getTagName())) { - String actionValue = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME); - if (ACTION_MEDIA_BROWSER_SERVICE.equals(actionValue)) { - mMediaIntentFilterFound = true; - return; - } - } - } - } - } - } - } - - private void checkForMediaSearchIntentFilter(Element element) { - if (!mMediaSearchIntentFilterFound) { - - for (Element filterChild : LintUtils.getChildren(element)) { - if (NODE_ACTION.equals(filterChild.getTagName())) { - String actionValue = filterChild.getAttributeNS(ANDROID_URI, ATTR_NAME); - if (ACTION_MEDIA_PLAY_FROM_SEARCH.equals(actionValue)) { - mMediaSearchIntentFilterFound = true; - break; - } - } - } - } - } - - // Implementation of the UastScanner - - @Override - @Nullable - public List<String> applicableSuperClasses() { - // We currently enable scanning only for media apps. - return mIsAutomotiveMediaApp ? - Arrays.asList(CLASS_MEDIA_SESSION_CALLBACK, - CLASS_V4MEDIA_SESSION_COMPAT_CALLBACK) - : null; - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - // Only check classes that are not declared abstract. - if (!context.getEvaluator().isAbstract(declaration)) { - MediaSessionCallbackVisitor visitor = new MediaSessionCallbackVisitor(context); - declaration.accept(visitor); - if (!visitor.isPlayFromSearchMethodFound() - && context.isEnabled(MISSING_ON_PLAY_FROM_SEARCH)) { - - context.reportUast(MISSING_ON_PLAY_FROM_SEARCH, declaration, - context.getUastNameLocation(declaration), - "This class does not override `" + - METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH + "` from `MediaSession.Callback`" + - " The method should be overridden and implemented to support " + - "Voice search on Android Auto."); - } - } - } - - /** - * A Visitor class to search for {@code MediaSession.Callback#onPlayFromSearch(..)} - * method declaration. - */ - private static class MediaSessionCallbackVisitor extends JavaRecursiveElementVisitor { - - private final JavaContext mContext; - - private boolean mOnPlayFromSearchFound; - - public MediaSessionCallbackVisitor(JavaContext context) { - this.mContext = context; - } - - public boolean isPlayFromSearchMethodFound() { - return mOnPlayFromSearchFound; - } - - @Override - public void visitMethod(PsiMethod method) { - super.visitMethod(method); - if (METHOD_MEDIA_SESSION_PLAY_FROM_SEARCH.equals(method.getName()) - && mContext.getEvaluator().parametersMatch(method, TYPE_STRING, - BUNDLE_ARG)) { - mOnPlayFromSearchFound = true; - } - } - } - - // Used by the IDE to show errors. - @SuppressWarnings("unused") - @NonNull - public static String[] getAllowedAutomotiveAppTypes() { - return new String[]{VAL_NAME_MEDIA, VAL_NAME_NOTIFICATION}; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java deleted file mode 100644 index 379fb18..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java +++ /dev/null
@@ -1,865 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.ExternalReferenceExpression; -import com.android.tools.klint.client.api.IssueRegistry; -import com.android.tools.klint.client.api.UastLintUtils; -import com.android.tools.klint.detector.api.*; -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.psi.*; -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.uast.*; -import org.jetbrains.uast.java.JavaUAnnotation; -import org.jetbrains.uast.java.JavaUTypeCastExpression; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.*; - -import static com.android.SdkConstants.*; -import static com.android.tools.klint.checks.PermissionRequirement.getAnnotationBooleanValue; -import static com.android.tools.klint.checks.SupportAnnotationDetector.*; -import static com.android.tools.klint.client.api.JavaParser.*; -import static com.android.tools.klint.detector.api.LintUtils.getAutoBoxedType; -import static com.android.tools.klint.detector.api.ResourceEvaluator.*; - -/** - * Checks annotations to make sure they are valid - */ -public class AnnotationDetector extends Detector implements Detector.UastScanner { - - public static final Implementation IMPLEMENTATION = new Implementation( - AnnotationDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Placing SuppressLint on a local variable doesn't work for class-file based checks */ - public static final Issue INSIDE_METHOD = Issue.create( - "LocalSuppress", //$NON-NLS-1$ - "@SuppressLint on invalid element", - - "The `@SuppressAnnotation` is used to suppress Lint warnings in Java files. However, " + - "while many lint checks analyzes the Java source code, where they can find " + - "annotations on (for example) local variables, some checks are analyzing the " + - "`.class` files. And in class files, annotations only appear on classes, fields " + - "and methods. Annotations placed on local variables disappear. If you attempt " + - "to suppress a lint error for a class-file based lint check, the suppress " + - "annotation not work. You must move the annotation out to the surrounding method.", - - Category.CORRECTNESS, - 3, - Severity.ERROR, - IMPLEMENTATION); - - /** Incorrectly using a support annotation */ - @SuppressWarnings("WeakerAccess") - public static final Issue ANNOTATION_USAGE = Issue.create( - "SupportAnnotationUsage", //$NON-NLS-1$ - "Incorrect support annotation usage", - - "This lint check makes sure that the support annotations (such as " + - "`@IntDef` and `@ColorInt`) are used correctly. For example, it's an " + - "error to specify an `@IntRange` where the `from` value is higher than " + - "the `to` value.", - - Category.CORRECTNESS, - 2, - Severity.ERROR, - IMPLEMENTATION); - - /** IntDef annotations should be unique */ - public static final Issue UNIQUE = Issue.create( - "UniqueConstants", //$NON-NLS-1$ - "Overlapping Enumeration Constants", - - "The `@IntDef` annotation allows you to " + - "create a light-weight \"enum\" or type definition. However, it's possible to " + - "accidentally specify the same value for two or more of the values, which can " + - "lead to hard-to-detect bugs. This check looks for this scenario and flags any " + - "repeated constants.\n" + - "\n" + - "In some cases, the repeated constant is intentional (for example, renaming a " + - "constant to a more intuitive name, and leaving the old name in place for " + - "compatibility purposes.) In that case, simply suppress this check by adding a " + - "`@SuppressLint(\"UniqueConstants\")` annotation.", - - Category.CORRECTNESS, - 3, - Severity.ERROR, - IMPLEMENTATION); - - /** Flags should typically be specified as bit shifts */ - public static final Issue FLAG_STYLE = Issue.create( - "ShiftFlags", //$NON-NLS-1$ - "Dangerous Flag Constant Declaration", - - "When defining multiple constants for use in flags, the recommended style is " + - "to use the form `1 << 2`, `1 << 3`, `1 << 4` and so on to ensure that the " + - "constants are unique and non-overlapping.", - - Category.CORRECTNESS, - 3, - Severity.WARNING, - IMPLEMENTATION); - - /** All IntDef constants should be included in switch */ - public static final Issue SWITCH_TYPE_DEF = Issue.create( - "SwitchIntDef", //$NON-NLS-1$ - "Missing @IntDef in Switch", - - "This check warns if a `switch` statement does not explicitly include all " + - "the values declared by the typedef `@IntDef` declaration.", - - Category.CORRECTNESS, - 3, - Severity.WARNING, - IMPLEMENTATION); - - /** Constructs a new {@link AnnotationDetector} check */ - public AnnotationDetector() { - } - - // ---- Implements JavaScanner ---- - - /** - * Set of fields we've already warned about {@link #FLAG_STYLE} for; these can - * be referenced multiple times, so we should only flag them once - */ - private Set<PsiElement> mWarnedFlags; - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(2); - types.add(UAnnotation.class); - types.add(USwitchExpression.class); - return types; - } - - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new AnnotationChecker(context); - } - - private class AnnotationChecker extends AbstractUastVisitor { - - private final JavaContext mContext; - - private AnnotationChecker(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitAnnotation(@NonNull UAnnotation annotation) { - String type = annotation.getQualifiedName(); - if (type == null || type.startsWith("java.lang.")) { - return false; - } - - if (FQCN_SUPPRESS_LINT.equals(type)) { - UElement parent = annotation.getUastParent(); - if (parent == null) { - return false; - } - // Only flag local variables and parameters (not classes, fields and methods) - if (!(parent instanceof UDeclarationsExpression - || parent instanceof ULocalVariable - || parent instanceof UParameter)) { - return false; - } - List<UNamedExpression> attributes = annotation.getAttributeValues(); - if (attributes.size() == 1) { - UNamedExpression attribute = attributes.get(0); - UExpression value = attribute.getExpression(); - if (value instanceof ULiteralExpression) { - Object v = ((ULiteralExpression) value).getValue(); - if (v instanceof String) { - String id = (String) v; - checkSuppressLint(annotation, id); - } - } else if (value instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue initializer = - (PsiArrayInitializerMemberValue) value; - for (PsiAnnotationMemberValue expression : initializer.getInitializers()) { - if (expression instanceof PsiLiteral) { - Object v = ((PsiLiteral) expression).getValue(); - if (v instanceof String) { - String id = (String) v; - if (!checkSuppressLint(annotation, id)) { - return false; - } - } - } - } - } - } - } else if (type.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) { - if (CHECK_RESULT_ANNOTATION.equals(type)) { - // Check that the return type of this method is not void! - if (annotation.getUastParent() instanceof UMethod) { - UMethod method = (UMethod) annotation.getUastParent(); - if (!method.isConstructor() - && PsiType.VOID.equals(method.getReturnType())) { - mContext.report(ANNOTATION_USAGE, annotation.getPsi(), - mContext.getLocation(annotation.getPsi()), - "@CheckResult should not be specified on `void` methods"); - } - } - } else if (INT_RANGE_ANNOTATION.equals(type) - || FLOAT_RANGE_ANNOTATION.equals(type)) { - // Check that the annotated element's type is int or long. - // Also make sure that from <= to. - boolean invalid; - if (INT_RANGE_ANNOTATION.equals(type)) { - checkTargetType(annotation, TYPE_INT, TYPE_LONG, true); - - long from = getLongAttribute(annotation, ATTR_FROM, Long.MIN_VALUE); - long to = getLongAttribute(annotation, ATTR_TO, Long.MAX_VALUE); - invalid = from > to; - } else { - checkTargetType(annotation, TYPE_FLOAT, TYPE_DOUBLE, true); - - double from = getDoubleAttribute(annotation, ATTR_FROM, - Double.NEGATIVE_INFINITY); - double to = getDoubleAttribute(annotation, ATTR_TO, - Double.POSITIVE_INFINITY); - invalid = from > to; - } - if (invalid) { - mContext.reportUast(ANNOTATION_USAGE, annotation, mContext.getUastLocation(annotation), - "Invalid range: the `from` attribute must be less than " - + "the `to` attribute"); - } - } else if (SIZE_ANNOTATION.equals(type)) { - // Check that the annotated element's type is an array, or a collection - // (or at least not an int or long; if so, suggest IntRange) - // Make sure the size and the modulo is not negative. - int unset = -42; - long exact = getLongAttribute(annotation, ATTR_VALUE, unset); - long min = getLongAttribute(annotation, ATTR_MIN, Long.MIN_VALUE); - long max = getLongAttribute(annotation, ATTR_MAX, Long.MAX_VALUE); - long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1); - if (min > max) { - mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()), - "Invalid size range: the `min` attribute must be less than " - + "the `max` attribute"); - } else if (multiple < 1) { - mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()), - "The size multiple must be at least 1"); - - } else if (exact < 0 && exact != unset || min < 0 && min != Long.MIN_VALUE) { - mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()), - "The size can't be negative"); - } - } else if (COLOR_INT_ANNOTATION.equals(type) || (PX_ANNOTATION.equals(type))) { - // Check that ColorInt applies to the right type - checkTargetType(annotation, TYPE_INT, TYPE_LONG, true); - } else if (INT_DEF_ANNOTATION.equals(type)) { - // Make sure IntDef constants are unique - ensureUniqueValues(annotation); - } else if (PERMISSION_ANNOTATION.equals(type) || - PERMISSION_ANNOTATION_READ.equals(type) || - PERMISSION_ANNOTATION_WRITE.equals(type)) { - // Check that if there are no arguments, this is specified on a parameter, - // and conversely, on methods and fields there is a valid argument. - if (annotation.getUastParent() instanceof UMethod) { - String value = PermissionRequirement.getAnnotationStringValue(annotation, ATTR_VALUE); - String[] anyOf = PermissionRequirement.getAnnotationStringValues(annotation, ATTR_ANY_OF); - String[] allOf = PermissionRequirement.getAnnotationStringValues(annotation, ATTR_ALL_OF); - - int set = 0; - //noinspection VariableNotUsedInsideIf - if (value != null) { - set++; - } - //noinspection VariableNotUsedInsideIf - if (allOf != null) { - set++; - } - //noinspection VariableNotUsedInsideIf - if (anyOf != null) { - set++; - } - - if (set == 0) { - mContext.report(ANNOTATION_USAGE, annotation.getPsi(), - mContext.getLocation(annotation.getPsi()), - "For methods, permission annotation should specify one " - + "of `value`, `anyOf` or `allOf`"); - } else if (set > 1) { - mContext.report(ANNOTATION_USAGE, annotation.getPsi(), - mContext.getLocation(annotation.getPsi()), - "Only specify one of `value`, `anyOf` or `allOf`"); - } - } - - } else if (type.endsWith(RES_SUFFIX)) { - // Check that resource type annotations are on ints - checkTargetType(annotation, TYPE_INT, TYPE_LONG, true); - } - } else { - // Look for typedefs (and make sure they're specified on the right type) - PsiElement resolved = annotation.resolve(); - if (resolved != null) { - PsiClass cls = (PsiClass) resolved; - if (cls.isAnnotationType() && cls.getModifierList() != null) { - for (PsiAnnotation a : cls.getModifierList().getAnnotations()) { - String name = a.getQualifiedName(); - if (INT_DEF_ANNOTATION.equals(name)) { - checkTargetType(annotation, TYPE_INT, TYPE_LONG, true); - } else if (STRING_DEF_ANNOTATION.equals(type)) { - checkTargetType(annotation, TYPE_STRING, null, true); - } - } - } - } - } - - return false; - } - - private void checkTargetType(@NonNull UAnnotation node, @NonNull String type1, - @Nullable String type2, boolean allowCollection) { - UElement parent = node.getUastParent(); - PsiType type; - - if (parent instanceof UDeclarationsExpression) { - List<UDeclaration> elements = ((UDeclarationsExpression) parent).getDeclarations(); - if (!elements.isEmpty()) { - UDeclaration element = elements.get(0); - if (element instanceof ULocalVariable) { - type = ((ULocalVariable) element).getType(); - } else { - return; - } - } else { - return; - } - } else if (parent instanceof UMethod) { - UMethod method = (UMethod) parent; - type = method.isConstructor() - ? mContext.getEvaluator().getClassType(method.getContainingClass()) - : method.getReturnType(); - } else if (parent instanceof UVariable) { - // Field or local variable or parameter - type = ((UVariable) parent).getType(); - } else { - return; - } - if (type == null) { - return; - } - - if (allowCollection) { - if (type instanceof PsiArrayType) { - // For example, int[] - type = type.getDeepComponentType(); - } else if (type instanceof PsiClassType) { - // For example, List<Integer> - PsiClassType classType = (PsiClassType)type; - if (classType.getParameters().length == 1) { - PsiClass resolved = classType.resolve(); - if (resolved != null && - InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) { - type = classType.getParameters()[0]; - } - } - } - } - - String typeName = type.getCanonicalText(); - if (!typeName.equals(type1) - && (type2 == null || !typeName.equals(type2))) { - // Autoboxing? You can put @DrawableRes on a java.lang.Integer for example - if (typeName.equals(getAutoBoxedType(type1)) - || type2 != null && typeName.equals(getAutoBoxedType(type2))) { - return; - } - - String expectedTypes = type2 == null ? type1 : type1 + " or " + type2; - if (typeName.equals(TYPE_STRING)) { - typeName = "String"; - } - String message = String.format( - "This annotation does not apply for type %1$s; expected %2$s", - typeName, expectedTypes); - Location location = mContext.getUastLocation(node); - mContext.report(ANNOTATION_USAGE, node, location, message); - } - } - - @Override - public boolean visitSwitchExpression(USwitchExpression switchExpression) { - UExpression condition = switchExpression.getExpression(); - if (condition != null && PsiType.INT.equals(condition.getExpressionType())) { - UAnnotation annotation = findIntDefAnnotation(condition); - if (annotation != null) { - UExpression value = - annotation.findDeclaredAttributeValue(ATTR_VALUE); - if (value == null) { - value = annotation.findDeclaredAttributeValue(null); - } - - if (UastExpressionUtils.isArrayInitializer(value)) { - List<UExpression> allowedValues = - ((UCallExpression) value).getValueArguments(); - switchExpression.accept(new SwitchChecker(switchExpression, allowedValues)); - } - } - } - return false; - } - - @Nullable - private Integer getConstantValue(@NonNull PsiField intDefConstantRef) { - Object constant = intDefConstantRef.computeConstantValue(); - if (constant instanceof Number) { - return ((Number)constant).intValue(); - } - - return null; - } - - /** - * Searches for the corresponding @IntDef annotation definition associated - * with a given node - */ - @Nullable - private UAnnotation findIntDefAnnotation(@NonNull UExpression expression) { - if (expression instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) expression).resolve(); - - if (resolved instanceof PsiModifierListOwner) { - PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations( - (PsiModifierListOwner)resolved); - PsiAnnotation[] relevantAnnotations = - filterRelevantAnnotations(mContext.getEvaluator(), annotations); - UAnnotation annotation = SupportAnnotationDetector.findIntDef( - JavaUAnnotation.wrap(relevantAnnotations)); - if (annotation != null) { - return annotation; - } - } - - if (resolved instanceof PsiLocalVariable) { - PsiLocalVariable variable = (PsiLocalVariable) resolved; - UExpression lastAssignment = UastLintUtils.findLastAssignment(variable, - expression, mContext); - - if(lastAssignment != null) { - return findIntDefAnnotation(lastAssignment); - } - - } - - } else if (expression instanceof UCallExpression) { - PsiMethod method = ((UCallExpression) expression).resolve(); - if (method != null) { - PsiAnnotation[] annotations = - mContext.getEvaluator().getAllAnnotations(method); - PsiAnnotation[] relevantAnnotations = - filterRelevantAnnotations(mContext.getEvaluator(), annotations); - List<UAnnotation> uAnnotations = JavaUAnnotation.wrap(relevantAnnotations); - UAnnotation annotation = SupportAnnotationDetector.findIntDef(uAnnotations); - if (annotation != null) { - return annotation; - } - } - } else if (expression instanceof UIfExpression) { - UIfExpression ifExpression = (UIfExpression) expression; - if (ifExpression.getThenExpression() != null) { - UAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression()); - if (result != null) { - return result; - } - } - if (ifExpression.getElseExpression() != null) { - UAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression()); - if (result != null) { - return result; - } - } - } else if (expression instanceof JavaUTypeCastExpression) { - return findIntDefAnnotation(((JavaUTypeCastExpression)expression).getOperand()); - - } else if (expression instanceof UParenthesizedExpression) { - return findIntDefAnnotation(((UParenthesizedExpression) expression).getExpression()); - } - - return null; - } - - private void ensureUniqueValues(@NonNull UAnnotation node) { - UExpression value = node.findAttributeValue(ATTR_VALUE); - if (value == null) { - value = node.findAttributeValue(null); - } - - if (!(UastExpressionUtils.isArrayInitializer(value))) { - return; - } - - PsiArrayInitializerMemberValue array = (PsiArrayInitializerMemberValue) value; - PsiAnnotationMemberValue[] initializers = array.getInitializers(); - Map<Number,Integer> valueToIndex = - Maps.newHashMapWithExpectedSize(initializers.length); - - boolean flag = getAnnotationBooleanValue(node, TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE; - if (flag) { - ensureUsingFlagStyle(initializers); - } - - ConstantEvaluator constantEvaluator = new ConstantEvaluator(mContext); - for (int index = 0; index < initializers.length; index++) { - PsiAnnotationMemberValue expression = initializers[index]; - Object o = constantEvaluator.evaluate(expression); - if (o instanceof Number) { - Number number = (Number) o; - if (valueToIndex.containsKey(number)) { - @SuppressWarnings("UnnecessaryLocalVariable") - Number repeatedValue = number; - - Location location; - String message; - int prevIndex = valueToIndex.get(number); - PsiElement prevConstant = initializers[prevIndex]; - message = String.format( - "Constants `%1$s` and `%2$s` specify the same exact " - + "value (%3$s); this is usually a cut & paste or " - + "merge error", - expression.getText(), prevConstant.getText(), - repeatedValue.toString()); - location = mContext.getLocation(expression); - Location secondary = mContext.getLocation(prevConstant); - secondary.setMessage("Previous same value"); - location.setSecondary(secondary); - UElement scope = getAnnotationScope(node); - mContext.reportUast(UNIQUE, scope, location, message); - break; - } - valueToIndex.put(number, index); - } - } - } - - private void ensureUsingFlagStyle(@NonNull PsiAnnotationMemberValue[] constants) { - if (constants.length < 3) { - return; - } - - for (PsiAnnotationMemberValue constant : constants) { - if (constant instanceof PsiReferenceExpression) { - PsiElement resolved = ((PsiReferenceExpression) constant).resolve(); - if (resolved instanceof PsiField) { - PsiExpression initializer = ((PsiField) resolved).getInitializer(); - if (initializer instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral) initializer; - Object o = literal.getValue(); - if (!(o instanceof Number)) { - continue; - } - long value = ((Number)o).longValue(); - // Allow -1, 0 and 1. You can write 1 as "1 << 0" but IntelliJ for - // example warns that that's a redundant shift. - if (Math.abs(value) <= 1) { - continue; - } - // Only warn if we're setting a specific bit - if (Long.bitCount(value) != 1) { - continue; - } - int shift = Long.numberOfTrailingZeros(value); - if (mWarnedFlags == null) { - mWarnedFlags = Sets.newHashSet(); - } - if (!mWarnedFlags.add(resolved)) { - return; - } - String message = String.format( - "Consider declaring this constant using 1 << %1$d instead", - shift); - Location location = mContext.getLocation(initializer); - mContext.report(FLAG_STYLE, initializer, location, message); - } - } - } - } - } - - private boolean checkSuppressLint(@NonNull UAnnotation node, @NonNull String id) { - IssueRegistry registry = mContext.getDriver().getRegistry(); - Issue issue = registry.getIssue(id); - // Special-case the ApiDetector issue, since it does both source file analysis - // only on field references, and class file analysis on the rest, so we allow - // annotations outside of methods only on fields - if (issue != null && !issue.getImplementation().getScope().contains(Scope.JAVA_FILE) - || issue == ApiDetector.UNSUPPORTED) { - // This issue doesn't have AST access: annotations are not - // available for local variables or parameters - UElement scope = getAnnotationScope(node); - mContext.report(INSIDE_METHOD, scope, mContext.getUastLocation(node), String.format( - "The `@SuppressLint` annotation cannot be used on a local " + - "variable with the lint check '%1$s': move out to the " + - "surrounding method", id)); - return false; - } - - return true; - } - - private class SwitchChecker extends AbstractUastVisitor { - - private final USwitchExpression mSwitchExpression; - private final List<UExpression> mAllowedValues; - private final List<Object> mFields; - private final List<Integer> mSeenValues; - - private boolean mReported = false; - - private SwitchChecker(USwitchExpression switchExpression, - List<UExpression> allowedValues) { - mSwitchExpression = switchExpression; - mAllowedValues = allowedValues; - - mFields = Lists.newArrayListWithCapacity(allowedValues.size()); - for (UExpression allowedValue : allowedValues) { - if (allowedValue instanceof ExternalReferenceExpression) { - ExternalReferenceExpression externalRef = - (ExternalReferenceExpression) allowedValue; - - PsiElement resolved = UastLintUtils.resolve(externalRef, switchExpression); - - if (resolved instanceof PsiField) { - mFields.add(resolved); - } - } else if (allowedValue instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) allowedValue).resolve(); - if (resolved != null) { - mFields.add(resolved); - } - } else if (allowedValue instanceof ULiteralExpression) { - mFields.add(allowedValue); - } - } - - mSeenValues = Lists.newArrayListWithCapacity(allowedValues.size()); - } - - @Override - public boolean visitSwitchClauseExpression(USwitchClauseExpression node) { - if (mReported) { - return true; - } - - if (mAllowedValues == null) { - return true; - } - - List<UExpression> caseValues = node.getCaseValues(); - if (caseValues == null) { - return true; - } - - for (UExpression caseValue : caseValues) { - if (caseValue instanceof ULiteralExpression) { - // Report warnings if you specify hardcoded constants. - // It's the wrong thing to do. - List<String> list = computeFieldNames(mSwitchExpression, - Arrays.asList(mAllowedValues)); - // Keep error message in sync with {@link #getMissingCases} - String message = "Don't use a constant here; expected one of: " + Joiner - .on(", ").join(list); - mContext.report(SWITCH_TYPE_DEF, caseValue, - mContext.getUastLocation(caseValue), message); - // Don't look for other missing typedef constants since you might - // have aliased with value - mReported = true; - - } else if (caseValue instanceof UReferenceExpression) { // default case can have null expression - PsiElement resolved = ((UReferenceExpression) caseValue).resolve(); - if (resolved == null) { - // If there are compilation issues (e.g. user is editing code) we - // can't be certain, so don't flag anything. - return true; - } - if (resolved instanceof PsiField) { - // We can't just do - // fields.remove(resolved); - // since the fields list contains instances of potentially - // different types with different hash codes (due to the - // external annotations, which are not of the same type as - // for example the ECJ based ones. - // - // The equals method on external field class deliberately handles - // this (but it can't make its hash code match what - // the ECJ fields do, which is tied to the ECJ binding hash code.) - // So instead, manually check for equals. These lists tend to - // be very short anyway. - boolean found = false; - ListIterator<Object> iterator = mFields.listIterator(); - while (iterator.hasNext()) { - Object field = iterator.next(); - if (field.equals(resolved)) { - iterator.remove(); - found = true; - break; - } - } - if (!found) { - // Look for local alias - UExpression initializer = mContext.getUastContext() - .getInitializerBody(((PsiField) resolved)); - if (initializer instanceof UReferenceExpression) { - resolved = ((UReferenceExpression) initializer).resolve(); - if (resolved instanceof PsiField) { - iterator = mFields.listIterator(); - while (iterator.hasNext()) { - Object field = iterator.next(); - if (field.equals(resolved)) { - iterator.remove(); - found = true; - break; - } - } - } - } - } - - if (found) { - Integer cv = getConstantValue((PsiField) resolved); - if (cv != null) { - mSeenValues.add(cv); - } - } else { - List<String> list = computeFieldNames( - mSwitchExpression, Collections.singletonList(mAllowedValues)); - // Keep error message in sync with {@link #getMissingCases} - String message = "Unexpected constant; expected one of: " + Joiner - .on(", ").join(list); - Location location = mContext.getUastNameLocation(caseValue); - mContext.report(SWITCH_TYPE_DEF, caseValue, location, message); - } - } - } - } - return true; - } - - @Override - public void afterVisitSwitchExpression(USwitchExpression node) { - reportMissingSwitchCases(); - super.afterVisitSwitchExpression(node); - } - - private void reportMissingSwitchCases() { - if (mReported) { - return; - } - - if (mAllowedValues == null) { - return; - } - - // Any missing switch constants? Before we flag them, look to see if any - // of them have the same values: those can be omitted - if (!mFields.isEmpty()) { - ListIterator<Object> iterator = mFields.listIterator(); - while (iterator.hasNext()) { - Object next = iterator.next(); - if (next instanceof PsiField) { - Integer cv = getConstantValue((PsiField)next); - if (mSeenValues.contains(cv)) { - iterator.remove(); - } - } - } - } - - if (!mFields.isEmpty()) { - List<String> list = computeFieldNames(mSwitchExpression, mFields); - // Keep error message in sync with {@link #getMissingCases} - String message = "Switch statement on an `int` with known associated constant " - + "missing case " + Joiner.on(", ").join(list); - Location location = mContext.getUastLocation(mSwitchExpression.getSwitchIdentifier()); - mContext.report(SWITCH_TYPE_DEF, mSwitchExpression, location, message); - } - } - } - } - - private static List<String> computeFieldNames( - @NonNull USwitchExpression node, Iterable<?> allowedValues) { - - List<String> list = Lists.newArrayList(); - for (Object o : allowedValues) { - if (o instanceof ExternalReferenceExpression) { - ExternalReferenceExpression externalRef = (ExternalReferenceExpression) o; - PsiElement resolved = UastLintUtils.resolve(externalRef, node); - if (resolved != null) { - o = resolved; - } - } else if (o instanceof PsiReferenceExpression) { - PsiElement resolved = ((PsiReferenceExpression) o).resolve(); - if (resolved != null) { - o = resolved; - } - } else if (o instanceof PsiLiteral) { - list.add("`" + ((PsiLiteral) o).getValue() + '`'); - continue; - } - - if (o instanceof PsiField) { - PsiField field = (PsiField) o; - // Only include class name if necessary - String name = field.getName(); - UClass clz = UastUtils.getParentOfType(node, UClass.class, true); - if (clz != null) { - PsiClass containingClass = field.getContainingClass(); - if (containingClass != null && !containingClass.equals(clz.getPsi())) { - name = containingClass.getName() + '.' + field.getName(); - } - } - list.add('`' + name + '`'); - } - } - Collections.sort(list); - return list; - } - - /** - * Returns the node to use as the scope for the given annotation node. - * You can't annotate an annotation itself (with {@code @SuppressLint}), but - * you should be able to place an annotation next to it, as a sibling, to only - * suppress the error on this annotated element, not the whole surrounding class. - */ - @NonNull - private static UElement getAnnotationScope(@NonNull UAnnotation node) { - UElement scope = UastUtils.getParentOfType(node, UAnnotation.class, true); - if (scope == null) { - scope = node; - } - return scope; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/Api.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/Api.java deleted file mode 100644 index 191b172..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/Api.java +++ /dev/null
@@ -1,109 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - - -import com.android.annotations.NonNull; - -import org.xml.sax.SAXException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Main entry point for API description. - * - * To create the {@link Api}, use {@link #parseApi(File)} - * - */ -public class Api { - - /** - * Parses simplified API file. - * @param apiFile the file to read - * @return a new ApiInfo - */ - public static Api parseApi(File apiFile) { - InputStream inputStream = null; - try { - inputStream = new FileInputStream(apiFile); - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - SAXParser parser = parserFactory.newSAXParser(); - ApiParser apiParser = new ApiParser(); - parser.parse(inputStream, apiParser); - inputStream.close(); - - // Also read in API (unless regenerating the map for newer libraries) - //noinspection PointlessBooleanExpression,TestOnlyProblems - if (!ApiLookup.DEBUG_FORCE_REGENERATE_BINARY) { - inputStream = Api.class.getResourceAsStream("api-versions-support-library.xml"); - if (inputStream != null) { - parser.parse(inputStream, apiParser); - } - } - - return new Api(apiParser.getClasses(), apiParser.getPackages()); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - // ignore - } - } - } - - return null; - } - - private final Map<String, ApiClass> mClasses; - private final Map<String, ApiPackage> mPackages; - - private Api( - @NonNull Map<String, ApiClass> classes, - @NonNull Map<String, ApiPackage> packages) { - mClasses = new HashMap<String, ApiClass>(classes); - mPackages = new HashMap<String, ApiPackage>(packages); - } - - ApiClass getClass(String fqcn) { - return mClasses.get(fqcn); - } - - Map<String, ApiClass> getClasses() { - return Collections.unmodifiableMap(mClasses); - } - - Map<String, ApiPackage> getPackages() { - return Collections.unmodifiableMap(mPackages); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiClass.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiClass.java deleted file mode 100644 index 80d1771..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiClass.java +++ /dev/null
@@ -1,506 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.CONSTRUCTOR_NAME; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.utils.Pair; -import com.google.common.collect.Lists; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Represents a class and its methods/fields. - * - * {@link #getSince()} gives the API level it was introduced. - * - * {@link #getMethod} returns when the method was introduced. - * {@link #getField} returns when the field was introduced. - */ -public class ApiClass implements Comparable<ApiClass> { - private final String mName; - private final int mSince; - private final int mDeprecatedIn; - - private final List<Pair<String, Integer>> mSuperClasses = Lists.newArrayList(); - private final List<Pair<String, Integer>> mInterfaces = Lists.newArrayList(); - - private final Map<String, Integer> mFields = new HashMap<String, Integer>(); - private final Map<String, Integer> mMethods = new HashMap<String, Integer>(); - private final Map<String, Integer> mDeprecatedMembersIn = new HashMap<String, Integer>(); - - // Persistence data: Used when writing out binary data in ApiLookup - List<String> members; - int index; // class number, e.g. entry in index where the pointer can be found - int indexOffset; // offset of the class entry - int memberOffsetBegin; // offset of the first member entry in the class - int memberOffsetEnd; // offset after the last member entry in the class - int memberIndexStart; // entry in index for first member - int memberIndexLength; // number of entries - - ApiClass(String name, int since, int deprecatedIn) { - mName = name; - mSince = since; - mDeprecatedIn = deprecatedIn; - } - - /** - * Returns the name of the class. - * @return the name of the class - */ - String getName() { - return mName; - } - - /** - * Returns when the class was introduced. - * @return the api level the class was introduced. - */ - int getSince() { - return mSince; - } - - /** - * Returns the API level a method was deprecated in, or 0 if the method is not deprecated - * - * @return the API level a method was deprecated in, or 0 if the method is not deprecated - */ - int getDeprecatedIn() { - return mDeprecatedIn; - } - - /** - * Returns when a field was added, or Integer.MAX_VALUE if it doesn't exist. - * @param name the name of the field. - * @param info the corresponding info - */ - int getField(String name, Api info) { - // The field can come from this class or from a super class or an interface - // The value can never be lower than this introduction of this class. - // When looking at super classes and interfaces, it can never be lower than when the - // super class or interface was added as a super class or interface to this class. - // Look at all the values and take the lowest. - // For instance: - // This class A is introduced in 5 with super class B. - // In 10, the interface C was added. - // Looking for SOME_FIELD we get the following: - // Present in A in API 15 - // Present in B in API 11 - // Present in C in API 7. - // The answer is 10, which is when C became an interface - int min = Integer.MAX_VALUE; - Integer i = mFields.get(name); - if (i != null) { - min = i; - } - - // now look at the super classes - for (Pair<String, Integer> superClassPair : mSuperClasses) { - ApiClass superClass = info.getClass(superClassPair.getFirst()); - if (superClass != null) { - i = superClass.getField(name, info); - int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; - if (tmp < min) { - min = tmp; - } - } - } - - // now look at the interfaces - for (Pair<String, Integer> superClassPair : mInterfaces) { - ApiClass superClass = info.getClass(superClassPair.getFirst()); - if (superClass != null) { - i = superClass.getField(name, info); - int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; - if (tmp < min) { - min = tmp; - } - } - } - - return min; - } - - /** - * Returns when a field was deprecated, or 0 if it's not deprecated - * - * @param name the name of the field. - * @param info the corresponding info - */ - int getMemberDeprecatedIn(String name, Api info) { - int deprecatedIn = findMemberDeprecatedIn(name, info); - return deprecatedIn < Integer.MAX_VALUE ? deprecatedIn : 0; - } - - private int findMemberDeprecatedIn(String name, Api info) { - // This follows the same logic as getField/getMethod. - // However, it also incorporates deprecation versions from the class. - int min = Integer.MAX_VALUE; - Integer i = mDeprecatedMembersIn.get(name); - if (i != null) { - min = i; - } - - // now look at the super classes - for (Pair<String, Integer> superClassPair : mSuperClasses) { - ApiClass superClass = info.getClass(superClassPair.getFirst()); - if (superClass != null) { - i = superClass.findMemberDeprecatedIn(name, info); - int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; - if (tmp < min) { - min = tmp; - } - } - } - - // now look at the interfaces - for (Pair<String, Integer> superClassPair : mInterfaces) { - ApiClass superClass = info.getClass(superClassPair.getFirst()); - if (superClass != null) { - i = superClass.findMemberDeprecatedIn(name, info); - int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; - if (tmp < min) { - min = tmp; - } - } - } - - return min; - } - - /** - * Returns when a method was added, or Integer.MAX_VALUE if it doesn't exist. - * This goes through the super class to find method only present there. - * @param methodSignature the method signature - */ - int getMethod(String methodSignature, Api info) { - // The method can come from this class or from a super class. - // The value can never be lower than this introduction of this class. - // When looking at super classes, it can never be lower than when the super class became - // a super class of this class. - // Look at all the values and take the lowest. - // For instance: - // This class A is introduced in 5 with super class B. - // In 10, the super class changes to C. - // Looking for foo() we get the following: - // Present in A in API 15 - // Present in B in API 11 - // Present in C in API 7. - // The answer is 10, which is when C became the super class. - int min = Integer.MAX_VALUE; - Integer i = mMethods.get(methodSignature); - if (i != null) { - min = i; - - // Constructors aren't inherited - if (methodSignature.startsWith(CONSTRUCTOR_NAME)) { - return i; - } - } - - // now look at the super classes - for (Pair<String, Integer> superClassPair : mSuperClasses) { - ApiClass superClass = info.getClass(superClassPair.getFirst()); - if (superClass != null) { - i = superClass.getMethod(methodSignature, info); - int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; - if (tmp < min) { - min = tmp; - } - } - } - - // now look at the interfaces classes - for (Pair<String, Integer> interfacePair : mInterfaces) { - ApiClass superClass = info.getClass(interfacePair.getFirst()); - if (superClass != null) { - i = superClass.getMethod(methodSignature, info); - int tmp = interfacePair.getSecond() > i ? interfacePair.getSecond() : i; - if (tmp < min) { - min = tmp; - } - } - } - - return min; - } - - void addField(String name, int since, int deprecatedIn) { - Integer i = mFields.get(name); - assert i == null; - mFields.put(name, since); - if (deprecatedIn > 0) { - mDeprecatedMembersIn.put(name, deprecatedIn); - } - } - - void addMethod(String name, int since, int deprecatedIn) { - // Strip off the method type at the end to ensure that the code which - // produces inherited methods doesn't get confused and end up multiple entries. - // For example, java/nio/Buffer has the method "array()Ljava/lang/Object;", - // and the subclass java/nio/ByteBuffer has the method "array()[B". We want - // the lookup on mMethods to associate the ByteBuffer array method to be - // considered overriding the Buffer method. - int index = name.indexOf(')'); - if (index != -1) { - name = name.substring(0, index + 1); - } - - Integer i = mMethods.get(name); - assert i == null || i == since : i; - mMethods.put(name, since); - if (deprecatedIn > 0) { - mDeprecatedMembersIn.put(name, deprecatedIn); - } - } - - void addSuperClass(String superClass, int since) { - addToArray(mSuperClasses, superClass, since); - } - - void addInterface(String interfaceClass, int since) { - addToArray(mInterfaces, interfaceClass, since); - } - - static void addToArray(List<Pair<String, Integer>> list, String name, int value) { - // check if we already have that name (at a lower level) - for (Pair<String, Integer> pair : list) { - if (name.equals(pair.getFirst())) { - assert false; - return; - } - } - - list.add(Pair.of(name, value)); - - } - - @Nullable - public String getPackage() { - int index = mName.lastIndexOf('/'); - if (index != -1) { - return mName.substring(0, index); - } - - return null; - } - - @NonNull - public String getSimpleName() { - int index = mName.lastIndexOf('/'); - if (index != -1) { - return mName.substring(index + 1); - } - - return mName; - } - - @Override - public String toString() { - return mName; - } - - /** - * Returns the set of all methods, including inherited - * ones. - * - * @param info the api to look up super classes from - * @return a set containing all the members fields - */ - Set<String> getAllMethods(Api info) { - Set<String> members = new HashSet<String>(100); - addAllMethods(info, members, true /*includeConstructors*/); - - return members; - } - - List<Pair<String, Integer>> getInterfaces() { - return mInterfaces; - } - - List<Pair<String, Integer>> getSuperClasses() { - return mSuperClasses; - } - - private void addAllMethods(Api info, Set<String> set, boolean includeConstructors) { - if (!includeConstructors) { - for (String method : mMethods.keySet()) { - if (!method.startsWith(CONSTRUCTOR_NAME)) { - set.add(method); - } - } - } else { - for (String method : mMethods.keySet()) { - set.add(method); - } - } - - for (Pair<String, Integer> superClass : mSuperClasses) { - ApiClass clz = info.getClass(superClass.getFirst()); - if (clz != null) { - clz.addAllMethods(info, set, false); - } - } - - // Get methods from implemented interfaces as well; - for (Pair<String, Integer> superClass : mInterfaces) { - ApiClass clz = info.getClass(superClass.getFirst()); - if (clz != null) { - clz.addAllMethods(info, set, false); - } - } - } - - /** - * Returns the set of all fields, including inherited - * ones. - * - * @param info the api to look up super classes from - * @return a set containing all the fields - */ - Set<String> getAllFields(Api info) { - Set<String> members = new HashSet<String>(100); - addAllFields(info, members); - - return members; - } - - private void addAllFields(Api info, Set<String> set) { - for (String field : mFields.keySet()) { - set.add(field); - } - - for (Pair<String, Integer> superClass : mSuperClasses) { - ApiClass clz = info.getClass(superClass.getFirst()); - assert clz != null : superClass.getSecond(); - clz.addAllFields(info, set); - } - - // Get methods from implemented interfaces as well; - for (Pair<String, Integer> superClass : mInterfaces) { - ApiClass clz = info.getClass(superClass.getFirst()); - assert clz != null : superClass.getSecond(); - clz.addAllFields(info, set); - } - } - - @Override - public int compareTo(@NonNull ApiClass other) { - return mName.compareTo(other.mName); - } - - /* This code can be used to scan through all the fields and look for fields - that have moved to a higher class: - Field android/view/MotionEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9 - Field android/provider/ContactsContract$CommonDataKinds$Organization#PHONETIC_NAME has api=5 but parent android/provider/ContactsContract$ContactNameColumns provides it as 11 - Field android/widget/ListView#CHOICE_MODE_MULTIPLE has api=1 but parent android/widget/AbsListView provides it as 11 - Field android/widget/ListView#CHOICE_MODE_NONE has api=1 but parent android/widget/AbsListView provides it as 11 - Field android/widget/ListView#CHOICE_MODE_SINGLE has api=1 but parent android/widget/AbsListView provides it as 11 - Field android/view/KeyEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9 - This is used for example in the ApiDetector to filter out warnings which result - when people follow Eclipse's advice to replace - ListView.CHOICE_MODE_MULTIPLE - references with - AbsListView.CHOICE_MODE_MULTIPLE - since the latter has API=11 and the former has API=1; since the constant is unchanged - between the two, and the literal is copied into the class, using the AbsListView - reference works. - public void checkFields(Api info) { - fieldLoop: - for (String field : mFields.keySet()) { - Integer since = getField(field, info); - if (since == null || since == Integer.MAX_VALUE) { - continue; - } - - for (Pair<String, Integer> superClass : mSuperClasses) { - ApiClass clz = info.getClass(superClass.getFirst()); - assert clz != null : superClass.getSecond(); - if (clz != null) { - Integer superSince = clz.getField(field, info); - if (superSince == Integer.MAX_VALUE) { - continue; - } - - if (superSince != null && superSince > since) { - String declaredIn = clz.findFieldDeclaration(info, field); - System.out.println("Field " + getName() + "#" + field + " has api=" - + since + " but parent " + declaredIn + " provides it as " - + superSince); - continue fieldLoop; - } - } - } - - // Get methods from implemented interfaces as well; - for (Pair<String, Integer> superClass : mInterfaces) { - ApiClass clz = info.getClass(superClass.getFirst()); - assert clz != null : superClass.getSecond(); - if (clz != null) { - Integer superSince = clz.getField(field, info); - if (superSince == Integer.MAX_VALUE) { - continue; - } - if (superSince != null && superSince > since) { - String declaredIn = clz.findFieldDeclaration(info, field); - System.out.println("Field " + getName() + "#" + field + " has api=" - + since + " but parent " + declaredIn + " provides it as " - + superSince); - continue fieldLoop; - } - } - } - } - } - - private String findFieldDeclaration(Api info, String name) { - if (mFields.containsKey(name)) { - return getName(); - } - for (Pair<String, Integer> superClass : mSuperClasses) { - ApiClass clz = info.getClass(superClass.getFirst()); - assert clz != null : superClass.getSecond(); - if (clz != null) { - String declaredIn = clz.findFieldDeclaration(info, name); - if (declaredIn != null) { - return declaredIn; - } - } - } - - // Get methods from implemented interfaces as well; - for (Pair<String, Integer> superClass : mInterfaces) { - ApiClass clz = info.getClass(superClass.getFirst()); - assert clz != null : superClass.getSecond(); - if (clz != null) { - String declaredIn = clz.findFieldDeclaration(info, name); - if (declaredIn != null) { - return declaredIn; - } - } - } - - return null; - } - */ -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java deleted file mode 100644 index fb720fe..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java +++ /dev/null
@@ -1,1974 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.repository.GradleVersion; -import com.android.repository.Revision; -import com.android.repository.api.LocalPackage; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.BuildToolInfo; -import com.android.sdklib.SdkVersionInfo; -import com.android.sdklib.repository.AndroidSdkHandler; -import com.android.tools.klint.client.api.*; -import com.android.tools.klint.detector.api.*; -import com.android.tools.klint.detector.api.Detector.ClassScanner; -import com.intellij.psi.*; -import com.intellij.psi.util.MethodSignatureUtil; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.android.inspections.klint.IntellijLintUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.android.SdkConstants.*; -import static com.android.tools.klint.detector.api.ClassContext.getFqcn; -import static com.android.utils.SdkUtils.getResourceFieldName; - -/** - * Looks for usages of APIs that are not supported in all the versions targeted - * by this application (according to its minimum API requirement in the manifest). - */ -public class ApiDetector extends ResourceXmlDetector - implements ClassScanner, Detector.UastScanner { - - private static final String ATTR_WIDTH = "width"; - private static final String ATTR_HEIGHT = "height"; - private static final String ATTR_SUPPORTS_RTL = "supportsRtl"; - - public static final String REQUIRES_API_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "RequiresApi"; //$NON-NLS-1$ - - /** Accessing an unsupported API */ - @SuppressWarnings("unchecked") - public static final Issue UNSUPPORTED = Issue.create( - "NewApi", //$NON-NLS-1$ - "Calling new methods on older versions", - - "This check scans through all the Android API calls in the application and " + - "warns about any calls that are not available on *all* versions targeted " + - "by this application (according to its minimum SDK attribute in the manifest).\n" + - "\n" + - "If you really want to use this API and don't need to support older devices just " + - "set the `minSdkVersion` in your `build.gradle` or `AndroidManifest.xml` files.\n" + - "\n" + - "If your code is *deliberately* accessing newer APIs, and you have ensured " + - "(e.g. with conditional execution) that this code will only ever be called on a " + - "supported platform, then you can annotate your class or method with the " + - "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " + - "`@TargetApi(11)`, such that this check considers 11 rather than your manifest " + - "file's minimum SDK as the required API level.\n" + - "\n" + - "If you are deliberately setting `android:` attributes in style definitions, " + - "make sure you place this in a `values-vNN` folder in order to avoid running " + - "into runtime conflicts on certain devices where manufacturers have added " + - "custom attributes whose ids conflict with the new ones on later platforms.\n" + - "\n" + - "Similarly, you can use tools:targetApi=\"11\" in an XML file to indicate that " + - "the element will only be inflated in an adequate context.", - Category.CORRECTNESS, - 6, - Severity.ERROR, - new Implementation( - ApiDetector.class, - EnumSet.of(Scope.JAVA_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST), - Scope.JAVA_FILE_SCOPE, - Scope.RESOURCE_FILE_SCOPE, - Scope.MANIFEST_SCOPE)); - - /** Accessing an inlined API on older platforms */ - public static final Issue INLINED = Issue.create( - "InlinedApi", //$NON-NLS-1$ - "Using inlined constants on older versions", - - "This check scans through all the Android API field references in the application " + - "and flags certain constants, such as static final integers and Strings, " + - "which were introduced in later versions. These will actually be copied " + - "into the class files rather than being referenced, which means that " + - "the value is available even when running on older devices. In some " + - "cases that's fine, and in other cases it can result in a runtime " + - "crash or incorrect behavior. It depends on the context, so consider " + - "the code carefully and device whether it's safe and can be suppressed " + - "or whether the code needs tbe guarded.\n" + - "\n" + - "If you really want to use this API and don't need to support older devices just " + - "set the `minSdkVersion` in your `build.gradle` or `AndroidManifest.xml` files." + - "\n" + - "If your code is *deliberately* accessing newer APIs, and you have ensured " + - "(e.g. with conditional execution) that this code will only ever be called on a " + - "supported platform, then you can annotate your class or method with the " + - "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " + - "`@TargetApi(11)`, such that this check considers 11 rather than your manifest " + - "file's minimum SDK as the required API level.\n", - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - ApiDetector.class, - Scope.JAVA_FILE_SCOPE)); - - /** Method conflicts with new inherited method */ - public static final Issue OVERRIDE = Issue.create( - "Override", //$NON-NLS-1$ - "Method conflicts with new inherited method", - - "Suppose you are building against Android API 8, and you've subclassed Activity. " + - "In your subclass you add a new method called `isDestroyed`(). At some later point, " + - "a method of the same name and signature is added to Android. Your method will " + - "now override the Android method, and possibly break its contract. Your method " + - "is not calling `super.isDestroyed()`, since your compilation target doesn't " + - "know about the method.\n" + - "\n" + - "The above scenario is what this lint detector looks for. The above example is " + - "real, since `isDestroyed()` was added in API 17, but it will be true for *any* " + - "method you have added to a subclass of an Android class where your build target " + - "is lower than the version the method was introduced in.\n" + - "\n" + - "To fix this, either rename your method, or if you are really trying to augment " + - "the builtin method if available, switch to a higher build target where you can " + - "deliberately add `@Override` on your overriding method, and call `super` if " + - "appropriate etc.\n", - Category.CORRECTNESS, - 6, - Severity.ERROR, - new Implementation( - ApiDetector.class, - Scope.CLASS_FILE_SCOPE)); - - /** Attribute unused on older versions */ - public static final Issue UNUSED = Issue.create( - "UnusedAttribute", //$NON-NLS-1$ - "Attribute unused on older versions", - - "This check finds attributes set in XML files that were introduced in a version " + - "newer than the oldest version targeted by your application (with the " + - "`minSdkVersion` attribute).\n" + - "\n" + - "This is not an error; the application will simply ignore the attribute. However, " + - "if the attribute is important to the appearance of functionality of your " + - "application, you should consider finding an alternative way to achieve the " + - "same result with only available attributes, and then you can optionally create " + - "a copy of the layout in a layout-vNN folder which will be used on API NN or " + - "higher where you can take advantage of the newer attribute.\n" + - "\n" + - "Note: This check does not only apply to attributes. For example, some tags can be " + - "unused too, such as the new `<tag>` element in layouts introduced in API 21.", - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - ApiDetector.class, - Scope.RESOURCE_FILE_SCOPE)); - - private static final String TAG_RIPPLE = "ripple"; - private static final String TAG_VECTOR = "vector"; - private static final String TAG_ANIMATED_VECTOR = "animated-vector"; - private static final String TAG_ANIMATED_SELECTOR = "animated-selector"; - - private static final String SDK_INT = "SDK_INT"; - private static final String REFLECTIVE_OPERATION_EXCEPTION = "java.lang.ReflectiveOperationException"; - public static final String ERROR = "error"; - - private ApiLookup mApiDatabase; - private boolean mWarnedMissingDb; - private int mMinApi = -1; - - /** Constructs a new API check */ - public ApiDetector() { - } - - @Override - public void beforeCheckProject(@NonNull Context context) { - if (mApiDatabase == null) { - mApiDatabase = ApiLookup.get(context.getClient()); - // We can't look up the minimum API required by the project here: - // The manifest file hasn't been processed yet in the -before- project hook. - // For now it's initialized lazily in getMinSdk(Context), but the - // lint infrastructure should be fixed to parse manifest file up front. - - if (mApiDatabase == null && !mWarnedMissingDb) { - mWarnedMissingDb = true; - context.report(IssueRegistry.LINT_ERROR, Location.create(context.file), - "Can't find API database; API check not performed"); - } else { - // See if you don't have at least version 23.0.1 of platform tools installed - AndroidSdkHandler sdk = context.getClient().getSdk(); - if (sdk == null) { - return; - } - LocalPackage pkgInfo = sdk.getLocalPackage(SdkConstants.FD_PLATFORM_TOOLS, - context.getClient().getRepositoryLogger()); - if (pkgInfo == null) { - return; - } - Revision revision = pkgInfo.getVersion(); - - // The platform tools must be at at least the same revision - // as the compileSdkVersion! - // And as a special case, for 23, they must be at 23.0.1 - // because 23.0.0 accidentally shipped without Android M APIs. - int compileSdkVersion = context.getProject().getBuildSdk(); - if (compileSdkVersion == 23) { - if (revision.getMajor() > 23 || revision.getMajor() == 23 - && (revision.getMinor() > 0 || revision.getMicro() > 0)) { - return; - } - } else if (compileSdkVersion <= revision.getMajor()) { - return; - } - - // Pick a location: when incrementally linting in the IDE, tie - // it to the current file - List<File> currentFiles = context.getProject().getSubset(); - Location location; - if (currentFiles != null && currentFiles.size() == 1) { - File file = currentFiles.get(0); - String contents = context.getClient().readFile(file); - int firstLineEnd = contents.indexOf('\n'); - if (firstLineEnd == -1) { - firstLineEnd = contents.length(); - } - location = Location.create(file, - new DefaultPosition(0, 0, 0), new - DefaultPosition(0, firstLineEnd, firstLineEnd)); - } else { - location = Location.create(context.file); - } - context.report(UNSUPPORTED, - location, - String.format("The SDK platform-tools version (%1$s) is too old " - + " to check APIs compiled with API %2$d; please update", - revision.toShortString(), - compileSdkVersion)); - } - } - } - - // ---- Implements XmlScanner ---- - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return true; - } - - @Override - public Collection<String> getApplicableElements() { - return ALL; - } - - @Override - public Collection<String> getApplicableAttributes() { - return ALL; - } - - @Override - public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { - if (mApiDatabase == null) { - return; - } - - int attributeApiLevel = -1; - if (ANDROID_URI.equals(attribute.getNamespaceURI())) { - String name = attribute.getLocalName(); - if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) && - !(name.equals(ATTR_ID)))) { - String owner = "android/R$attr"; //$NON-NLS-1$ - attributeApiLevel = mApiDatabase.getFieldVersion(owner, name); - int minSdk = getMinSdk(context); - if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion() - && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement()) - && !isBenignUnusedAttribute(name) - && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) { - if (RtlDetector.isRtlAttributeName(name) || ATTR_SUPPORTS_RTL.equals(name)) { - // No need to warn for example that - // "layout_alignParentEnd will only be used in API level 17 and higher" - // since we have a dedicated RTL lint rule dealing with those attributes - - // However, paddingStart in particular is known to cause crashes - // when used on TextViews (and subclasses of TextViews), on some - // devices, because vendor specific attributes conflict with the - // later-added framework resources, and these are apparently read - // by the text views. - // - // However, as of build tools 23.0.1 aapt works around this by packaging - // the resources differently. - if (name.equals(ATTR_PADDING_START)) { - BuildToolInfo buildToolInfo = context.getProject().getBuildTools(); - Revision buildTools = buildToolInfo != null - ? buildToolInfo.getRevision() : null; - boolean isOldBuildTools = buildTools != null && - (buildTools.getMajor() < 23 || buildTools.getMajor() == 23 - && buildTools.getMinor() == 0 && buildTools.getMicro() == 0); - if ((buildTools == null || isOldBuildTools) && - viewMayExtendTextView(attribute.getOwnerElement())) { - Location location = context.getLocation(attribute); - String message = String.format( - "Attribute `%1$s` referenced here can result in a crash on " - + "some specific devices older than API %2$d " - + "(current min is %3$d)", - attribute.getLocalName(), attributeApiLevel, minSdk); - //noinspection VariableNotUsedInsideIf - if (buildTools != null) { - message = String.format("Upgrade `buildToolsVersion` from " - + "`%1$s` to at least `23.0.1`; if not, ", - buildTools.toShortString()) - + Character.toLowerCase(message.charAt(0)) - + message.substring(1); - } - context.report(UNSUPPORTED, attribute, location, message); - } - } - } else { - Location location = context.getLocation(attribute); - String message = String.format( - "Attribute `%1$s` is only used in API level %2$d and higher " - + "(current min is %3$d)", - attribute.getLocalName(), attributeApiLevel, minSdk); - context.report(UNUSED, attribute, location, message); - } - } - } - - // Special case: - // the dividers attribute is present in API 1, but it won't be read on older - // versions, so don't flag the common pattern - // android:divider="?android:attr/dividerHorizontal" - // since this will work just fine. See issue 67440 for more. - if (name.equals("divider")) { - return; - } - } - - String value = attribute.getValue(); - String owner = null; - String name = null; - String prefix; - if (value.startsWith(ANDROID_PREFIX)) { - prefix = ANDROID_PREFIX; - } else if (value.startsWith(ANDROID_THEME_PREFIX)) { - prefix = ANDROID_THEME_PREFIX; - if (context.getResourceFolderType() == ResourceFolderType.DRAWABLE) { - int api = 21; - int minSdk = getMinSdk(context); - if (api > minSdk && api > context.getFolderVersion() - && api > getLocalMinSdk(attribute.getOwnerElement())) { - Location location = context.getLocation(attribute); - String message; - message = String.format( - "Using theme references in XML drawables requires API level %1$d " - + "(current min is %2$d)", api, - minSdk); - context.report(UNSUPPORTED, attribute, location, message); - // Don't flag individual theme attribute requirements here, e.g. once - // we've told you that you need at least v21 to reference themes, we don't - // need to also tell you that ?android:selectableItemBackground requires - // API level 11 - return; - } - } - } else if (value.startsWith(PREFIX_ANDROID) && ATTR_NAME.equals(attribute.getName()) - && TAG_ITEM.equals(attribute.getOwnerElement().getTagName()) - && attribute.getOwnerElement().getParentNode() != null - && TAG_STYLE.equals(attribute.getOwnerElement().getParentNode().getNodeName())) { - owner = "android/R$attr"; //$NON-NLS-1$ - name = value.substring(PREFIX_ANDROID.length()); - prefix = null; - } else if (value.startsWith(PREFIX_ANDROID) && ATTR_PARENT.equals(attribute.getName()) - && TAG_STYLE.equals(attribute.getOwnerElement().getTagName())) { - owner = "android/R$style"; //$NON-NLS-1$ - name = getResourceFieldName(value.substring(PREFIX_ANDROID.length())); - prefix = null; - } else { - return; - } - - if (owner == null) { - // Convert @android:type/foo into android/R$type and "foo" - int index = value.indexOf('/', prefix.length()); - if (index != -1) { - owner = "android/R$" //$NON-NLS-1$ - + value.substring(prefix.length(), index); - name = getResourceFieldName(value.substring(index + 1)); - } else if (value.startsWith(ANDROID_THEME_PREFIX)) { - owner = "android/R$attr"; //$NON-NLS-1$ - name = value.substring(ANDROID_THEME_PREFIX.length()); - } else { - return; - } - } - int api = mApiDatabase.getFieldVersion(owner, name); - int minSdk = getMinSdk(context); - if (api > minSdk && api > context.getFolderVersion() - && api > getLocalMinSdk(attribute.getOwnerElement())) { - // Don't complain about resource references in the tools namespace, - // such as for example "tools:layout="@android:layout/list_content", - // used only for designtime previews - if (TOOLS_URI.equals(attribute.getNamespaceURI())) { - return; - } - - //noinspection StatementWithEmptyBody - if (attributeApiLevel >= api) { - // The attribute will only be *read* on platforms >= attributeApiLevel. - // If this isn't lower than the attribute reference's API level, it - // won't be a problem - } else if (attributeApiLevel > minSdk) { - String attributeName = attribute.getLocalName(); - Location location = context.getLocation(attribute); - String message = String.format( - "`%1$s` requires API level %2$d (current min is %3$d), but note " - + "that attribute `%4$s` is only used in API level %5$d " - + "and higher", - name, api, minSdk, attributeName, attributeApiLevel); - context.report(UNSUPPORTED, attribute, location, message); - } else { - Location location = context.getLocation(attribute); - String message = String.format( - "`%1$s` requires API level %2$d (current min is %3$d)", - value, api, minSdk); - context.report(UNSUPPORTED, attribute, location, message); - } - } - } - - /** - * Returns true if the view tag is possibly a text view. It may not be certain, - * but will err on the side of caution (for example, any custom view is considered - * to be a potential text view.) - */ - private static boolean viewMayExtendTextView(@NonNull Element element) { - String tag = element.getTagName(); - if (tag.equals(VIEW_TAG)) { - tag = element.getAttribute(ATTR_CLASS); - if (tag == null || tag.isEmpty()) { - return false; - } - } - - //noinspection SimplifiableIfStatement - if (tag.indexOf('.') != -1) { - // Custom views: not sure. Err on the side of caution. - return true; - - } - - return tag.contains("Text") // TextView, EditText, etc - || tag.contains(BUTTON) // Button, ToggleButton, etc - || tag.equals("DigitalClock") - || tag.equals("Chronometer") - || tag.equals(CHECK_BOX) - || tag.equals(SWITCH); - } - - /** - * Returns true if this attribute is in a drawable document with one of the - * root tags that require API 21 - */ - private static boolean isAlreadyWarnedDrawableFile(@NonNull XmlContext context, - @NonNull Attr attribute, int attributeApiLevel) { - // Don't complain if it's in a drawable file where we've already - // flagged the root drawable type as being unsupported - if (context.getResourceFolderType() == ResourceFolderType.DRAWABLE - && attributeApiLevel == 21) { - String root = attribute.getOwnerDocument().getDocumentElement().getTagName(); - if (TAG_RIPPLE.equals(root) - || TAG_VECTOR.equals(root) - || TAG_ANIMATED_VECTOR.equals(root) - || TAG_ANIMATED_SELECTOR.equals(root)) { - return true; - } - } - - return false; - } - - /** - * Is the given attribute a "benign" unused attribute, one we probably don't need to - * flag to the user as not applicable on all versions? These are typically attributes - * which add some nice platform behavior when available, but that are not critical - * and developers would not typically need to be aware of to try to implement workarounds - * on older platforms. - */ - private static boolean isBenignUnusedAttribute(@NonNull String name) { - return ATTR_LABEL_FOR.equals(name) - || ATTR_TEXT_IS_SELECTABLE.equals(name) - || "textAlignment".equals(name) - || ATTR_FULL_BACKUP_CONTENT.equals(name); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - if (mApiDatabase == null) { - return; - } - - String tag = element.getTagName(); - - ResourceFolderType folderType = context.getResourceFolderType(); - if (folderType != ResourceFolderType.LAYOUT) { - if (folderType == ResourceFolderType.DRAWABLE) { - checkElement(context, element, TAG_VECTOR, 21, "1.4", UNSUPPORTED); - checkElement(context, element, TAG_RIPPLE, 21, null, UNSUPPORTED); - checkElement(context, element, TAG_ANIMATED_SELECTOR, 21, null, UNSUPPORTED); - checkElement(context, element, TAG_ANIMATED_VECTOR, 21, null, UNSUPPORTED); - checkElement(context, element, "drawable", 24, null, UNSUPPORTED); - if ("layer-list".equals(tag)) { - checkLevelList(context, element); - } else if (tag.contains(".")) { - checkElement(context, element, tag, 24, null, UNSUPPORTED); - } - } - if (element.getParentNode().getNodeType() != Node.ELEMENT_NODE) { - // Root node - return; - } - NodeList childNodes = element.getChildNodes(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node textNode = childNodes.item(i); - if (textNode.getNodeType() == Node.TEXT_NODE) { - String text = textNode.getNodeValue(); - if (text.contains(ANDROID_PREFIX)) { - text = text.trim(); - // Convert @android:type/foo into android/R$type and "foo" - int index = text.indexOf('/', ANDROID_PREFIX.length()); - if (index != -1) { - String typeString = text.substring(ANDROID_PREFIX.length(), index); - if (ResourceType.getEnum(typeString) != null) { - String owner = "android/R$" + typeString; - String name = getResourceFieldName(text.substring(index + 1)); - int api = mApiDatabase.getFieldVersion(owner, name); - int minSdk = getMinSdk(context); - if (api > minSdk && api > context.getFolderVersion() - && api > getLocalMinSdk(element)) { - Location location = context.getLocation(textNode); - String message = String.format( - "`%1$s` requires API level %2$d (current min is %3$d)", - text, api, minSdk); - context.report(UNSUPPORTED, element, location, message); - } - } - } - } - } - } - } else { - if (VIEW_TAG.equals(tag)) { - tag = element.getAttribute(ATTR_CLASS); - if (tag == null || tag.isEmpty()) { - return; - } - } else { - // TODO: Complain if <tag> is used at the root level! - checkElement(context, element, TAG, 21, null, UNUSED); - } - - // Check widgets to make sure they're available in this version of the SDK. - if (tag.indexOf('.') != -1) { - // Custom views aren't in the index - return; - } - String fqn = "android/widget/" + tag; //$NON-NLS-1$ - if (tag.equals("TextureView")) { //$NON-NLS-1$ - fqn = "android/view/TextureView"; //$NON-NLS-1$ - } - // TODO: Consider other widgets outside of android.widget.* - int api = mApiDatabase.getClassVersion(fqn); - int minSdk = getMinSdk(context); - if (api > minSdk && api > context.getFolderVersion() - && api > getLocalMinSdk(element)) { - Location location = context.getLocation(element); - String message = String.format( - "View requires API level %1$d (current min is %2$d): `<%3$s>`", - api, minSdk, tag); - context.report(UNSUPPORTED, element, location, message); - } - } - } - - /** Checks whether the given element is the given tag, and if so, whether it satisfied - * the minimum version that the given tag is supported in */ - private void checkLevelList(@NonNull XmlContext context, @NonNull Element element) { - Node curr = element.getFirstChild(); - while (curr != null) { - if (curr.getNodeType() == Node.ELEMENT_NODE - && TAG_ITEM.equals(curr.getNodeName())) { - Element e = (Element) curr; - if (e.hasAttributeNS(ANDROID_URI, ATTR_WIDTH) - || e.hasAttributeNS(ANDROID_URI, ATTR_HEIGHT)) { - int attributeApiLevel = 23; // Using width and height on layer-list children requires M - int minSdk = getMinSdk(context); - if (attributeApiLevel > minSdk - && attributeApiLevel > context.getFolderVersion() - && attributeApiLevel > getLocalMinSdk(element)) { - for (String attributeName : new String[] { ATTR_WIDTH, ATTR_HEIGHT}) { - Attr attribute = e.getAttributeNodeNS(ANDROID_URI, attributeName); - if (attribute == null) { - continue; - } - Location location = context.getLocation(attribute); - String message = String.format( - "Attribute `%1$s` is only used in API level %2$d and higher " - + "(current min is %3$d)", - attribute.getLocalName(), attributeApiLevel, minSdk); - context.report(UNUSED, attribute, location, message); - } - } - } - } - curr = curr.getNextSibling(); - } - } - - /** Checks whether the given element is the given tag, and if so, whether it satisfied - * the minimum version that the given tag is supported in */ - private void checkElement(@NonNull XmlContext context, @NonNull Element element, - @NonNull String tag, int api, @Nullable String gradleVersion, @NonNull Issue issue) { - if (tag.equals(element.getTagName())) { - int minSdk = getMinSdk(context); - if (api > minSdk - && api > context.getFolderVersion() - && api > getLocalMinSdk(element) - && !featureProvidedByGradle(context, gradleVersion)) { - Location location = context.getLocation(element); - - // For the <drawable> tag we report it against the class= attribute - if ("drawable".equals(tag)) { - Attr attribute = element.getAttributeNode(ATTR_CLASS); - if (attribute == null) { - return; - } - location = context.getLocation(attribute); - tag = ATTR_CLASS; - } - - String message; - if (issue == UNSUPPORTED) { - message = String.format( - "`<%1$s>` requires API level %2$d (current min is %3$d)", tag, api, - minSdk); - if (gradleVersion != null) { - message += String.format( - " or building with Android Gradle plugin %1$s or higher", - gradleVersion); - } else if (tag.contains(".")) { - message = String.format( - "Custom drawables requires API level %1$d (current min is %2$d)", - api, minSdk); - } - } else { - assert issue == UNUSED : issue; - message = String.format( - "`<%1$s>` is only used in API level %2$d and higher " - + "(current min is %3$d)", tag, api, minSdk); - } - context.report(issue, element, location, message); - } - } - } - - protected int getMinSdk(Context context) { - if (mMinApi == -1) { - AndroidVersion minSdkVersion = context.getMainProject().getMinSdkVersion(); - mMinApi = minSdkVersion.getFeatureLevel(); - } - - return mMinApi; - } - - /** - * Returns the minimum SDK to use in the given element context, or -1 if no - * {@code tools:targetApi} attribute was found. - * - * @param element the element to look at, including parents - * @return the API level to use for this element, or -1 - */ - private static int getLocalMinSdk(@NonNull Element element) { - //noinspection ConstantConditions - while (element != null) { - String targetApi = element.getAttributeNS(TOOLS_URI, ATTR_TARGET_API); - if (targetApi != null && !targetApi.isEmpty()) { - if (Character.isDigit(targetApi.charAt(0))) { - try { - return Integer.parseInt(targetApi); - } catch (NumberFormatException e) { - break; - } - } else { - return SdkVersionInfo.getApiByBuildCode(targetApi, true); - } - } - - Node parent = element.getParentNode(); - if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) { - element = (Element) parent; - } else { - break; - } - } - - return -1; - } - - /** - * Checks if the current project supports features added in {@code minGradleVersion} version of - * the Android gradle plugin. - * - * @param context Current context. - * @param minGradleVersionString Version in which support for a given feature was added, or null - * if it's not supported at build time. - */ - private static boolean featureProvidedByGradle(@NonNull XmlContext context, - @Nullable String minGradleVersionString) { - if (minGradleVersionString == null) { - return false; - } - - GradleVersion gradleModelVersion = context.getProject().getGradleModelVersion(); - if (gradleModelVersion != null) { - GradleVersion minVersion = GradleVersion.tryParse(minGradleVersionString); - if (minVersion != null - && gradleModelVersion.compareIgnoringQualifiers(minVersion) >= 0) { - return true; - } - } - return false; - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - if (mApiDatabase == null) { - return new AbstractUastVisitor() { - @Override - public boolean visitElement(@NotNull UElement element) { - // No-op. Workaround for super currently calling - // ProgressIndicatorProvider.checkCanceled(); - return super.visitElement(element); - } - }; - } - return new ApiVisitor(context); - } - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(9); - types.add(UImportStatement.class); - types.add(USimpleNameReferenceExpression.class); - types.add(UVariable.class); - types.add(UTryExpression.class); - types.add(UBinaryExpressionWithType.class); - types.add(UBinaryExpression.class); - types.add(UCallExpression.class); - types.add(UClass.class); - types.add(UTypeReferenceExpression.class); - types.add(UClassLiteralExpression.class); - types.add(UMethod.class); - return types; - } - - /** - * Checks whether the given instruction is a benign usage of a constant defined in - * a later version of Android than the application's {@code minSdkVersion}. - * - * @param node the instruction to check - * @param name the name of the constant - * @param owner the field owner - * @return true if the given usage is safe on older versions than the introduction - * level of the constant - */ - private static boolean isBenignConstantUsage( - @Nullable UElement node, - @NonNull String name, - @NonNull String owner - ) { - if (owner.equals("android/os/Build$VERSION_CODES")) { //$NON-NLS-1$ - // These constants are required for compilation, not execution - // and valid code checks it even on older platforms - return true; - } - if (owner.equals("android/view/ViewGroup$LayoutParams") //$NON-NLS-1$ - && name.equals("MATCH_PARENT")) { //$NON-NLS-1$ - return true; - } - if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$ - && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$ - || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$ - || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$ - // android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1, - // but in API 11 it was moved up to the parent class AbsListView. - // Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11, - // but the constant is the same as the older version, so accept this without - // warning. - return true; - } - - // Gravity#START and Gravity#END are okay; these were specifically written to - // be backwards compatible (by using the same lower bits for START as LEFT and - // for END as RIGHT) - if ("android/view/Gravity".equals(owner) //$NON-NLS-1$ - && ("START".equals(name) || "END".equals(name))) { //$NON-NLS-1$ //$NON-NLS-2$ - return true; - } - - if (node == null) { - return false; - } - - // It's okay to reference the constant as a case constant (since that - // code path won't be taken) or in a condition of an if statement - UElement curr = node.getUastParent(); - while (curr != null) { - if (curr instanceof USwitchClauseExpression) { - List<UExpression> caseValues = ((USwitchClauseExpression) curr).getCaseValues(); - if (caseValues != null) { - for (UExpression condition : caseValues) { - if (condition != null && UastUtils.isChildOf(node, condition, false)) { - return true; - } - } - } - return false; - } else if (curr instanceof UIfExpression) { - UExpression condition = ((UIfExpression) curr).getCondition(); - return UastUtils.isChildOf(node, condition, false); - } else if (curr instanceof UMethod || curr instanceof UClass) { - break; - } - curr = curr.getUastParent(); - } - - return false; - } - - public static int getRequiredVersion(String errorMessage) { - Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); - Matcher matcher = pattern.matcher(errorMessage); - if (matcher.find()) { - return Integer.parseInt(matcher.group(1)); - } - - return -1; - } - - private final class ApiVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - - private ApiVisitor(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitTypeReferenceExpression(@NotNull UTypeReferenceExpression node) { - checkType(node.getType(), node); - return super.visitTypeReferenceExpression(node); - } - - @Override - public boolean visitClassLiteralExpression(@NotNull UClassLiteralExpression node) { - checkType(node.getType(), node); - return super.visitClassLiteralExpression(node); - } - - @Override - public boolean visitImportStatement(@NotNull UImportStatement statement) { - if (!statement.isOnDemand()) { - PsiElement resolved = statement.resolve(); - if (resolved instanceof PsiField) { - checkField(statement, (PsiField)resolved); - } - } - - return super.visitImportStatement(statement); - } - - @Override - public boolean visitSimpleNameReferenceExpression(@NotNull USimpleNameReferenceExpression node) { - PsiElement resolved = node.resolve(); - if (resolved instanceof PsiField) { - checkField(node, (PsiField)resolved); - } - - return super.visitSimpleNameReferenceExpression(node); - } - - @Override - public boolean visitBinaryExpressionWithType(@NotNull UBinaryExpressionWithType node) { - visitTypeCastExpression(node); - return super.visitBinaryExpressionWithType(node); - } - - private void visitTypeCastExpression(UBinaryExpressionWithType expression) { - UExpression operand = expression.getOperand(); - - PsiType operandType = operand.getExpressionType(); - PsiType castType = expression.getType(); - - if (castType.equals(operandType)) { - return; - } - if (!(operandType instanceof PsiClassType)) { - return; - } - if (!(castType instanceof PsiClassType)) { - return; - } - PsiClassType classType = (PsiClassType)operandType; - PsiClassType interfaceType = (PsiClassType)castType; - checkCast(expression, classType, interfaceType); - } - - private void checkCast(@NonNull UElement node, @NonNull PsiClassType classType, - @NonNull PsiClassType interfaceType) { - if (classType.equals(interfaceType)) { - return; - } - JavaEvaluator evaluator = mContext.getEvaluator(); - String classTypeInternal = evaluator.getInternalName(classType); - String interfaceTypeInternal = evaluator.getInternalName(interfaceType); - if ("java/lang/Object".equals(interfaceTypeInternal)) { - return; - } - - int api = mApiDatabase.getValidCastVersion(classTypeInternal, interfaceTypeInternal); - if (api == -1) { - return; - } - - int minSdk = getMinSdk(mContext); - if (api <= minSdk) { - return; - } - - if (isSuppressed(api, node, minSdk, mContext, UNSUPPORTED)) { - return; - } - - Location location = mContext.getUastLocation(node); - String message = String.format("Cast from %1$s to %2$s requires API level %3$d (current min is %4$d)", - UastLintUtils.getClassName(classType), - UastLintUtils.getClassName(interfaceType), api, minSdk); - mContext.report(UNSUPPORTED, location, message); - } - - @Override - public boolean visitClass(@NotNull UClass aClass) { - // Check for repeatable annotations - if (aClass.isAnnotationType()) { - PsiModifierList modifierList = aClass.getModifierList(); - if (modifierList != null) { - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - String name = annotation.getQualifiedName(); - if ("java.lang.annotation.Repeatable".equals(name)) { - int api = 24; // minSdk for repeatable annotations - int minSdk = getMinSdk(mContext); - if (!isSuppressed(api, aClass, minSdk, mContext, UNSUPPORTED)) { - Location location = mContext.getLocation(annotation); - String message = String.format("Repeatable annotation requires " - + "API level %1$d (current min is %2$d)", api, minSdk); - mContext.report(UNSUPPORTED, annotation, location, message); - } - } else if ("java.lang.annotation.Target".equals(name)) { - PsiNameValuePair[] attributes = annotation.getParameterList() - .getAttributes(); - for (PsiNameValuePair pair : attributes) { - PsiAnnotationMemberValue value = pair.getValue(); - if (value instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue array - = (PsiArrayInitializerMemberValue) value; - for (PsiAnnotationMemberValue t : array.getInitializers()) { - checkAnnotationTarget(t, modifierList); - } - } else if (value != null) { - checkAnnotationTarget(value, modifierList); - } - } - } - } - } - } else { - for (UTypeReferenceExpression t : aClass.getUastSuperTypes()) { - checkType(t.getType(), t); - } - } - - return super.visitClass(aClass); - } - - private void checkAnnotationTarget(@NonNull PsiAnnotationMemberValue element, - PsiModifierList modifierList) { - if (element instanceof UReferenceExpression) { - UReferenceExpression ref = (UReferenceExpression) element; - String referenceName = UastLintUtils.getReferenceName(ref); - if ("TYPE_PARAMETER".equals(referenceName) - || "TYPE_USE".equals(referenceName)) { - PsiAnnotation retention = modifierList - .findAnnotation("java.lang.annotation.Retention"); - if (retention == null || - retention.getText().contains("RUNTIME")) { - Location location = mContext.getLocation(element); - String message = String.format("Type annotations are not " - + "supported in Android: %1$s", referenceName); - mContext.report(UNSUPPORTED, element, location, message); - } - } - } - } - - @Override - public boolean visitCallExpression(@NotNull UCallExpression expression) { - checkMethodCallExpression(expression); - return super.visitCallExpression(expression); - } - - private void checkMethodCallExpression(@NotNull UCallExpression expression) { - PsiMethod method = expression.resolve(); - if (method != null) { - PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - return; - } - - PsiParameterList parameterList = method.getParameterList(); - if (parameterList.getParametersCount() > 0) { - PsiParameter[] parameters = parameterList.getParameters(); - - List<UExpression> arguments = expression.getValueArguments(); - for (int i = 0; i < parameters.length; i++) { - PsiType parameterType = parameters[i].getType(); - if (parameterType instanceof PsiClassType) { - if (i >= arguments.size()) { - // We can end up with more arguments than parameters when - // there is a varargs call. - break; - } - UExpression argument = arguments.get(i); - PsiType argumentType = argument.getExpressionType(); - if (argumentType == null || parameterType.equals(argumentType) || !(argumentType instanceof PsiClassType)) { - continue; - } - checkCast(argument, (PsiClassType)argumentType, (PsiClassType)parameterType); - } - } - } - - JavaEvaluator evaluator = mContext.getEvaluator(); - String fqcn = containingClass.getQualifiedName(); - String owner = evaluator.getInternalName(containingClass); - if (owner == null) { - return; // Couldn't resolve type - } - - String name = IntellijLintUtils.getInternalMethodName(method); - String desc = IntellijLintUtils.getInternalDescription(method, false, false); - if (desc == null) { - // Couldn't compute description of method for some reason; probably - // failure to resolve parameter types - return; - } - - boolean hasApiAnnotation = false; - int api = mApiDatabase.getCallVersion(owner, name, desc); - if (api == -1) { - api = getTargetApi(method.getModifierList()); - if (api == -1 && method.isConstructor()) { - api = getTargetApi(method.getContainingClass().getModifierList()); - } - - if (api == -1) { - return; - } else { - hasApiAnnotation = true; - } - } - - int minSdk = getMinSdk(mContext); - if (api <= minSdk) { - return; - } - - // The lint API database contains two optimizations: - // First, all members that were available in API 1 are omitted from the database, since that saves - // about half of the size of the database, and for API check purposes, we don't need to distinguish - // between "doesn't exist" and "available in all versions". - // Second, all inherited members were inlined into each class, so that it doesn't have to do a - // repeated search up the inheritance chain. - // - // Unfortunately, in this custom PSI detector, we look up the real resolved method, which can sometimes - // have a different minimum API. - // - // For example, SQLiteDatabase had a close() method from API 1. Therefore, calling SQLiteDatabase is supported - // in all versions. However, it extends SQLiteClosable, which in API 16 added "implements Closable". In - // this detector, if we have the following code: - // void test(SQLiteDatabase db) { db.close } - // here the call expression will be the close method on type SQLiteClosable. And that will result in an API - // requirement of API 16, since the close method it now resolves to is in API 16. - // - // To work around this, we can now look up the type of the call expression ("db" in the above, but it could - // have been more complicated), and if that's a different type than the type of the method, we look up - // *that* method from lint's database instead. Furthermore, it's possible for that method to return "-1" - // and we can't tell if that means "doesn't exist" or "present in API 1", we then check the package prefix - // to see whether we know it's an API method whose members should all have been inlined. - if (!hasApiAnnotation && UastExpressionUtils.isMethodCall(expression)) { - UExpression qualifier = expression.getReceiver(); - if (qualifier != null && !(qualifier instanceof UThisExpression) && !(qualifier instanceof USuperExpression)) { - PsiType type = qualifier.getExpressionType(); - if (type != null && type instanceof PsiClassType) { - String expressionOwner = evaluator.getInternalName((PsiClassType)type); - if (expressionOwner != null && !expressionOwner.equals(owner)) { - int specificApi = mApiDatabase.getCallVersion(expressionOwner, name, desc); - if (specificApi == -1) { - if (ApiLookup.isRelevantOwner(expressionOwner)) { - return; - } - } else if (specificApi <= minSdk) { - return; - } else { - // For example, for Bundle#getString(String,String) the API level is 12, whereas for - // BaseBundle#getString(String,String) the API level is 21. If the code specified a Bundle instead of - // a BaseBundle, reported the Bundle level in the error message instead. - if (specificApi < api) { - api = specificApi; - fqcn = expressionOwner.replace('/', '.'); - } - api = Math.min(specificApi, api); - } - } - } - } else { - // Unqualified call; need to search in our super hierarchy - PsiClass cls = null; - PsiType receiverType = expression.getReceiverType(); - if (receiverType instanceof PsiClassType) { - cls = ((PsiClassType) receiverType).resolve(); - } - - while (cls != null) { - if (cls instanceof PsiAnonymousClass) { - // If it's an unqualified call in an anonymous class, we need to rely on the - // resolve method to find out whether the method is picked up from the anonymous - // class chain or any outer classes - boolean found = false; - PsiClassType anonymousBaseType = ((PsiAnonymousClass)cls).getBaseClassType(); - PsiClass anonymousBase = anonymousBaseType.resolve(); - if (anonymousBase != null && anonymousBase.isInheritor(containingClass, true)) { - cls = anonymousBase; - found = true; - } else { - PsiClass surroundingBaseType = PsiTreeUtil.getParentOfType(cls, PsiClass.class, true); - if (surroundingBaseType != null && surroundingBaseType.isInheritor(containingClass, true)) { - cls = surroundingBaseType; - found = true; - } - } - if (!found) { - break; - } - } - String expressionOwner = evaluator.getInternalName(cls); - if (expressionOwner == null) { - break; - } - int specificApi = mApiDatabase.getCallVersion(expressionOwner, name, desc); - if (specificApi == -1) { - if (ApiLookup.isRelevantOwner(expressionOwner)) { - return; - } - } else if (specificApi <= minSdk) { - return; - } else { - if (specificApi < api) { - api = specificApi; - fqcn = expressionOwner.replace('/', '.'); - } - api = Math.min(specificApi, api); - break; - } - cls = cls.getSuperClass(); - } - } - } - - if (isSuppressed(api, expression, minSdk, mContext, UNSUPPORTED)) { - return; - } - - // If you're simply calling super.X from method X, even if method X is in a higher API level than the minSdk, we're - // generally safe; that method should only be called by the framework on the right API levels. (There is a danger of - // somebody calling that method locally in other contexts, but this is hopefully unlikely.) - if (UastExpressionUtils.isMethodCall(expression)) { - if (expression.getReceiver() instanceof USuperExpression) { - PsiMethod containingMethod = UastUtils.getContainingMethod(expression); - if (containingMethod != null && name.equals(containingMethod.getName()) - && MethodSignatureUtil.areSignaturesEqual(method, containingMethod) - // We specifically exclude constructors from this check, because we do want to flag constructors requiring the - // new API level; it's highly likely that the constructor is called by local code so you should specifically - // investigate this as a developer - && !method.isConstructor()) { - return; - } - } - } - - UElement locationNode = expression.getMethodIdentifier(); - if (locationNode == null) { - locationNode = expression; - } - Location location = mContext.getUastLocation(locationNode); - String message = String.format("Call requires API level %1$d (current min is %2$d): %3$s", api, minSdk, - fqcn + '#' + method.getName()); - - mContext.report(UNSUPPORTED, location, message); - } - } - - @Override - public boolean visitLocalVariable(ULocalVariable variable) { - UExpression initializer = variable.getUastInitializer(); - if (initializer == null) { - return true; - } - - PsiType initializerType = initializer.getExpressionType(); - if (!(initializerType instanceof PsiClassType)) { - return true; - } - - PsiType interfaceType = variable.getType(); - if (initializerType.equals(interfaceType)) { - return true; - } - - if (!(interfaceType instanceof PsiClassType)) { - return true; - } - - checkCast(initializer, (PsiClassType)initializerType, (PsiClassType)interfaceType); - return true; - } - - @Override - public boolean visitBinaryExpression(@NotNull UBinaryExpression node) { - if (UastExpressionUtils.isAssignment(node)) { - visitAssignmentExpression(node); - } - - return super.visitBinaryExpression(node); - } - - private void visitAssignmentExpression(UBinaryExpression expression) { - UExpression rExpression = expression.getRightOperand(); - PsiType rhsType = rExpression.getExpressionType(); - if (!(rhsType instanceof PsiClassType)) { - return; - } - - PsiType interfaceType = expression.getLeftOperand().getExpressionType(); - if (rhsType.equals(interfaceType)) { - return; - } - - if (!(interfaceType instanceof PsiClassType)) { - return; - } - - checkCast(rExpression, (PsiClassType)rhsType, (PsiClassType)interfaceType); - } - - @Override - public boolean visitTryExpression(@NotNull UTryExpression statement) { - if (statement.getHasResources()) { - int api = 19; // minSdk for try with resources - int minSdk = getMinSdk(mContext); - - if (api > minSdk && api > getTargetApi(statement)) { - Location location = mContext.getUastLocation(statement); - String message = String.format("Try-with-resources requires " - + "API level %1$d (current min is %2$d)", api, minSdk); - LintDriver driver = mContext.getDriver(); - if (!driver.isSuppressed(mContext, UNSUPPORTED, statement)) { - mContext.report(UNSUPPORTED, statement, location, message); - } - } - } - - for (UCatchClause catchClause : statement.getCatchClauses()) { - - // Special case reflective operation exception which can be implicitly used - // with multi-catches: see issue 153406 - int minSdk = getMinSdk(mContext); - if(minSdk < 19 && isMultiCatchReflectiveOperationException(catchClause)) { - String message = String.format("Multi-catch with these reflection exceptions requires API level 19 (current min is %d) " + - "because they get compiled to the common but new super type `ReflectiveOperationException`. " + - "As a workaround either create individual catch statements, or catch `Exception`.", - minSdk); - - mContext.report(UNSUPPORTED, getCatchParametersLocation(mContext, catchClause), message); - continue; - } - - for (UTypeReferenceExpression typeReference : catchClause.getTypeReferences()) { - checkCatchTypeElement(statement, typeReference, typeReference.getType()); - } - } - - return super.visitTryExpression(statement); - } - - private void checkCatchTypeElement(@NonNull UTryExpression statement, - @NonNull UTypeReferenceExpression typeReference, - @Nullable PsiType type) { - PsiClass resolved = null; - if (type instanceof PsiClassType) { - resolved = ((PsiClassType) type).resolve(); - } - if (resolved != null) { - String signature = mContext.getEvaluator().getInternalName(resolved); - int api = mApiDatabase.getClassVersion(signature); - if (api == -1) { - return; - } - int minSdk = getMinSdk(mContext); - if (api <= minSdk) { - return; - } - int target = getTargetApi(statement); - if (target != -1 && api <= target) { - return; - } - - Location location = mContext.getUastLocation(typeReference); - String fqcn = resolved.getQualifiedName(); - String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", - api, minSdk, fqcn); - mContext.report(UNSUPPORTED, location, message); - } - } - - private void checkType(PsiType type, UElement element) { - if (!(type instanceof PsiClassType)) { - return; - } - - PsiClass resolved = ((PsiClassType) type).resolve(); - if (resolved == null) { - return; - } - - String signature = mContext.getEvaluator().getInternalName(resolved); - int api = mApiDatabase.getClassVersion(signature); - if (api == -1) { - return; - } - - int minSdk = getMinSdk(mContext); - if (api <= minSdk) { - return; - } - - if (isSuppressed(api, element, minSdk, mContext, UNSUPPORTED)) { - return; - } - - Location location = mContext.getUastLocation(element); - String fqcn = resolved.getQualifiedName(); - String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", - api, minSdk, fqcn); - mContext.report(UNSUPPORTED, location, message); - } - - /** - * Checks a Java source field reference. Returns true if the field is known - * regardless of whether it's an invalid field or not - */ - private boolean checkField(@NonNull UElement node, @NonNull PsiField field) { - PsiType type = field.getType(); - Issue issue; - if ((type instanceof PsiPrimitiveType) || LintUtils.isString(type)) { - issue = INLINED; - } else { - issue = UNSUPPORTED; - } - - String name = field.getName(); - PsiClass containingClass = field.getContainingClass(); - if (containingClass == null || name == null) { - return false; - } - String owner = mContext.getEvaluator().getInternalName(containingClass); - int api = mApiDatabase.getFieldVersion(owner, name); - if (api != -1) { - int minSdk = getMinSdk(mContext); - if (api > minSdk - && api > getTargetApi(node)) { - if (isBenignConstantUsage(node, name, owner)) { - return true; - } - - String fqcn = getFqcn(owner) + '#' + name; - - // For import statements, place the underlines only under the - // reference, not the import and static keywords - if (node instanceof UImportStatement) { - UElement reference = ((UImportStatement) node).getImportReference(); - if (reference != null) { - node = reference; - } - } - - LintDriver driver = mContext.getDriver(); - if (driver.isSuppressed(mContext, INLINED, node)) { - return true; - } - - // backwards compatibility: lint used to use this issue type so respect - // older suppress annotations - if (driver.isSuppressed(mContext, UNSUPPORTED, node)) { - return true; - } - if (isWithinVersionCheckConditional(node, api, mContext)) { - return true; - } - if (isPrecededByVersionCheckExit(node, api, mContext)) { - return true; - } - - String message = String.format( - "Field requires API level %1$d (current min is %2$d): `%3$s`", - api, minSdk, fqcn); - - Location location = mContext.getUastLocation(node); - mContext.report(issue, node, location, message); - } - - return true; - } - - return false; - } - } - - private static boolean isSuppressed(int api, UElement element, int minSdk, JavaContext context, Issue issue) { - if (api <= minSdk) { - return true; - } - - int target = getTargetApi(element); - if (target != -1) { - if (api <= target) { - return true; - } - } - - LintDriver driver = context.getDriver(); - if(driver.isSuppressed(context, issue, element)) { - return true; - } - - if (isWithinVersionCheckConditional(element, api, context)) { - return true; - } - if (isPrecededByVersionCheckExit(element, api, context)) { - return true; - } - - return false; - } - - private static int getTargetApi(@Nullable UElement scope) { - while (scope != null) { - if (scope instanceof PsiModifierListOwner) { - PsiModifierList modifierList = ((PsiModifierListOwner) scope).getModifierList(); - int targetApi = getTargetApi(modifierList); - if (targetApi != -1) { - return targetApi; - } - } - scope = scope.getUastParent(); - if (scope instanceof PsiFile) { - break; - } - } - - return -1; - } - - /** - * Returns the API level for the given AST node if specified with - * an {@code @TargetApi} annotation. - * - * @param modifierList the modifier list to check - * @return the target API level, or -1 if not specified - */ - public static int getTargetApi(@Nullable PsiModifierList modifierList) { - if (modifierList == null) { - return -1; - } - - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - String fqcn = annotation.getQualifiedName(); - if (fqcn != null && - (fqcn.equals(FQCN_TARGET_API) - || fqcn.equals(REQUIRES_API_ANNOTATION) - || fqcn.equals(TARGET_API))) { // when missing imports - PsiAnnotationParameterList parameterList = annotation.getParameterList(); - for (PsiNameValuePair pair : parameterList.getAttributes()) { - PsiAnnotationMemberValue v = pair.getValue(); - if (v instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral)v; - Object value = literal.getValue(); - if (value instanceof Integer) { - return (Integer) value; - } else if (value instanceof String) { - return codeNameToApi((String) value); - } - } else if (v instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue mv = (PsiArrayInitializerMemberValue)v; - for (PsiAnnotationMemberValue mmv : mv.getInitializers()) { - if (mmv instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral)mmv; - Object value = literal.getValue(); - if (value instanceof Integer) { - return (Integer) value; - } else if (value instanceof String) { - return codeNameToApi((String) value); - } - } - } - } else if (v instanceof PsiExpression) { - // PsiExpression nodes are not present in light classes, so - // we can use Java PSI api to get the qualified name - if (v instanceof PsiReferenceExpression) { - String name = ((PsiReferenceExpression)v).getQualifiedName(); - return codeNameToApi(name); - } else { - return codeNameToApi(v.getText()); - } - } - } - } - } - - return -1; - } - - private static int codeNameToApi(@NonNull String text) { - int dotIndex = text.lastIndexOf('.'); - if (dotIndex != -1) { - text = text.substring(dotIndex + 1); - } - - return SdkVersionInfo.getApiByBuildCode(text, true); - } - - private static class VersionCheckWithExitFinder extends AbstractUastVisitor { - - private final UExpression mExpression; - private final UElement mEndElement; - private final int mApi; - private final JavaContext mContext; - - private boolean mFound = false; - private boolean mDone = false; - - public VersionCheckWithExitFinder(UExpression expression, UElement endElement, - int api, JavaContext context) { - mExpression = expression; - - mEndElement = endElement; - mApi = api; - mContext = context; - } - - @Override - public boolean visitElement(@NotNull UElement node) { - if (mDone) { - return true; - } - - if (node.equals(mEndElement)) { - mDone = true; - } - - return mDone || !mExpression.equals(node); - } - - @Override - public boolean visitIfExpression(@NotNull UIfExpression ifStatement) { - - if (mDone) { - return true; - } - - UExpression thenBranch = ifStatement.getThenExpression(); - UExpression elseBranch = ifStatement.getElseExpression(); - - if (thenBranch != null) { - Boolean level = isVersionCheckConditional(mApi, thenBranch, ifStatement, mContext); - //noinspection VariableNotUsedInsideIf - if (level != null) { - // See if the body does an immediate return - if (isUnconditionalReturn(thenBranch)) { - mFound = true; - mDone = true; - } - } - } - - if (elseBranch != null) { - Boolean level = isVersionCheckConditional(mApi, elseBranch, ifStatement, mContext); - //noinspection VariableNotUsedInsideIf - if (level != null) { - if (isUnconditionalReturn(elseBranch)) { - mFound = true; - mDone = true; - } - } - } - - return true; - } - - public boolean found() { - return mFound; - } - } - - private static boolean isPrecededByVersionCheckExit( - UElement element, - int api, - JavaContext context - ) { - //noinspection unchecked - UExpression currentExpression = UastUtils.getParentOfType(element, UExpression.class, - true, UMethod.class, UClass.class); - - while(currentExpression != null) { - VersionCheckWithExitFinder visitor = new VersionCheckWithExitFinder( - currentExpression, element, api, context); - currentExpression.accept(visitor); - - if (visitor.found()) { - return true; - } - - element = currentExpression; - //noinspection unchecked - currentExpression = UastUtils.getParentOfType(currentExpression, UExpression.class, - true, UMethod.class, UClass.class); - } - - return false; - } - - private static boolean isUnconditionalReturn(UExpression expression) { - if (expression instanceof UBlockExpression) { - List<UExpression> expressions = ((UBlockExpression) expression).getExpressions(); - return !expressions.isEmpty() && (isUnconditionalReturn(expressions.get(expressions.size() - 1))); - } - - return expression instanceof UReturnExpression || - expression instanceof UThrowExpression || - (expression instanceof UCallExpression && - ERROR.equals(((UCallExpression)expression).getMethodName())); - } - - private static boolean isWithinVersionCheckConditional( - UElement element, - int api, - JavaContext context - ) { - UElement current = element.getUastParent(); - UElement prev = element; - while (current != null) { - if (current instanceof UIfExpression) { - UIfExpression ifStatement = (UIfExpression) current; - Boolean isConditional = isVersionCheckConditional(api, prev, ifStatement, context); - if (isConditional != null) { - return isConditional; - } - } else if (current instanceof UBinaryExpression) { - if (isAndedWithConditional(current, api, prev) || isOredWithConditional(current, api, prev)) { - return true; - } - } else if (current instanceof UMethod || current instanceof UFile) { - return false; - } - prev = current; - current = current.getUastParent(); - } - - return false; - } - - @Nullable - private static Boolean isVersionCheckConditional( - int api, - UElement prev, - UIfExpression ifStatement, - @NonNull JavaContext context) { - UExpression condition = ifStatement.getCondition(); - if (condition != prev && condition instanceof UBinaryExpression) { - Boolean isConditional = isVersionCheckConditional(api, prev, ifStatement, (UBinaryExpression) condition); - if (isConditional != null) { - return isConditional; - } - } else if (condition instanceof UCallExpression) { - UCallExpression call = (UCallExpression) condition; - PsiMethod method = call.resolve(); - if (method != null && !method.hasModifierProperty(PsiModifier.ABSTRACT)) { - UExpression body = context.getUastContext().getMethodBody(method); - List<UExpression> expressions; - if (body instanceof UBlockExpression) { - expressions = ((UBlockExpression) body).getExpressions(); - } else { - expressions = Collections.singletonList(body); - } - - if (expressions.size() == 1) { - UExpression statement = expressions.get(0); - if (statement instanceof UReturnExpression) { - UReturnExpression returnStatement = (UReturnExpression) statement; - UExpression returnValue = returnStatement.getReturnExpression(); - if (returnValue instanceof UBinaryExpression) { - Boolean isConditional = isVersionCheckConditional(api, null, null, - (UBinaryExpression) returnValue); - if (isConditional != null) { - return isConditional; - } - } - } - } - } - } - return null; - } - - @Nullable - private static Boolean isVersionCheckConditional( - int api, - @Nullable UElement prev, - @Nullable UIfExpression ifStatement, - @NonNull UBinaryExpression binary) { - UastBinaryOperator tokenType = binary.getOperator(); - if (tokenType == UastBinaryOperator.GREATER || tokenType == UastBinaryOperator.GREATER_OR_EQUALS || - tokenType == UastBinaryOperator.LESS_OR_EQUALS || tokenType == UastBinaryOperator.LESS || - tokenType == UastBinaryOperator.EQUALS || tokenType == UastBinaryOperator.IDENTITY_EQUALS) { - UExpression left = binary.getLeftOperand(); - if (left instanceof UReferenceExpression) { - UReferenceExpression ref = (UReferenceExpression)left; - if (SDK_INT.equals(ref.getResolvedName())) { - UExpression right = binary.getRightOperand(); - int level = -1; - if (right instanceof UReferenceExpression) { - UReferenceExpression ref2 = (UReferenceExpression)right; - String codeName = ref2.getResolvedName(); - if (codeName == null) { - return false; - } - level = SdkVersionInfo.getApiByBuildCode(codeName, true); - } else if (right instanceof ULiteralExpression) { - ULiteralExpression lit = (ULiteralExpression)right; - Object value = lit.getValue(); - if (value instanceof Integer) { - level = (Integer) value; - } - } - if (level != -1) { - boolean fromThen = ifStatement == null || prev.equals(ifStatement.getThenExpression()); - boolean fromElse = ifStatement != null && prev.equals(ifStatement.getElseExpression()); - assert fromThen == !fromElse; - if (tokenType == UastBinaryOperator.GREATER_OR_EQUALS) { - // if (SDK_INT >= ICE_CREAM_SANDWICH) { <call> } else { ... } - return level >= api && fromThen; - } - else if (tokenType == UastBinaryOperator.GREATER) { - // if (SDK_INT > ICE_CREAM_SANDWICH) { <call> } else { ... } - return level >= api - 1 && fromThen; - } - else if (tokenType == UastBinaryOperator.LESS_OR_EQUALS) { - // if (SDK_INT <= ICE_CREAM_SANDWICH) { ... } else { <call> } - return level >= api - 1 && fromElse; - } - else if (tokenType == UastBinaryOperator.LESS) { - // if (SDK_INT < ICE_CREAM_SANDWICH) { ... } else { <call> } - return level >= api && fromElse; - } - else if (tokenType == UastBinaryOperator.EQUALS - || tokenType == UastBinaryOperator.IDENTITY_EQUALS) { - // if (SDK_INT == ICE_CREAM_SANDWICH) { <call> } else { } - return level >= api && fromThen; - } else { - assert false : tokenType; - } - } - } - } - } else if (tokenType == UastBinaryOperator.LOGICAL_AND - && (ifStatement != null && prev == ifStatement.getThenExpression())) { - if (isAndedWithConditional(ifStatement.getCondition(), api, prev)) { - return true; - } - } - return null; - } - - private static Location getCatchParametersLocation(JavaContext context, UCatchClause catchClause) { - List<UTypeReferenceExpression> types = catchClause.getTypeReferences(); - if (types.isEmpty()) { - return Location.NONE; - } - - Location first = context.getUastLocation(types.get(0)); - if (types.size() < 2) { - return first; - } - - Location last = context.getUastLocation(types.get(types.size() - 1)); - File file = first.getFile(); - Position start = first.getStart(); - Position end = last.getEnd(); - - if (start == null) { - return Location.create(file); - } - - return Location.create(file, start, end); - } - - private static boolean isMultiCatchReflectiveOperationException(UCatchClause catchClause) { - List<PsiType> types = catchClause.getTypes(); - if (types.size() < 2) { - return false; - } - - for (PsiType t : types) { - if(!isSubclassOfReflectiveOperationException(t)) { - return false; - } - } - - return true; - } - - private static boolean isAndedWithConditional(UElement element, int api, @Nullable UElement target) { - if (element instanceof UBinaryExpression) { - UBinaryExpression inner = (UBinaryExpression) element; - if (inner.getOperator() == UastBinaryOperator.LOGICAL_AND) { - return isAndedWithConditional(inner.getLeftOperand(), api, target) || - inner.getRightOperand() != target && isAndedWithConditional(inner.getRightOperand(), api, target); - } else if (inner.getLeftOperand() instanceof UReferenceExpression && - SDK_INT.equals(((UReferenceExpression)inner.getLeftOperand()).getResolvedName())) { - UastOperator tokenType = inner.getOperator(); - UExpression right = inner.getRightOperand(); - int level = getApiLevel(right); - if (level != -1) { - if (tokenType == UastBinaryOperator.GREATER_OR_EQUALS) { - // if (SDK_INT >= ICE_CREAM_SANDWICH && <call> - return level >= api; - } - else if (tokenType == UastBinaryOperator.GREATER) { - // if (SDK_INT > ICE_CREAM_SANDWICH) && <call> - return level >= api - 1; - } - else if (tokenType == UastBinaryOperator.EQUALS - || tokenType == UastBinaryOperator.IDENTITY_EQUALS) { - // if (SDK_INT == ICE_CREAM_SANDWICH) && <call> - return level >= api; - } - } - } - } - - return false; - } - - private static boolean isOredWithConditional(UElement element, int api, @Nullable UElement target) { - if (element instanceof UBinaryExpression) { - UBinaryExpression inner = (UBinaryExpression) element; - if (inner.getOperator() == UastBinaryOperator.LOGICAL_OR) { - return isOredWithConditional(inner.getLeftOperand(), api, target) || - inner.getRightOperand() != target && isOredWithConditional(inner.getRightOperand(), api, target); - } else if (inner.getLeftOperand() instanceof UReferenceExpression && - SDK_INT.equals(((UReferenceExpression)inner.getLeftOperand()).getResolvedName())) { - UastOperator tokenType = inner.getOperator(); - UExpression right = inner.getRightOperand(); - int level = getApiLevel(right); - if (level != -1) { - if (tokenType == UastBinaryOperator.LESS_OR_EQUALS) { - // if (SDK_INT <= ICE_CREAM_SANDWICH || <call> - return level >= api - 1; - } - else if (tokenType == UastBinaryOperator.LESS) { - // if (SDK_INT < ICE_CREAM_SANDWICH) || <call> - return level >= api; - } - else if (tokenType == UastBinaryOperator.NOT_EQUALS) { - // if (SDK_INT < ICE_CREAM_SANDWICH) || <call> - return level == api; - } - } - } - } - - return false; - } - - private static int getApiLevel(UElement apiLevelElement) { - if (apiLevelElement instanceof UReferenceExpression) { - UReferenceExpression ref2 = (UReferenceExpression)apiLevelElement; - String codeName = ref2.getResolvedName(); - if (codeName == null) { - return -1; - } - return SdkVersionInfo.getApiByBuildCode(codeName, true); - } else if (apiLevelElement instanceof ULiteralExpression) { - ULiteralExpression lit = (ULiteralExpression)apiLevelElement; - Object value = lit.getValue(); - if (value instanceof Integer) { - return ((Integer)value).intValue(); - } - } - return -1; - } - - private static boolean isSubclassOfReflectiveOperationException(PsiType type) { - for (PsiType t : type.getSuperTypes()) { - if (REFLECTIVE_OPERATION_EXCEPTION.equals(t.getCanonicalText())) { - return true; - } - } - return false; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiLookup.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiLookup.java deleted file mode 100644 index 619cf4f..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiLookup.java +++ /dev/null
@@ -1,1352 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_PKG; -import static com.android.SdkConstants.DOT_XML; -import static com.android.tools.klint.detector.api.LintUtils.assertionsEnabled; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.repository.api.LocalPackage; -import com.android.sdklib.repository.AndroidSdkHandler; -import com.android.tools.klint.client.api.LintClient; -//import com.android.tools.klint.client.api.SdkWrapper; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.utils.Pair; -import com.google.common.base.Charsets; -import com.google.common.collect.Lists; -import com.google.common.io.ByteSink; -import com.google.common.io.Files; -import com.google.common.primitives.UnsignedBytes; - -import java.io.File; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Database for API checking: Allows quick lookup of a given class, method or field - * to see which API level it was introduced in. - * <p> - * This class is optimized for quick bytecode lookup used in conjunction with the - * ASM library: It has lookup methods that take internal JVM signatures, and for a method - * call for example it processes the owner, name and description parameters separately - * the way they are provided from ASM. - * <p> - * The {@link Api} class provides access to the full Android API along with version - * information, initialized from an XML file. This lookup class adds a binary cache around - * the API to make initialization faster and to require fewer objects. It creates - * a binary cache data structure, which fits in a single byte array, which means that - * to open the database you can just read in the byte array and go. On one particular - * machine, this takes about 30-50 ms versus 600-800ms for the full parse. It also - * helps memory by placing everything in a compact byte array instead of needing separate - * strings (2 bytes per character in a char[] for the 25k method entries, 11k field entries - * and 6k class entries) - and it also avoids the same number of Map.Entry objects. - * When creating the memory data structure it performs a few other steps to help memory: - * <ul> - * <li> It stores the strings as single bytes, since all the JVM signatures are in ASCII - * <li> It strips out the method return types (which takes the binary size down from - * about 4.7M to 4.0M) - * <li> It strips out all APIs that have since=1, since the lookup only needs to find - * classes, methods and fields that have an API level *higher* than 1. This drops - * the memory use down from 4.0M to 1.7M. - * </ul> - */ -public class ApiLookup { - /** Relative path to the api-versions.xml database file within the Lint installation */ - private static final String XML_FILE_PATH = "platform-tools/api/api-versions.xml"; //$NON-NLS-1$ - private static final String FILE_HEADER = "API database used by Android lint\000"; - private static final int BINARY_FORMAT_VERSION = 8; - private static final boolean DEBUG_SEARCH = false; - private static final boolean WRITE_STATS = false; - - private static final int CLASS_HEADER_MEMBER_OFFSETS = 1; - private static final int CLASS_HEADER_API = 2; - private static final int CLASS_HEADER_DEPRECATED = 3; - private static final int CLASS_HEADER_INTERFACES = 4; - private static final int HAS_DEPRECATION_BYTE_FLAG = 1 << 7; - private static final int API_MASK = ~HAS_DEPRECATION_BYTE_FLAG; - - @VisibleForTesting - static final boolean DEBUG_FORCE_REGENERATE_BINARY = false; - - private final Api mInfo; - private byte[] mData; - private int[] mIndices; - - private static WeakReference<ApiLookup> sInstance = new WeakReference<ApiLookup>(null); - - private int mPackageCount; - - /** - * Returns an instance of the API database - * - * @param client the client to associate with this database - used only for - * logging. The database object may be shared among repeated invocations, - * and in that case client used will be the one originally passed in. - * In other words, this parameter may be ignored if the client created - * is not new. - * @return a (possibly shared) instance of the API database, or null - * if its data can't be found - */ - @Nullable - public static ApiLookup get(@NonNull LintClient client) { - synchronized (ApiLookup.class) { - ApiLookup db = sInstance.get(); - if (db == null) { - File file = client.findResource(XML_FILE_PATH); - if (file == null) { - // AOSP build environment? - String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$ - if (build != null) { - file = new File(build, "development/sdk/api-versions.xml" //$NON-NLS-1$ - .replace('/', File.separatorChar)); - } - } - - if (file == null || !file.exists()) { - return null; - } else { - db = get(client, file); - } - sInstance = new WeakReference<ApiLookup>(db); - } - - return db; - } - } - - @VisibleForTesting - @Nullable - static String getPlatformVersion(@NonNull LintClient client) { - AndroidSdkHandler sdk = client.getSdk(); - if (sdk != null) { - LocalPackage pkgInfo = sdk - .getLocalPackage(SdkConstants.FD_PLATFORM_TOOLS, client.getRepositoryLogger()); - if (pkgInfo != null) { - return pkgInfo.getVersion().toShortString(); - } - } - return null; - } - - @VisibleForTesting - @NonNull - static String getCacheFileName(@NonNull String xmlFileName, @Nullable String platformVersion) { - if (LintUtils.endsWith(xmlFileName, DOT_XML)) { - xmlFileName = xmlFileName.substring(0, xmlFileName.length() - DOT_XML.length()); - } - - StringBuilder sb = new StringBuilder(100); - sb.append(xmlFileName); - - // Incorporate version number in the filename to avoid upgrade filename - // conflicts on Windows (such as issue #26663) - sb.append('-').append(BINARY_FORMAT_VERSION); - - if (platformVersion != null) { - sb.append('-').append(platformVersion); - } - - sb.append(".bin"); //$NON-NLS-1$ - return sb.toString(); - } - - /** - * Returns an instance of the API database - * - * @param client the client to associate with this database - used only for - * logging - * @param xmlFile the XML file containing configuration data to use for this - * database - * @return a (possibly shared) instance of the API database, or null - * if its data can't be found - */ - private static ApiLookup get(LintClient client, File xmlFile) { - if (!xmlFile.exists()) { - client.log(null, "The API database file %1$s does not exist", xmlFile); - return null; - } - - File cacheDir = client.getCacheDir(true/*create*/); - if (cacheDir == null) { - cacheDir = xmlFile.getParentFile(); - } - - String platformVersion = getPlatformVersion(client); - File binaryData = new File(cacheDir, getCacheFileName(xmlFile.getName(), platformVersion)); - - if (DEBUG_FORCE_REGENERATE_BINARY) { - System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom " - + xmlFile + "\nto " + binaryData); - if (!createCache(client, xmlFile, binaryData)) { - return null; - } - } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified() - || binaryData.length() == 0) { - if (!createCache(client, xmlFile, binaryData)) { - return null; - } - } - - if (!binaryData.exists()) { - client.log(null, "The API database file %1$s does not exist", binaryData); - return null; - } - - return new ApiLookup(client, xmlFile, binaryData, null); - } - - private static boolean createCache(LintClient client, File xmlFile, File binaryData) { - long begin = 0; - if (WRITE_STATS) { - begin = System.currentTimeMillis(); - } - - Api info = Api.parseApi(xmlFile); - - if (WRITE_STATS) { - long end = System.currentTimeMillis(); - System.out.println("Reading XML data structures took " + (end - begin) + " ms)"); - } - - if (info != null) { - try { - writeDatabase(binaryData, info); - return true; - } catch (IOException ioe) { - client.log(ioe, "Can't write API cache file"); - } - } - - return false; - } - - /** Use one of the {@link #get} factory methods instead */ - private ApiLookup( - @NonNull LintClient client, - @NonNull File xmlFile, - @Nullable File binaryFile, - @Nullable Api info) { - mInfo = info; - - if (binaryFile != null) { - readData(client, xmlFile, binaryFile); - } - } - - /** - * Database format: - * <pre> - * (Note: all numbers are big endian; the format uses 1, 2, 3 and 4 byte integers.) - * - * - * 1. A file header, which is the exact contents of {@link #FILE_HEADER} encoded - * as ASCII characters. The purpose of the header is to identify what the file - * is for, for anyone attempting to open the file. - * 2. A file version number. If the binary file does not match the reader's expected - * version, it can ignore it (and regenerate the cache from XML). - * - * 3. The index table. When the data file is read, this is used to initialize the - * {@link #mIndices} array. The index table is built up like this: - * a. The number of index entries (e.g. number of elements in the {@link #mIndices} array) - * [1 4-byte int] - * b. The number of java/javax packages [1 4 byte int] - * c. Offsets to the package entries, one for each package, and each offset is 4 bytes. - * d. Offsets to the class entries, one for each class, and each offset is 4 bytes. - * e. Offsets to the member entries, one for each member, and each offset is 4 bytes. - * - * 4. The member entries -- one for each member. A given class entry will point to the - * first and last members in the index table above, and the offset of a given member - * is pointing to the offset of these entries. - * a. The name and description (except for the return value) of the member, in JVM format - * (e.g. for toLowerCase(char) we'd have "toLowerCase(C)". This is converted into - * UTF_8 representation as bytes [n bytes, the length of the byte representation of - * the description). - * b. A terminating 0 byte [1 byte]. - * c. The API level the member was introduced in [1 byte], BUT with the - * top bit ({@link #HAS_DEPRECATION_BYTE_FLAG}) set if the member is deprecated. - * d. IF the member is deprecated, the API level the member was deprecated in [1 byte]. - * Note that this byte does not appear if the bit indicated in (c) is not set. - * - * 5. The class entries -- one for each class. - * a. The index within this class entry where the metadata (other than the name) - * can be found. [1 byte]. This means that if you know a class by its number, - * you can quickly jump to its metadata without scanning through the string to - * find the end of it, by just adding this byte to the current offset and - * then you're at the data described below for (d). - * b. The name of the class (just the base name, not the package), as encoded as a - * UTF-8 string. [n bytes] - * c. A terminating 0 [1 byte]. - * d. The index in the index table (3) of the first member in the class [a 3 byte integer.] - * e. The number of members in the class [a 2 byte integer]. - * f. The API level the class was introduced in [1 byte], BUT with the - * top bit ({@link #HAS_DEPRECATION_BYTE_FLAG}) set if the class is deprecated. - * g. IF the class is deprecated, the API level the class was deprecated in [1 byte]. - * Note that this byte does not appear if the bit indicated in (f) is not set. - * h. The number of new super classes and interfaces [1 byte]. This counts only - * super classes and interfaces added after the original API level of the class. - * i. For each super class or interface counted in h, - * I. The index of the class [a 3 byte integer] - * II. The API level the class/interface was added [1 byte] - * - * 6. The package entries -- one for each package. - * a. The name of the package as encoded as a UTF-8 string. [n bytes] - * b. A terminating 0 [1 byte]. - * c. The index in the index table (3) of the first class in the package [a 3 byte integer.] - * d. The number of classes in the package [a 2 byte integer]. - * </pre> - */ - private void readData(@NonNull LintClient client, @NonNull File xmlFile, - @NonNull File binaryFile) { - if (!binaryFile.exists()) { - client.log(null, "%1$s does not exist", binaryFile); - return; - } - long start = System.currentTimeMillis(); - try { - byte[] b = Files.toByteArray(binaryFile); - - // First skip the header - int offset = 0; - byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII); - for (byte anExpectedHeader : expectedHeader) { - if (anExpectedHeader != b[offset++]) { - client.log(null, "Incorrect file header: not an API database cache " + - "file, or a corrupt cache file"); - return; - } - } - - // Read in the format number - if (b[offset++] != BINARY_FORMAT_VERSION) { - // Force regeneration of new binary data with up to date format - if (createCache(client, xmlFile, binaryFile)) { - readData(client, xmlFile, binaryFile); // Recurse - } - - return; - } - - int indexCount = get4ByteInt(b, offset); - offset += 4; - mPackageCount = get4ByteInt(b, offset); - offset += 4; - - mIndices = new int[indexCount]; - for (int i = 0; i < indexCount; i++) { - // TODO: Pack the offsets: They increase by a small amount for each entry, so - // no need to spend 4 bytes on each. These will need to be processed when read - // back in anyway, so consider storing the offset -deltas- as single bytes and - // adding them up cumulatively in readData(). - mIndices[i] = get4ByteInt(b, offset); - offset += 4; - } - mData = b; - // TODO: We only need to keep the data portion here since we've initialized - // the offset array separately. - // TODO: Investigate (profile) accessing the byte buffer directly instead of - // accessing a byte array. - } catch (Throwable e) { - client.log(null, "Failure reading binary cache file %1$s", binaryFile.getPath()); - client.log(null, "Please delete the file and restart the IDE/lint: %1$s", - binaryFile.getPath()); - client.log(e, null); - } - if (WRITE_STATS) { - long end = System.currentTimeMillis(); - System.out.println("\nRead API database in " + (end - start) - + " milliseconds."); - System.out.println("Size of data table: " + mData.length + " bytes (" - + Integer.toString(mData.length / 1024) + "k)\n"); - } - } - - /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */ - private static void writeDatabase(File file, Api info) throws IOException { - Map<String, ApiClass> classMap = info.getClasses(); - - List<ApiPackage> packages = Lists.newArrayList(info.getPackages().values()); - Collections.sort(packages); - - // Compute members of each class that must be included in the database; we can - // skip those that have the same since-level as the containing class. And we - // also need to keep those entries that are marked deprecated. - int estimatedSize = 0; - for (ApiPackage pkg : packages) { - estimatedSize += 4; // offset entry - estimatedSize += pkg.getName().length() + 20; // package entry - - if (assertionsEnabled() && !isRelevantOwner(pkg.getName() + "/") && - !pkg.getName().startsWith("android/support")) { - System.out.println("Warning: isRelevantOwner fails for " + pkg.getName() + "/"); - } - - for (ApiClass apiClass : pkg.getClasses()) { - estimatedSize += 4; // offset entry - estimatedSize += apiClass.getName().length() + 20; // class entry - - Set<String> allMethods = apiClass.getAllMethods(info); - Set<String> allFields = apiClass.getAllFields(info); - // Strip out all members that have been supported since version 1. - // This makes the database *much* leaner (down from about 4M to about - // 1.7M), and this just fills the table with entries that ultimately - // don't help the API checker since it just needs to know if something - // requires a version *higher* than the minimum. If in the future the - // database needs to answer queries about whether a method is public - // or not, then we'd need to put this data back in. - int clsSince = apiClass.getSince(); - List<String> members = new ArrayList<String>(allMethods.size() + allFields.size()); - for (String member : allMethods) { - if (apiClass.getMethod(member, info) != clsSince - || apiClass.getMemberDeprecatedIn(member, info) > 0) { - members.add(member); - } - } - for (String member : allFields) { - if (apiClass.getField(member, info) != clsSince - || apiClass.getMemberDeprecatedIn(member, info) > 0) { - members.add(member); - } - } - - estimatedSize += 2 + 4 * (apiClass.getInterfaces().size()); - if (apiClass.getSuperClasses().size() > 1) { - estimatedSize += 2 + 4 * (apiClass.getSuperClasses().size()); - } - - // Only include classes that have one or more members requiring version 2 or higher: - Collections.sort(members); - apiClass.members = members; - for (String member : members) { - estimatedSize += member.length(); - estimatedSize += 16; - } - } - - // Ensure the classes are sorted - Collections.sort(pkg.getClasses()); - } - - // Write header - ByteBuffer buffer = ByteBuffer.allocate(estimatedSize); - buffer.order(ByteOrder.BIG_ENDIAN); - buffer.put(FILE_HEADER.getBytes(Charsets.US_ASCII)); - buffer.put((byte) BINARY_FORMAT_VERSION); - - int indexCountOffset = buffer.position(); - int indexCount = 0; - - buffer.putInt(0); // placeholder - - // Write the number of packages in the package index - buffer.putInt(packages.size()); - - // Write package index - int newIndex = buffer.position(); - for (ApiPackage pkg : packages) { - pkg.indexOffset = newIndex; - newIndex += 4; - indexCount++; - } - - // Write class index - for (ApiPackage pkg : packages) { - for (ApiClass cls : pkg.getClasses()) { - cls.indexOffset = newIndex; - cls.index = indexCount; - newIndex += 4; - indexCount++; - } - } - - // Write member indices - for (ApiPackage pkg : packages) { - for (ApiClass cls : pkg.getClasses()) { - if (cls.members != null && !cls.members.isEmpty()) { - cls.memberOffsetBegin = newIndex; - cls.memberIndexStart = indexCount; - for (String ignored : cls.members) { - newIndex += 4; - indexCount++; - } - cls.memberOffsetEnd = newIndex; - cls.memberIndexLength = indexCount - cls.memberIndexStart; - } else { - cls.memberOffsetBegin = -1; - cls.memberOffsetEnd = -1; - cls.memberIndexStart = -1; - cls.memberIndexLength = 0; - } - } - } - - // Fill in the earlier index count - buffer.position(indexCountOffset); - buffer.putInt(indexCount); - buffer.position(newIndex); - - // Write member entries - for (ApiPackage pkg : packages) { - for (ApiClass apiClass : pkg.getClasses()) { - String clz = apiClass.getName(); - int index = apiClass.memberOffsetBegin; - for (String member : apiClass.members) { - // Update member offset to point to this entry - int start = buffer.position(); - buffer.position(index); - buffer.putInt(start); - index = buffer.position(); - buffer.position(start); - - int since; - if (member.indexOf('(') != -1) { - since = apiClass.getMethod(member, info); - } else { - since = apiClass.getField(member, info); - } - if (since == Integer.MAX_VALUE) { - assert false : clz + ':' + member; - since = 1; - } - - int deprecatedIn = apiClass.getMemberDeprecatedIn(member, info); - if (deprecatedIn != 0) { - assert deprecatedIn != -1 : deprecatedIn + " for " + member; - } - - byte[] signature = member.getBytes(Charsets.UTF_8); - for (byte b : signature) { - // Make sure all signatures are really just simple ASCII - assert b == (b & 0x7f) : member; - buffer.put(b); - // Skip types on methods - if (b == (byte) ')') { - break; - } - } - buffer.put((byte) 0); - int api = since; - assert api == UnsignedBytes.toInt((byte) api); - assert api >= 1 && api < 0xFF; // max that fits in a byte - - boolean isDeprecated = deprecatedIn > 0; - if (isDeprecated) { - api |= HAS_DEPRECATION_BYTE_FLAG; - } - - buffer.put((byte) api); - - if (isDeprecated) { - assert deprecatedIn == UnsignedBytes.toInt((byte) deprecatedIn); - buffer.put((byte) deprecatedIn); - } - } - assert index == apiClass.memberOffsetEnd : apiClass.memberOffsetEnd; - } - } - - // Write class entries. These are written together, rather than - // being spread out among the member entries, in order to have - // reference locality (search that a binary search through the classes - // are likely to look at entries near each other.) - for (ApiPackage pkg : packages) { - List<ApiClass> classes = pkg.getClasses(); - for (ApiClass cls : classes) { - int index = buffer.position(); - buffer.position(cls.indexOffset); - buffer.putInt(index); - buffer.position(index); - String name = cls.getSimpleName(); - - byte[] nameBytes = name.getBytes(Charsets.UTF_8); - assert nameBytes.length < 254 : name; - buffer.put((byte)(nameBytes.length + 2)); // 2: terminating 0, and this byte itself - buffer.put(nameBytes); - buffer.put((byte) 0); - - // 3 bytes for beginning, 2 bytes for *length* - put3ByteInt(buffer, cls.memberIndexStart); - put2ByteInt(buffer, cls.memberIndexLength); - - ApiClass apiClass = classMap.get(cls.getName()); - assert apiClass != null : cls.getName(); - int since = apiClass.getSince(); - assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits - int deprecatedIn = apiClass.getDeprecatedIn(); - boolean isDeprecated = deprecatedIn > 0; - // The first byte is deprecated in - if (isDeprecated) { - since |= HAS_DEPRECATION_BYTE_FLAG; - assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits - } - buffer.put((byte) since); - if (isDeprecated) { - assert deprecatedIn == UnsignedBytes.toInt((byte) deprecatedIn) : deprecatedIn; - buffer.put((byte) deprecatedIn); - } - - List<Pair<String, Integer>> interfaces = apiClass.getInterfaces(); - int count = 0; - if (interfaces != null && !interfaces.isEmpty()) { - for (Pair<String, Integer> pair : interfaces) { - int api = pair.getSecond(); - if (api > apiClass.getSince()) { - count++; - } - } - } - List<Pair<String, Integer>> supers = apiClass.getSuperClasses(); - if (supers != null && !supers.isEmpty()) { - for (Pair<String, Integer> pair : supers) { - int api = pair.getSecond(); - if (api > apiClass.getSince()) { - count++; - } - } - } - buffer.put((byte)count); - if (count > 0) { - if (supers != null) { - for (Pair<String, Integer> pair : supers) { - int api = pair.getSecond(); - if (api > apiClass.getSince()) { - ApiClass superClass = classMap.get(pair.getFirst()); - assert superClass != null : cls; - put3ByteInt(buffer, superClass.index); - buffer.put((byte) api); - } - } - } - if (interfaces != null) { - for (Pair<String, Integer> pair : interfaces) { - int api = pair.getSecond(); - if (api > apiClass.getSince()) { - ApiClass interfaceClass = classMap.get(pair.getFirst()); - assert interfaceClass != null : cls; - put3ByteInt(buffer, interfaceClass.index); - buffer.put((byte) api); - } - } - } - } - } - } - - for (ApiPackage pkg : packages) { - int index = buffer.position(); - buffer.position(pkg.indexOffset); - buffer.putInt(index); - buffer.position(index); - - byte[] bytes = pkg.getName().getBytes(Charsets.UTF_8); - buffer.put(bytes); - buffer.put((byte)0); - - List<ApiClass> classes = pkg.getClasses(); - if (classes.isEmpty()) { - put3ByteInt(buffer, 0); - put2ByteInt(buffer, 0); - } else { - // 3 bytes for beginning, 2 bytes for *length* - int firstClassIndex = classes.get(0).index; - int classCount = classes.get(classes.size() - 1).index - firstClassIndex + 1; - put3ByteInt(buffer, firstClassIndex); - put2ByteInt(buffer, classCount); - } - } - - int size = buffer.position(); - assert size <= buffer.limit(); - buffer.mark(); - - if (WRITE_STATS) { - System.out.print("Actual binary size: " + size + " bytes"); - System.out.println(String.format(" (%.1fM)", size/(1024*1024.f))); - } - - // Now dump this out as a file - // There's probably an API to do this more efficiently; TODO: Look into this. - byte[] b = new byte[size]; - buffer.rewind(); - buffer.get(b); - if (file.exists()) { - boolean deleted = file.delete(); - assert deleted : file; - } - ByteSink sink = Files.asByteSink(file); - sink.write(b); - } - - // For debugging only - private String dumpEntry(int offset) { - if (DEBUG_SEARCH) { - StringBuilder sb = new StringBuilder(200); - for (int i = offset; i < mData.length; i++) { - if (mData[i] == 0) { - break; - } - char c = (char) UnsignedBytes.toInt(mData[i]); - sb.append(c); - } - - return sb.toString(); - } else { - return "<disabled>"; //$NON-NLS-1$ - } - } - - private static int compare(byte[] data, int offset, byte terminator, String s, int sOffset, - int max) { - int i = offset; - int j = sOffset; - for (; j < max; i++, j++) { - byte b = data[i]; - char c = s.charAt(j); - // TODO: Check somewhere that the strings are purely in the ASCII range; if not - // they're not a match in the database - byte cb = (byte) c; - int delta = b - cb; - if (delta != 0) { - return delta; - } - } - - return data[i] - terminator; - } - - /** - * Returns the API version required by the given class reference, - * or -1 if this is not a known API class. Note that it may return -1 - * for classes introduced in version 1; internally the database only - * stores version data for version 2 and up. - * - * @param className the internal name of the class, e.g. its - * fully qualified name (as returned by Class.getName(), but with - * '.' replaced by '/'. - * @return the minimum API version the method is supported for, or -1 if - * it's unknown <b>or version 1</b>. - */ - public int getClassVersion(@NonNull String className) { - //noinspection VariableNotUsedInsideIf - if (mData != null) { - return getClassVersion(findClass(className)); - } else { - assert mInfo != null; - ApiClass clz = mInfo.getClass(className); - if (clz != null) { - int since = clz.getSince(); - if (since == Integer.MAX_VALUE) { - since = -1; - } - return since; - } - } - - return -1; - } - - /** - * Returns true if the given owner class is known in the API database. - * - * @param className the internal name of the class, e.g. its fully qualified name (as returned - * by Class.getName(), but with '.' replaced by '/' (and '$' for inner - * classes) - * @return true if this is a class found in the API database - */ - public boolean isKnownClass(@NonNull String className) { - return findClass(className) != -1; - } - - private int getClassVersion(int classNumber) { - if (classNumber != -1) { - int offset = seekClassData(classNumber, CLASS_HEADER_API); - int api = UnsignedBytes.toInt(mData[offset]) & API_MASK; - return api > 1 ? api : -1; - } - return -1; - } - - /** - * Returns the API version required to perform the given cast, or -1 if this is valid for all - * versions of the class (or, if these are not known classes or if the cast is not valid at - * all.) <p> Note also that this method should only be called for interfaces that are actually - * implemented by this class or extending the given super class (check elsewhere); it doesn't - * distinguish between interfaces implemented in the initial version of the class and interfaces - * not implemented at all. - * - * @param sourceClass the internal name of the class, e.g. its fully qualified name (as - * returned by Class.getName(), but with '.' replaced by '/'. - * @param destinationClass the class to cast the sourceClass to - * @return the minimum API version the method is supported for, or 1 or -1 if it's unknown. - */ - public int getValidCastVersion(@NonNull String sourceClass, - @NonNull String destinationClass) { - if (mData != null) { - int classNumber = findClass(sourceClass); - if (classNumber != -1) { - int interfaceNumber = findClass(destinationClass); - if (interfaceNumber != -1) { - int offset = seekClassData(classNumber, CLASS_HEADER_INTERFACES); - int interfaceCount = mData[offset++]; - for (int i = 0; i < interfaceCount; i++) { - int clsNumber = get3ByteInt(mData, offset); - offset += 3; - int api = mData[offset++]; - if (clsNumber == interfaceNumber) { - return api; - } - } - return getClassVersion(classNumber); - } - } - } else { - assert mInfo != null; - ApiClass clz = mInfo.getClass(sourceClass); - if (clz != null) { - List<Pair<String, Integer>> interfaces = clz.getInterfaces(); - for (Pair<String,Integer> pair : interfaces) { - String interfaceName = pair.getFirst(); - if (interfaceName.equals(destinationClass)) { - return pair.getSecond(); - } - } - } - } - - return -1; - } - /** - * Returns the API version the given class was deprecated in, or -1 if the class - * is not deprecated. - * - * @param className the internal name of the method's owner class, e.g. its - * fully qualified name (as returned by Class.getName(), but with - * '.' replaced by '/'. - * @return the API version the API was deprecated in, or -1 if - * it's unknown <b>or version 0</b>. - */ - public int getClassDeprecatedIn(@NonNull String className) { - if (mData != null) { - int classNumber = findClass(className); - if (classNumber != -1) { - int offset = seekClassData(classNumber, CLASS_HEADER_DEPRECATED); - if (offset == -1) { - // Not deprecated - return -1; - } - int deprecatedIn = UnsignedBytes.toInt(mData[offset]); - return deprecatedIn != 0 ? deprecatedIn : -1; - } - } else { - assert mInfo != null; - ApiClass clz = mInfo.getClass(className); - if (clz != null) { - int deprecatedIn = clz.getDeprecatedIn(); - if (deprecatedIn == Integer.MAX_VALUE) { - deprecatedIn = -1; - } - return deprecatedIn; - } - } - - return -1; - } - - /** - * Returns the API version required by the given method call. The method is - * referred to by its {@code owner}, {@code name} and {@code desc} fields. - * If the method is unknown it returns -1. Note that it may return -1 for - * classes introduced in version 1; internally the database only stores - * version data for version 2 and up. - * - * @param owner the internal name of the method's owner class, e.g. its - * fully qualified name (as returned by Class.getName(), but with - * '.' replaced by '/'. - * @param name the method's name - * @param desc the method's descriptor - see {@link Type} - * @return the minimum API version the method is supported for, or 1 or -1 if - * it's unknown. - */ - public int getCallVersion( - @NonNull String owner, - @NonNull String name, - @NonNull String desc) { - //noinspection VariableNotUsedInsideIf - if (mData != null) { - int classNumber = findClass(owner); - if (classNumber != -1) { - int api = findMember(classNumber, name, desc); - if (api == -1) { - return getClassVersion(classNumber); - } - return api; - } - } else { - assert mInfo != null; - ApiClass clz = mInfo.getClass(owner); - if (clz != null) { - String signature = name + desc; - int since = clz.getMethod(signature, mInfo); - if (since == Integer.MAX_VALUE) { - since = -1; - } - return since; - } - } - - return -1; - } - - /** - * Returns the API version the given call was deprecated in, or -1 if the method - * is not deprecated. - * - * @param owner the internal name of the method's owner class, e.g. its - * fully qualified name (as returned by Class.getName(), but with - * '.' replaced by '/'. - * @param name the method's name - * @param desc the method's descriptor - see {@link Type} - * @return the API version the API was deprecated in, or 1 or -1 if - * it's unknown. - */ - public int getCallDeprecatedIn( - @NonNull String owner, - @NonNull String name, - @NonNull String desc) { - //noinspection VariableNotUsedInsideIf - if (mData != null) { - int classNumber = findClass(owner); - if (classNumber != -1) { - int deprecatedIn = findMemberDeprecatedIn(classNumber, name, desc); - return deprecatedIn != 0 ? deprecatedIn : -1; - } - } else { - assert mInfo != null; - ApiClass clz = mInfo.getClass(owner); - if (clz != null) { - String signature = name + desc; - int deprecatedIn = clz.getMemberDeprecatedIn(signature, mInfo); - if (deprecatedIn == Integer.MAX_VALUE) { - deprecatedIn = -1; - } - return deprecatedIn; - } - } - - return -1; - } - - /** - * Returns the API version required to access the given field, or -1 if this - * is not a known API method. Note that it may return -1 for classes - * introduced in version 1; internally the database only stores version data - * for version 2 and up. - * - * @param owner the internal name of the method's owner class, e.g. its - * fully qualified name (as returned by Class.getName(), but with - * '.' replaced by '/'. - * @param name the method's name - * @return the minimum API version the method is supported for, or 1 or -1 if - * it's unknown. - */ - public int getFieldVersion( - @NonNull String owner, - @NonNull String name) { - //noinspection VariableNotUsedInsideIf - if (mData != null) { - int classNumber = findClass(owner); - if (classNumber != -1) { - int api = findMember(classNumber, name, null); - if (api == -1) { - return getClassVersion(classNumber); - } - return api; - } - } else { - assert mInfo != null; - ApiClass clz = mInfo.getClass(owner); - if (clz != null) { - int since = clz.getField(name, mInfo); - if (since == Integer.MAX_VALUE) { - since = -1; - } - return since; - } - } - - return -1; - } - - /** - * Returns the API version the given field was deprecated in, or -1 if the field - * is not deprecated. - * - * @param owner the internal name of the method's owner class, e.g. its - * fully qualified name (as returned by Class.getName(), but with - * '.' replaced by '/'. - * @param name the method's name - * @return the API version the API was deprecated in, or 1 or -1 if - * it's unknown. - */ - public int getFieldDeprecatedIn( - @NonNull String owner, - @NonNull String name) { - //noinspection VariableNotUsedInsideIf - if (mData != null) { - int classNumber = findClass(owner); - if (classNumber != -1) { - int deprecatedIn = findMemberDeprecatedIn(classNumber, name, null); - return deprecatedIn != 0 ? deprecatedIn : -1; - } - } else { - assert mInfo != null; - ApiClass clz = mInfo.getClass(owner); - if (clz != null) { - int deprecatedIn = clz.getMemberDeprecatedIn(name, mInfo); - if (deprecatedIn == Integer.MAX_VALUE) { - deprecatedIn = -1; - } - return deprecatedIn; - } - } - - return -1; - } - - /** - * Returns true if the given owner (in VM format) is relevant to the database. - * This allows quick filtering out of owners that won't return any data - * for the various {@code #getFieldVersion} etc methods. - * - * @param owner the owner to look up - * @return true if the owner might be relevant to the API database - */ - public static boolean isRelevantOwner(@NonNull String owner) { - if (owner.startsWith("java")) { //$NON-NLS-1$ // includes javax/ - return true; - } - if (owner.startsWith(ANDROID_PKG)) { - return !owner.startsWith("/support/", 7); - } else if (owner.startsWith("org/")) { //$NON-NLS-1$ - if (owner.startsWith("xml", 4) //$NON-NLS-1$ - || owner.startsWith("w3c/", 4) //$NON-NLS-1$ - || owner.startsWith("json/", 4) //$NON-NLS-1$ - || owner.startsWith("apache/", 4)) { //$NON-NLS-1$ - return true; - } - } else if (owner.startsWith("com/")) { //$NON-NLS-1$ - if (owner.startsWith("google/", 4) //$NON-NLS-1$ - || owner.startsWith("android/", 4)) { //$NON-NLS-1$ - return true; - } - } else if (owner.startsWith("junit") //$NON-NLS-1$ - || owner.startsWith("dalvik")) { //$NON-NLS-1$ - return true; - } - - return false; - } - - /** - * Returns true if the given owner (in VM format) is a valid Java package supported - * in any version of Android. - * - * @param owner the package, in VM format - * @return true if the package is included in one or more versions of Android - */ - public boolean isValidJavaPackage(@NonNull String owner) { - return findPackage(owner) != -1; - } - - /** Returns the package index of the given class, or -1 if it is unknown */ - private int findPackage(@NonNull String owner) { - assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner; - - // The index array contains class indexes from 0 to classCount and - // member indices from classCount to mIndices.length. - int low = 0; - int high = mPackageCount - 1; - // Compare the api info at the given index. - int classNameLength = owner.lastIndexOf('/'); - while (low <= high) { - int middle = (low + high) >>> 1; - int offset = mIndices[middle]; - - if (DEBUG_SEARCH) { - System.out.println("Comparing string " + owner.substring(0, classNameLength) - + " with entry at " + offset + ": " + dumpEntry(offset)); - } - - int compare = compare(mData, offset, (byte) 0, owner, 0, classNameLength); - if (compare == 0) { - if (DEBUG_SEARCH) { - System.out.println("Found " + dumpEntry(offset)); - } - return middle; - } - - if (compare < 0) { - low = middle + 1; - } else if (compare > 0) { - high = middle - 1; - } else { - assert false; // compare == 0 already handled above - return -1; - } - } - - return -1; - } - - private static int get4ByteInt(@NonNull byte[] data, int offset) { - byte b1 = data[offset++]; - byte b2 = data[offset++]; - byte b3 = data[offset++]; - byte b4 = data[offset]; - // The byte data is always big endian. - return (b1 & 0xFF) << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); - } - - private static void put3ByteInt(@NonNull ByteBuffer buffer, int value) { - // Big endian - byte b3 = (byte) (value & 0xFF); - value >>>= 8; - byte b2 = (byte) (value & 0xFF); - value >>>= 8; - byte b1 = (byte) (value & 0xFF); - buffer.put(b1); - buffer.put(b2); - buffer.put(b3); - } - - private static void put2ByteInt(@NonNull ByteBuffer buffer, int value) { - // Big endian - byte b2 = (byte) (value & 0xFF); - value >>>= 8; - byte b1 = (byte) (value & 0xFF); - buffer.put(b1); - buffer.put(b2); - } - - private static int get3ByteInt(@NonNull byte[] mData, int offset) { - byte b1 = mData[offset++]; - byte b2 = mData[offset++]; - byte b3 = mData[offset]; - // The byte data is always big endian. - return (b1 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b3 & 0xFF); - } - - private static int get2ByteInt(@NonNull byte[] data, int offset) { - byte b1 = data[offset++]; - byte b2 = data[offset]; - // The byte data is always big endian. - return (b1 & 0xFF) << 8 | (b2 & 0xFF); - } - - /** Returns the class number of the given class, or -1 if it is unknown */ - private int findClass(@NonNull String owner) { - assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner; - - int packageNumber = findPackage(owner); - if (packageNumber == -1) { - return -1; - } - int curr = mIndices[packageNumber]; - while (mData[curr] != 0) { - curr++; - } - curr++; - - // 3 bytes for first offset - int low = get3ByteInt(mData, curr); - curr += 3; - - int length = get2ByteInt(mData, curr); - if (length == 0) { - return -1; - } - int high = low + length - 1; - int index = owner.lastIndexOf('/'); - int classNameLength = owner.length(); - while (low <= high) { - int middle = (low + high) >>> 1; - int offset = mIndices[middle]; - offset++; // skip the byte which points to the metadata after the name - - if (DEBUG_SEARCH) { - System.out.println("Comparing string " + owner.substring(0, classNameLength) - + " with entry at " + offset + ": " + dumpEntry(offset)); - } - - int compare = compare(mData, offset, (byte) 0, owner, index + 1, classNameLength); - if (compare == 0) { - if (DEBUG_SEARCH) { - System.out.println("Found " + dumpEntry(offset)); - } - return middle; - } - - if (compare < 0) { - low = middle + 1; - } else if (compare > 0) { - high = middle - 1; - } else { - assert false; // compare == 0 already handled above - return -1; - } - } - - return -1; - } - - private int findMember(int classNumber, @NonNull String name, @Nullable String desc) { - return findMember(classNumber, name, desc, false); - } - - private int findMemberDeprecatedIn(int classNumber, @NonNull String name, - @Nullable String desc) { - return findMember(classNumber, name, desc, true); - } - - private int seekClassData(int classNumber, int field) { - int offset = mIndices[classNumber]; - offset += mData[offset] & 0xFF; - if (field == CLASS_HEADER_MEMBER_OFFSETS) { - return offset; - } - offset += 5; // 3 bytes for start, 2 bytes for length - if (field == CLASS_HEADER_API) { - return offset; - } - boolean hasDeprecation = (mData[offset] & HAS_DEPRECATION_BYTE_FLAG) != 0; - offset++; - if (field == CLASS_HEADER_DEPRECATED) { - return hasDeprecation ? offset : -1; - } else if (hasDeprecation) { - offset++; - } - assert field == CLASS_HEADER_INTERFACES; - return offset; - } - - private int findMember(int classNumber, @NonNull String name, @Nullable String desc, - boolean deprecation) { - int curr = seekClassData(classNumber, CLASS_HEADER_MEMBER_OFFSETS); - - // 3 bytes for first offset - int low = get3ByteInt(mData, curr); - curr += 3; - - int length = get2ByteInt(mData, curr); - if (length == 0) { - return -1; - } - int high = low + length - 1; - - while (low <= high) { - int middle = (low + high) >>> 1; - int offset = mIndices[middle]; - - if (DEBUG_SEARCH) { - System.out.println("Comparing string " + (name + ';' + desc) + - " with entry at " + offset + ": " + dumpEntry(offset)); - } - - int compare; - if (desc != null) { - // Method - int nameLength = name.length(); - compare = compare(mData, offset, (byte) '(', name, 0, nameLength); - if (compare == 0) { - offset += nameLength; - int argsEnd = desc.indexOf(')'); - // Only compare up to the ) -- after that we have a return value in the - // input description, which isn't there in the database - compare = compare(mData, offset, (byte) ')', desc, 0, argsEnd); - if (compare == 0) { - if (DEBUG_SEARCH) { - System.out.println("Found " + dumpEntry(offset)); - } - - offset += argsEnd + 1; - - if (mData[offset++] == 0) { - // Yes, terminated argument list: get the API level - int api = UnsignedBytes.toInt(mData[offset]); - if (deprecation) { - if ((api & HAS_DEPRECATION_BYTE_FLAG) != 0) { - return UnsignedBytes.toInt(mData[offset + 1]); - } else { - return -1; - } - } else { - return api & API_MASK; - } - } - } - } - } else { - // Field - int nameLength = name.length(); - compare = compare(mData, offset, (byte) 0, name, 0, nameLength); - if (compare == 0) { - offset += nameLength; - if (mData[offset++] == 0) { - // Yes, terminated argument list: get the API level - int api = UnsignedBytes.toInt(mData[offset]); - if (deprecation) { - if ((api & HAS_DEPRECATION_BYTE_FLAG) != 0) { - return UnsignedBytes.toInt(mData[offset + 1]); - } else { - return -1; - } - } else { - return api & API_MASK; - } - } - } - } - - if (compare < 0) { - low = middle + 1; - } else if (compare > 0) { - high = middle - 1; - } else { - assert false; // compare == 0 already handled above - return -1; - } - } - - return -1; - } - - /** Clears out any existing lookup instances */ - @VisibleForTesting - static void dispose() { - sInstance.clear(); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiPackage.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiPackage.java deleted file mode 100644 index e233a21..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiPackage.java +++ /dev/null
@@ -1,69 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.google.common.collect.Lists; - -import java.util.List; - -/** - * Represents a package and its classes - */ -public class ApiPackage implements Comparable<ApiPackage> { - private final String mName; - private final List<ApiClass> mClasses = Lists.newArrayListWithExpectedSize(100); - - // Persistence data: Used when writing out binary data in ApiLookup - int indexOffset; // offset of the package entry - - ApiPackage(@NonNull String name) { - mName = name; - } - - /** - * Returns the name of the class (fully qualified name) - * @return the name of the class - */ - @NonNull - public String getName() { - return mName; - } - - /** - * Returns the classes in this package - * @return the classes in this package - */ - @NonNull - public List<ApiClass> getClasses() { - return mClasses; - } - - void addClass(@NonNull ApiClass clz) { - mClasses.add(clz); - } - - @Override - public int compareTo(@NonNull ApiPackage other) { - return mName.compareTo(other.mName); - } - - @Override - public String toString() { - return mName; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiParser.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiParser.java deleted file mode 100644 index 56a09bc..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiParser.java +++ /dev/null
@@ -1,154 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import java.util.HashMap; -import java.util.Map; - -/** - * Parser for the simplified XML API format version 1. - */ -public class ApiParser extends DefaultHandler { - - private static final String NODE_API = "api"; - private static final String NODE_CLASS = "class"; - private static final String NODE_FIELD = "field"; - private static final String NODE_METHOD = "method"; - private static final String NODE_EXTENDS = "extends"; - private static final String NODE_IMPLEMENTS = "implements"; - - private static final String ATTR_NAME = "name"; - private static final String ATTR_SINCE = "since"; - private static final String ATTR_DEPRECATED = "deprecated"; - - private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>(1000); - private final Map<String, ApiPackage> mPackages = new HashMap<String, ApiPackage>(); - - private ApiClass mCurrentClass; - - ApiParser() { - } - - Map<String, ApiClass> getClasses() { - return mClasses; - } - Map<String, ApiPackage> getPackages() { return mPackages; } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - - if (localName == null || localName.isEmpty()) { - localName = qName; - } - - try { - //noinspection StatementWithEmptyBody - if (NODE_API.equals(localName)) { - // do nothing. - } else if (NODE_CLASS.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = Integer.parseInt(attributes.getValue(ATTR_SINCE)); - - String deprecatedAttr = attributes.getValue(ATTR_DEPRECATED); - int deprecatedIn; - if (deprecatedAttr != null) { - deprecatedIn = Integer.parseInt(deprecatedAttr); - } else { - deprecatedIn = 0; - } - mCurrentClass = addClass(name, since, deprecatedIn); - - } else if (NODE_EXTENDS.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - - mCurrentClass.addSuperClass(name, since); - - } else if (NODE_IMPLEMENTS.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - - mCurrentClass.addInterface(name, since); - - } else if (NODE_METHOD.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - int deprecatedIn = getDeprecatedIn(attributes); - mCurrentClass.addMethod(name, since, deprecatedIn); - - } else if (NODE_FIELD.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - int deprecatedIn = getDeprecatedIn(attributes); - - mCurrentClass.addField(name, since, deprecatedIn); - - } - - } finally { - super.startElement(uri, localName, qName, attributes); - } - } - - private ApiClass addClass(String name, int apiLevel, int deprecatedIn) { - // There should not be any duplicates - ApiClass theClass = mClasses.get(name); - assert theClass == null; - theClass = new ApiClass(name, apiLevel, deprecatedIn); - mClasses.put(name, theClass); - - String pkg = theClass.getPackage(); - if (pkg != null) { - ApiPackage apiPackage = mPackages.get(pkg); - if (apiPackage == null) { - apiPackage = new ApiPackage(pkg); - mPackages.put(pkg, apiPackage); - } - apiPackage.addClass(theClass); - } - - return theClass; - } - - private int getSince(Attributes attributes) { - int since = mCurrentClass.getSince(); - String sinceAttr = attributes.getValue(ATTR_SINCE); - - if (sinceAttr != null) { - since = Integer.parseInt(sinceAttr); - } - - return since; - } - - private int getDeprecatedIn(Attributes attributes) { - int deprecatedIn = mCurrentClass.getDeprecatedIn(); - String deprecatedAttr = attributes.getValue(ATTR_DEPRECATED); - - if (deprecatedAttr != null) { - deprecatedIn = Integer.parseInt(deprecatedAttr); - } - - return deprecatedIn; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppCompatCallDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppCompatCallDetector.java deleted file mode 100644 index a02c873..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppCompatCallDetector.java +++ /dev/null
@@ -1,174 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT; -import static com.android.SdkConstants.CLASS_ACTIVITY; -import static com.android.tools.klint.detector.api.TextFormat.RAW; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TextFormat; -import com.intellij.psi.PsiMethod; - -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.List; - -public class AppCompatCallDetector extends Detector implements Detector.UastScanner { - public static final Issue ISSUE = Issue.create( - "AppCompatMethod", - "Using Wrong AppCompat Method", - "When using the appcompat library, there are some methods you should be calling " + - "instead of the normal ones; for example, `getSupportActionBar()` instead of " + - "`getActionBar()`. This lint check looks for calls to the wrong method.", - Category.CORRECTNESS, 6, Severity.WARNING, - new Implementation( - AppCompatCallDetector.class, - Scope.JAVA_FILE_SCOPE)). - addMoreInfo("http://developer.android.com/tools/support-library/index.html"); - - private static final String GET_ACTION_BAR = "getActionBar"; - private static final String START_ACTION_MODE = "startActionMode"; - private static final String SET_PROGRESS_BAR_VIS = "setProgressBarVisibility"; - private static final String SET_PROGRESS_BAR_IN_VIS = "setProgressBarIndeterminateVisibility"; - private static final String SET_PROGRESS_BAR_INDETERMINATE = "setProgressBarIndeterminate"; - private static final String REQUEST_WINDOW_FEATURE = "requestWindowFeature"; - /** If you change number of parameters or order, update {@link #getMessagePart(String, int,TextFormat)} */ - private static final String ERROR_MESSAGE_FORMAT = "Should use `%1$s` instead of `%2$s` name"; - - private boolean mDependsOnAppCompat; - - public AppCompatCallDetector() { - } - - @Override - public void beforeCheckProject(@NonNull Context context) { - Boolean dependsOnAppCompat = context.getProject().dependsOn(APPCOMPAT_LIB_ARTIFACT); - mDependsOnAppCompat = dependsOnAppCompat != null && dependsOnAppCompat; - } - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Arrays.asList( - GET_ACTION_BAR, - START_ACTION_MODE, - SET_PROGRESS_BAR_VIS, - SET_PROGRESS_BAR_IN_VIS, - SET_PROGRESS_BAR_INDETERMINATE, - REQUEST_WINDOW_FEATURE); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - if (mDependsOnAppCompat && isAppBarActivityCall(context, node, method)) { - String name = method.getName(); - String replace = null; - if (GET_ACTION_BAR.equals(name)) { - replace = "getSupportActionBar"; - } else if (START_ACTION_MODE.equals(name)) { - replace = "startSupportActionMode"; - } else if (SET_PROGRESS_BAR_VIS.equals(name)) { - replace = "setSupportProgressBarVisibility"; - } else if (SET_PROGRESS_BAR_IN_VIS.equals(name)) { - replace = "setSupportProgressBarIndeterminateVisibility"; - } else if (SET_PROGRESS_BAR_INDETERMINATE.equals(name)) { - replace = "setSupportProgressBarIndeterminate"; - } else if (REQUEST_WINDOW_FEATURE.equals(name)) { - replace = "supportRequestWindowFeature"; - } - - if (replace != null) { - String message = String.format(ERROR_MESSAGE_FORMAT, replace, name); - context.report(ISSUE, node, context.getUastLocation(node), message); - } - } - - } - - private static boolean isAppBarActivityCall(@NonNull JavaContext context, - @NonNull UCallExpression node, @NonNull PsiMethod method) { - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.isMemberInSubClassOf(method, CLASS_ACTIVITY, false)) { - // Make sure that the calling context is a subclass of ActionBarActivity; - // we don't want to flag these calls if they are in non-appcompat activities - // such as PreferenceActivity (see b.android.com/58512) - UClass cls = UastUtils.getParentOfType(node, UClass.class, true); - return cls != null && InheritanceUtil.isInheritor( - cls, false, "android.support.v7.app.ActionBarActivity"); - } - return false; - } - - /** - * Given an error message created by this lint check, return the corresponding old method name - * that it suggests should be deleted. (Intended to support quickfix implementations - * for this lint check.) - * - * @param errorMessage the error message originally produced by this detector - * @param format the format of the error message - * @return the corresponding old method name, or null if not recognized - */ - @Nullable - public static String getOldCall(@NonNull String errorMessage, @NonNull TextFormat format) { - return getMessagePart(errorMessage, 2, format); - } - - /** - * Given an error message created by this lint check, return the corresponding new method name - * that it suggests replace the old method name. (Intended to support quickfix implementations - * for this lint check.) - * - * @param errorMessage the error message originally produced by this detector - * @param format the format of the error message - * @return the corresponding new method name, or null if not recognized - */ - @Nullable - public static String getNewCall(@NonNull String errorMessage, @NonNull TextFormat format) { - return getMessagePart(errorMessage, 1, format); - } - - @Nullable - private static String getMessagePart(@NonNull String errorMessage, int group, - @NonNull TextFormat format) { - List<String> parameters = LintUtils.getFormattedParameters( - RAW.convertTo(ERROR_MESSAGE_FORMAT, format), - errorMessage); - if (parameters.size() == 2 && group <= 2) { - return parameters.get(group - 1); - } - - return null; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppIndexingApiDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppIndexingApiDetector.java deleted file mode 100644 index 2e6140b..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppIndexingApiDetector.java +++ /dev/null
@@ -1,744 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_EXPORTED; -import static com.android.SdkConstants.ATTR_HOST; -import static com.android.SdkConstants.ATTR_PATH; -import static com.android.SdkConstants.ATTR_PATH_PREFIX; -import static com.android.SdkConstants.ATTR_SCHEME; -import static com.android.SdkConstants.CLASS_ACTIVITY; -import static com.android.xml.AndroidManifest.ATTRIBUTE_MIME_TYPE; -import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME; -import static com.android.xml.AndroidManifest.ATTRIBUTE_PORT; -import static com.android.xml.AndroidManifest.NODE_ACTION; -import static com.android.xml.AndroidManifest.NODE_ACTIVITY; -import static com.android.xml.AndroidManifest.NODE_APPLICATION; -import static com.android.xml.AndroidManifest.NODE_CATEGORY; -import static com.android.xml.AndroidManifest.NODE_DATA; -import static com.android.xml.AndroidManifest.NODE_INTENT; -import static com.android.xml.AndroidManifest.NODE_MANIFEST; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceItem; -import com.android.ide.common.resources.ResourceUrl; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.client.api.XmlParser; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; - -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; - -import java.io.File; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - - -/** - * Check if the usage of App Indexing is correct. - */ -public class AppIndexingApiDetector extends Detector implements XmlScanner, Detector.UastScanner { - - private static final Implementation URL_IMPLEMENTATION = new Implementation( - AppIndexingApiDetector.class, Scope.MANIFEST_SCOPE); - - @SuppressWarnings("unchecked") - private static final Implementation APP_INDEXING_API_IMPLEMENTATION = - new Implementation( - AppIndexingApiDetector.class, - EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST), - Scope.JAVA_FILE_SCOPE, Scope.MANIFEST_SCOPE); - - public static final Issue ISSUE_URL_ERROR = Issue.create( - "GoogleAppIndexingUrlError", //$NON-NLS-1$ - "URL not supported by app for Google App Indexing", - "Ensure the URL is supported by your app, to get installs and traffic to your" - + " app from Google Search.", - Category.USABILITY, 5, Severity.ERROR, URL_IMPLEMENTATION) - .addMoreInfo("https://g.co/AppIndexing/AndroidStudio"); - - public static final Issue ISSUE_APP_INDEXING = - Issue.create( - "GoogleAppIndexingWarning", //$NON-NLS-1$ - "Missing support for Google App Indexing", - "Adds URLs to get your app into the Google index, to get installs" - + " and traffic to your app from Google Search.", - Category.USABILITY, 5, Severity.WARNING, URL_IMPLEMENTATION) - .addMoreInfo("https://g.co/AppIndexing/AndroidStudio"); - - public static final Issue ISSUE_APP_INDEXING_API = - Issue.create( - "GoogleAppIndexingApiWarning", //$NON-NLS-1$ - "Missing support for Google App Indexing Api", - "Adds URLs to get your app into the Google index, to get installs" - + " and traffic to your app from Google Search.", - Category.USABILITY, 5, Severity.WARNING, APP_INDEXING_API_IMPLEMENTATION) - .addMoreInfo("https://g.co/AppIndexing/AndroidStudio") - .setEnabledByDefault(false); - - private static final String[] PATH_ATTR_LIST = new String[]{ATTR_PATH_PREFIX, ATTR_PATH}; - private static final String SCHEME_MISSING = "android:scheme is missing"; - private static final String HOST_MISSING = "android:host is missing"; - private static final String DATA_MISSING = "Missing data element"; - private static final String URL_MISSING = "Missing URL for the intent filter"; - private static final String NOT_BROWSABLE - = "Activity supporting ACTION_VIEW is not set as BROWSABLE"; - private static final String ILLEGAL_NUMBER = "android:port is not a legal number"; - - private static final String APP_INDEX_START = "start"; //$NON-NLS-1$ - private static final String APP_INDEX_END = "end"; //$NON-NLS-1$ - private static final String APP_INDEX_VIEW = "view"; //$NON-NLS-1$ - private static final String APP_INDEX_VIEW_END = "viewEnd"; //$NON-NLS-1$ - private static final String CLIENT_CONNECT = "connect"; //$NON-NLS-1$ - private static final String CLIENT_DISCONNECT = "disconnect"; //$NON-NLS-1$ - private static final String ADD_API = "addApi"; //$NON-NLS-1$ - - private static final String APP_INDEXING_API_CLASS - = "com.google.android.gms.appindexing.AppIndexApi"; - private static final String GOOGLE_API_CLIENT_CLASS - = "com.google.android.gms.common.api.GoogleApiClient"; - private static final String GOOGLE_API_CLIENT_BUILDER_CLASS - = "com.google.android.gms.common.api.GoogleApiClient.Builder"; - private static final String API_CLASS = "com.google.android.gms.appindexing.AppIndex"; - - public enum IssueType { - SCHEME_MISSING(AppIndexingApiDetector.SCHEME_MISSING), - HOST_MISSING(AppIndexingApiDetector.HOST_MISSING), - DATA_MISSING(AppIndexingApiDetector.DATA_MISSING), - URL_MISSING(AppIndexingApiDetector.URL_MISSING), - NOT_BROWSABLE(AppIndexingApiDetector.NOT_BROWSABLE), - ILLEGAL_NUMBER(AppIndexingApiDetector.ILLEGAL_NUMBER), - EMPTY_FIELD("cannot be empty"), - MISSING_SLASH("attribute should start with '/'"), - UNKNOWN("unknown error type"); - - private final String message; - - IssueType(String str) { - this.message = str; - } - - public static IssueType parse(String str) { - for (IssueType type : IssueType.values()) { - if (str.contains(type.message)) { - return type; - } - } - return UNKNOWN; - } - } - - // ---- Implements XmlScanner ---- - @Override - @Nullable - public Collection<String> getApplicableElements() { - return Collections.singletonList(NODE_APPLICATION); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element application) { - List<Element> activities = extractChildrenByName(application, NODE_ACTIVITY); - boolean applicationHasActionView = false; - for (Element activity : activities) { - List<Element> intents = extractChildrenByName(activity, NODE_INTENT); - boolean activityHasActionView = false; - for (Element intent : intents) { - boolean actionView = hasActionView(intent); - if (actionView) { - activityHasActionView = true; - } - visitIntent(context, intent); - } - if (activityHasActionView) { - applicationHasActionView = true; - if (activity.hasAttributeNS(ANDROID_URI, ATTR_EXPORTED)) { - Attr exported = activity.getAttributeNodeNS(ANDROID_URI, ATTR_EXPORTED); - if (!exported.getValue().equals("true")) { - // Report error if the activity supporting action view is not exported. - context.report(ISSUE_URL_ERROR, activity, - context.getLocation(activity), - "Activity supporting ACTION_VIEW is not exported"); - } - } - } - } - if (!applicationHasActionView && !context.getProject().isLibrary()) { - // Report warning if there is no activity that supports action view. - context.report(ISSUE_APP_INDEXING, application, context.getLocation(application), - // This error message is more verbose than the other app indexing lint warnings, because it - // shows up on a blank project, and we want to make it obvious by just looking at the error - // message what this is - "App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW " + - "intent filter. See issue explanation for more details."); - } - } - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList(CLASS_ACTIVITY); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - if (declaration.getName() == null) { - return; - } - - // In case linting the base class itself. - if (!InheritanceUtil.isInheritor(declaration, true, CLASS_ACTIVITY)) { - return; - } - - declaration.accept(new MethodVisitor(context, declaration)); - } - - static class MethodVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - private final UClass mCls; - - private final List<UCallExpression> mStartMethods; - private final List<UCallExpression> mEndMethods; - private final List<UCallExpression> mConnectMethods; - private final List<UCallExpression> mDisconnectMethods; - private boolean mHasAddAppIndexApi; - - private MethodVisitor(JavaContext context, UClass cls) { - mCls = cls; - mContext = context; - mStartMethods = Lists.newArrayListWithExpectedSize(2); - mEndMethods = Lists.newArrayListWithExpectedSize(2); - mConnectMethods = Lists.newArrayListWithExpectedSize(2); - mDisconnectMethods = Lists.newArrayListWithExpectedSize(2); - } - - @Override - public boolean visitClass(UClass aClass) { - if (aClass.getPsi().equals(mCls.getPsi())) { - return super.visitClass(aClass); - } else { - // Don't go into inner classes - return true; - } - } - - @Override - public void afterVisitClass(UClass node) { - report(); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (UastExpressionUtils.isMethodCall(node)) { - visitMethodCallExpression(node); - } - return super.visitCallExpression(node); - } - - private void visitMethodCallExpression(UCallExpression node) { - String methodName = node.getMethodName(); - if (methodName == null) { - return; - } - - if (methodName.equals(APP_INDEX_START)) { - if (JavaEvaluator.isMemberInClass(node.resolve(), APP_INDEXING_API_CLASS)) { - mStartMethods.add(node); - } - } - else if (methodName.equals(APP_INDEX_END)) { - if (JavaEvaluator.isMemberInClass(node.resolve(), APP_INDEXING_API_CLASS)) { - mEndMethods.add(node); - } - } - else if (methodName.equals(APP_INDEX_VIEW)) { - if (JavaEvaluator.isMemberInClass(node.resolve(), APP_INDEXING_API_CLASS)) { - mStartMethods.add(node); - } - } - else if (methodName.equals(APP_INDEX_VIEW_END)) { - if (JavaEvaluator.isMemberInClass(node.resolve(), APP_INDEXING_API_CLASS)) { - mEndMethods.add(node); - } - } - else if (methodName.equals(CLIENT_CONNECT)) { - if (JavaEvaluator.isMemberInClass(node.resolve(), GOOGLE_API_CLIENT_CLASS)) { - mConnectMethods.add(node); - } - } - else if (methodName.equals(CLIENT_DISCONNECT)) { - if (JavaEvaluator.isMemberInClass(node.resolve(), GOOGLE_API_CLIENT_CLASS)) { - mDisconnectMethods.add(node); - } - } - else if (methodName.equals(ADD_API)) { - if (JavaEvaluator - .isMemberInClass(node.resolve(), GOOGLE_API_CLIENT_BUILDER_CLASS)) { - List<UExpression> args = node.getValueArguments(); - if (!args.isEmpty()) { - PsiElement resolved = UastUtils.tryResolve(args.get(0)); - if (resolved instanceof PsiField && - JavaEvaluator.isMemberInClass((PsiField) resolved, API_CLASS)) { - mHasAddAppIndexApi = true; - } - } - } - } - } - - private void report() { - // finds the activity classes that need app activity annotation - Set<String> activitiesToCheck = getActivitiesToCheck(mContext); - - // app indexing API used but no support in manifest - boolean hasIntent = activitiesToCheck.contains(mCls.getQualifiedName()); - if (!hasIntent) { - for (UCallExpression call : mStartMethods) { - mContext.report(ISSUE_APP_INDEXING_API, call, - mContext.getUastNameLocation(call), - "Missing support for Google App Indexing in the manifest"); - } - for (UCallExpression call : mEndMethods) { - mContext.report(ISSUE_APP_INDEXING_API, call, - mContext.getUastNameLocation(call), - "Missing support for Google App Indexing in the manifest"); - } - return; - } - - // `AppIndex.AppIndexApi.start / end / view / viewEnd` should exist - if (mStartMethods.isEmpty() && mEndMethods.isEmpty()) { - mContext.reportUast(ISSUE_APP_INDEXING_API, mCls, - mContext.getUastNameLocation(mCls), - "Missing support for Google App Indexing API"); - return; - } - - for (UCallExpression startNode : mStartMethods) { - List<UExpression> expressions = startNode.getValueArguments(); - if (expressions.isEmpty()) { - continue; - } - UExpression startClient = expressions.get(0); - - // GoogleApiClient should `addApi(AppIndex.APP_INDEX_API)` - if (!mHasAddAppIndexApi) { - String message = String.format( - "GoogleApiClient `%1$s` has not added support for App Indexing API", - startClient.asSourceString()); - mContext.report(ISSUE_APP_INDEXING_API, startClient, - mContext.getUastLocation(startClient), message); - } - - // GoogleApiClient `connect` should exist - if (!hasOperand(startClient, mConnectMethods)) { - String message = String.format("GoogleApiClient `%1$s` is not connected", - startClient.asSourceString()); - mContext.report(ISSUE_APP_INDEXING_API, startClient, - mContext.getUastLocation(startClient), message); - } - - // `AppIndex.AppIndexApi.end` should pair with `AppIndex.AppIndexApi.start` - if (!hasFirstArgument(startClient, mEndMethods)) { - mContext.report(ISSUE_APP_INDEXING_API, startNode, - mContext.getUastNameLocation(startNode), - "Missing corresponding `AppIndex.AppIndexApi.end` method"); - } - } - - for (UCallExpression endNode : mEndMethods) { - List<UExpression> expressions = endNode.getValueArguments(); - if (expressions.isEmpty()) { - continue; - } - UExpression endClient = expressions.get(0); - - // GoogleApiClient should `addApi(AppIndex.APP_INDEX_API)` - if (!mHasAddAppIndexApi) { - String message = String.format( - "GoogleApiClient `%1$s` has not added support for App Indexing API", - endClient.asSourceString()); - mContext.report(ISSUE_APP_INDEXING_API, endClient, - mContext.getUastLocation(endClient), message); - } - - // GoogleApiClient `disconnect` should exist - if (!hasOperand(endClient, mDisconnectMethods)) { - String message = String.format("GoogleApiClient `%1$s`" - + " is not disconnected", endClient.asSourceString()); - mContext.report(ISSUE_APP_INDEXING_API, endClient, - mContext.getUastLocation(endClient), message); - } - - // `AppIndex.AppIndexApi.start` should pair with `AppIndex.AppIndexApi.end` - if (!hasFirstArgument(endClient, mStartMethods)) { - mContext.report(ISSUE_APP_INDEXING_API, endNode, - mContext.getUastNameLocation(endNode), - "Missing corresponding `AppIndex.AppIndexApi.start` method"); - } - } - } - } - - /** - * Gets names of activities which needs app indexing. i.e. the activities have data tag in their - * intent filters. - * TODO: Cache the activities to speed up batch lint. - * - * @param context The context to check in. - */ - private static Set<String> getActivitiesToCheck(Context context) { - Set<String> activitiesToCheck = Sets.newHashSet(); - List<File> manifestFiles = context.getProject().getManifestFiles(); - XmlParser xmlParser = context.getDriver().getClient().getXmlParser(); - if (xmlParser != null) { - // TODO: Avoid visit all manifest files before enable this check by default. - for (File manifest : manifestFiles) { - XmlContext xmlContext = - new XmlContext(context.getDriver(), context.getProject(), - null, manifest, null, xmlParser); - Document doc = xmlParser.parseXml(xmlContext); - if (doc != null) { - List<Element> children = LintUtils.getChildren(doc); - for (Element child : children) { - if (child.getNodeName().equals(NODE_MANIFEST)) { - List<Element> apps = extractChildrenByName(child, NODE_APPLICATION); - for (Element app : apps) { - List<Element> acts = extractChildrenByName(app, NODE_ACTIVITY); - for (Element act : acts) { - List<Element> intents = extractChildrenByName(act, NODE_INTENT); - for (Element intent : intents) { - List<Element> data = extractChildrenByName(intent, - NODE_DATA); - if (!data.isEmpty() && act.hasAttributeNS( - ANDROID_URI, ATTRIBUTE_NAME)) { - Attr attr = act.getAttributeNodeNS( - ANDROID_URI, ATTRIBUTE_NAME); - String activityName = attr.getValue(); - int dotIndex = activityName.indexOf('.'); - if (dotIndex <= 0) { - String pkg = context.getMainProject().getPackage(); - if (pkg != null) { - if (dotIndex == 0) { - activityName = pkg + activityName; - } - else { - activityName = pkg + '.' + activityName; - } - } - } - activitiesToCheck.add(activityName); - } - } - } - } - } - } - } - } - } - return activitiesToCheck; - } - - private static void visitIntent(@NonNull XmlContext context, @NonNull Element intent) { - boolean actionView = hasActionView(intent); - boolean browsable = isBrowsable(intent); - boolean isHttp = false; - boolean hasScheme = false; - boolean hasHost = false; - boolean hasPort = false; - boolean hasPath = false; - boolean hasMimeType = false; - Element firstData = null; - List<Element> children = extractChildrenByName(intent, NODE_DATA); - for (Element data : children) { - if (firstData == null) { - firstData = data; - } - if (isHttpSchema(data)) { - isHttp = true; - } - checkSingleData(context, data); - - for (String name : PATH_ATTR_LIST) { - if (data.hasAttributeNS(ANDROID_URI, name)) { - hasPath = true; - } - } - - if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) { - hasScheme = true; - } - - if (data.hasAttributeNS(ANDROID_URI, ATTR_HOST)) { - hasHost = true; - } - - if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) { - hasPort = true; - } - - if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_MIME_TYPE)) { - hasMimeType = true; - } - } - - // In data field, a URL is consisted by - // <scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>] - // Each part of the URL should not have illegal character. - if ((hasPath || hasHost || hasPort) && !hasScheme) { - context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData), - SCHEME_MISSING); - } - - if ((hasPath || hasPort) && !hasHost) { - context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData), - HOST_MISSING); - } - - if (actionView && browsable) { - if (firstData == null) { - // If this activity is an ACTION_VIEW action with category BROWSABLE, but doesn't - // have data node, it may be a mistake and we will report error. - context.report(ISSUE_URL_ERROR, intent, context.getLocation(intent), - DATA_MISSING); - } else if (!hasScheme && !hasMimeType) { - // If this activity is an action view, is browsable, but has neither a - // URL nor mimeType, it may be a mistake and we will report error. - context.report(ISSUE_URL_ERROR, firstData, context.getLocation(firstData), - URL_MISSING); - } - } - - // If this activity is an ACTION_VIEW action, has a http URL but doesn't have - // BROWSABLE, it may be a mistake and and we will report warning. - if (actionView && isHttp && !browsable) { - context.report(ISSUE_APP_INDEXING, intent, context.getLocation(intent), - NOT_BROWSABLE); - } - - if (actionView && !hasScheme) { - context.report(ISSUE_APP_INDEXING, intent, context.getLocation(intent), - "Missing URL"); - } - } - - /** - * Check if the intent filter supports action view. - * - * @param intent the intent filter - * @return true if it does - */ - private static boolean hasActionView(@NonNull Element intent) { - List<Element> children = extractChildrenByName(intent, NODE_ACTION); - for (Element action : children) { - if (action.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) { - Attr attr = action.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME); - if (attr.getValue().equals("android.intent.action.VIEW")) { - return true; - } - } - } - return false; - } - - /** - * Check if the intent filter is browsable. - * - * @param intent the intent filter - * @return true if it does - */ - private static boolean isBrowsable(@NonNull Element intent) { - List<Element> children = extractChildrenByName(intent, NODE_CATEGORY); - for (Element e : children) { - if (e.hasAttributeNS(ANDROID_URI, ATTRIBUTE_NAME)) { - Attr attr = e.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_NAME); - if (attr.getNodeValue().equals("android.intent.category.BROWSABLE")) { - return true; - } - } - } - return false; - } - - /** - * Check if the data node contains http schema - * - * @param data the data node - * @return true if it does - */ - private static boolean isHttpSchema(@NonNull Element data) { - if (data.hasAttributeNS(ANDROID_URI, ATTR_SCHEME)) { - String value = data.getAttributeNodeNS(ANDROID_URI, ATTR_SCHEME).getValue(); - if (value.equalsIgnoreCase("http") || value.equalsIgnoreCase("https")) { - return true; - } - } - return false; - } - - private static void checkSingleData(@NonNull XmlContext context, @NonNull Element data) { - // path, pathPrefix and pathPattern should starts with /. - for (String name : PATH_ATTR_LIST) { - if (data.hasAttributeNS(ANDROID_URI, name)) { - Attr attr = data.getAttributeNodeNS(ANDROID_URI, name); - String path = replaceUrlWithValue(context, attr.getValue()); - if (!path.startsWith("/") && !path.startsWith(SdkConstants.PREFIX_RESOURCE_REF)) { - context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr), - "android:" + name + " attribute should start with '/', but it is : " - + path); - } - } - } - - // port should be a legal number. - if (data.hasAttributeNS(ANDROID_URI, ATTRIBUTE_PORT)) { - Attr attr = data.getAttributeNodeNS(ANDROID_URI, ATTRIBUTE_PORT); - try { - String port = replaceUrlWithValue(context, attr.getValue()); - //noinspection ResultOfMethodCallIgnored - Integer.parseInt(port); - } catch (NumberFormatException e) { - context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr), - ILLEGAL_NUMBER); - } - } - - // Each field should be non empty. - NamedNodeMap attrs = data.getAttributes(); - for (int i = 0; i < attrs.getLength(); i++) { - Node item = attrs.item(i); - if (item.getNodeType() == Node.ATTRIBUTE_NODE) { - Attr attr = (Attr) attrs.item(i); - if (attr.getValue().isEmpty()) { - context.report(ISSUE_URL_ERROR, attr, context.getLocation(attr), - attr.getName() + " cannot be empty"); - } - } - } - } - - private static String replaceUrlWithValue(@NonNull XmlContext context, - @NonNull String str) { - Project project = context.getProject(); - LintClient client = context.getClient(); - if (!client.supportsProjectResources()) { - return str; - } - ResourceUrl style = ResourceUrl.parse(str); - if (style == null || style.type != ResourceType.STRING || style.framework) { - return str; - } - AbstractResourceRepository resources = client.getProjectResources(project, true); - if (resources == null) { - return str; - } - List<ResourceItem> items = resources.getResourceItem(ResourceType.STRING, style.name); - if (items == null || items.isEmpty()) { - return str; - } - ResourceValue resourceValue = items.get(0).getResourceValue(false); - if (resourceValue == null) { - return str; - } - return resourceValue.getValue() == null ? str : resourceValue.getValue(); - } - - /** - * If a method with a certain argument exists in the list of methods. - * - * @param argument The first argument of the method. - * @param list The methods list. - * @return If such a method exists in the list. - */ - private static boolean hasFirstArgument(UExpression argument, List<UCallExpression> list) { - for (UCallExpression call : list) { - List<UExpression> expressions = call.getValueArguments(); - if (!expressions.isEmpty()) { - UExpression argument2 = expressions.get(0); - if (argument.asSourceString().equals(argument2.asSourceString())) { - return true; - } - } - } - return false; - } - - /** - * If a method with a certain operand exists in the list of methods. - * - * @param operand The operand of the method. - * @param list The methods list. - * @return If such a method exists in the list. - */ - private static boolean hasOperand(UExpression operand, List<UCallExpression> list) { - for (UCallExpression method : list) { - UElement operand2 = method.getReceiver(); - if (operand2 != null && operand.asSourceString().equals(operand2.asSourceString())) { - return true; - } - } - return false; - } - - private static List<Element> extractChildrenByName(@NonNull Element node, - @NonNull String name) { - List<Element> result = Lists.newArrayList(); - List<Element> children = LintUtils.getChildren(node); - for (Element child : children) { - if (child.getNodeName().equals(name)) { - result.add(child); - } - } - return result; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BadHostnameVerifierDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BadHostnameVerifierDetector.java deleted file mode 100644 index cc6a6c5..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BadHostnameVerifierDetector.java +++ /dev/null
@@ -1,141 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.JavaRecursiveElementVisitor; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiReturnStatement; -import com.intellij.psi.PsiThrowStatement; - -import org.jetbrains.uast.*; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -public class BadHostnameVerifierDetector extends Detector implements Detector.UastScanner { - - @SuppressWarnings("unchecked") - private static final Implementation IMPLEMENTATION = - new Implementation(BadHostnameVerifierDetector.class, - Scope.JAVA_FILE_SCOPE); - - public static final Issue ISSUE = Issue.create("BadHostnameVerifier", - "Insecure HostnameVerifier", - "This check looks for implementations of `HostnameVerifier` " + - "whose `verify` method always returns true (thus trusting any hostname) " + - "which could result in insecure network traffic caused by trusting arbitrary " + - "hostnames in TLS/SSL certificates presented by peers.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION); - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList("javax.net.ssl.HostnameVerifier"); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - JavaEvaluator evaluator = context.getEvaluator(); - for (PsiMethod method : declaration.findMethodsByName("verify", false)) { - if (evaluator.methodMatches(method, null, false, - TYPE_STRING, "javax.net.ssl.SSLSession")) { - ComplexVisitor visitor = new ComplexVisitor(context); - declaration.accept(visitor); - if (visitor.isComplex()) { - return; - } - - Location location = context.getNameLocation(method); - String message = String.format("`%1$s` always returns `true`, which " + - "could cause insecure network traffic due to trusting " - + "TLS/SSL server certificates for wrong hostnames", - method.getName()); - context.report(ISSUE, location, message); - break; - } - } - } - - private static class ComplexVisitor extends AbstractUastVisitor { - @SuppressWarnings("unused") - private final JavaContext mContext; - private boolean mComplex; - - public ComplexVisitor(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitThrowExpression(UThrowExpression node) { - mComplex = true; - return super.visitThrowExpression(node); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - // TODO: Ignore certain known safe methods, e.g. Logging etc - mComplex = true; - return super.visitCallExpression(node); - } - - @Override - public boolean visitReturnExpression(UReturnExpression node) { - UExpression argument = node.getReturnExpression(); - if (argument != null) { - // TODO: Only do this if certain that there isn't some intermediate - // assignment, as exposed by the unit test - //Object value = ConstantEvaluator.evaluate(mContext, argument); - //if (Boolean.TRUE.equals(value)) { - if (UastLiteralUtils.isTrueLiteral(argument)) { - mComplex = false; - } else { - mComplex = true; // "return false" or some complicated logic - } - } - return super.visitReturnExpression(node); - } - - private boolean isComplex() { - return mComplex; - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BatteryDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BatteryDetector.java deleted file mode 100644 index 7853317..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BatteryDetector.java +++ /dev/null
@@ -1,139 +0,0 @@ -/* - * 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.*; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; - -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; - -import static com.android.SdkConstants.*; - -/** - * Checks looking for issues that negatively affect battery life - */ -public class BatteryDetector extends ResourceXmlDetector implements - Detector.UastScanner { - - @SuppressWarnings("unchecked") - public static final Implementation IMPLEMENTATION = new Implementation( - BatteryDetector.class, - EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE), - Scope.MANIFEST_SCOPE, - Scope.JAVA_FILE_SCOPE); - - /** Issues that negatively affect battery life */ - public static final Issue ISSUE = Issue.create( - "BatteryLife", //$NON-NLS-1$ - "Battery Life Issues", - - "This issue flags code that either\n" + - "* negatively affects battery life, or\n" + - "* uses APIs that have recently changed behavior to prevent background tasks " + - "from consuming memory and battery excessively.\n" + - "\n" + - "Generally, you should be using `JobScheduler` or `GcmNetworkManager` instead.\n" + - "\n" + - "For more details on how to update your code, please see" + - "http://developer.android.com/preview/features/background-optimization.html", - - Category.CORRECTNESS, - 5, - Severity.WARNING, - IMPLEMENTATION) - .addMoreInfo("http://developer.android.com/preview/features/background-optimization.html"); - - /** Constructs a new {@link BatteryDetector} */ - public BatteryDetector() { - } - - @Override - public Collection<String> getApplicableElements() { - return Collections.singletonList("action"); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - assert element.getTagName().equals("action"); - Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME); - if (attr == null) { - return; - } - String name = attr.getValue(); - if ("android.net.conn.CONNECTIVITY_CHANGE".equals(name) - && element.getParentNode() != null - && element.getParentNode().getParentNode() != null - && TAG_RECEIVER.equals(element.getParentNode().getParentNode().getNodeName()) - && context.getMainProject().getTargetSdkVersion().getFeatureLevel() >= 24) { - String message = "Declaring a broadcastreceiver for " - + "`android.net.conn.CONNECTIVITY_CHANGE` is deprecated for apps targeting " - + "N and higher. In general, apps should not rely on this broadcast and " - + "instead use `JobScheduler` or `GCMNetworkManager`."; - context.report(ISSUE, element, context.getValueLocation(attr), message); - } - - if ("android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS".equals(name) - && context.getMainProject().getTargetSdkVersion().getFeatureLevel() >= 23) { - String message = getBatteryOptimizationsErrorMessage(); - context.report(ISSUE, element, context.getValueLocation(attr), message); - } - - if ("android.hardware.action.NEW_PICTURE".equals(name) - || "android.hardware.action.NEW_VIDEO".equals(name) - || "com.android.camera.NEW_PICTURE".equals(name)) { - String message = String.format("Use of %1$s is deprecated for all apps starting " - + "with the N release independent of the target SDK. Apps should not " - + "rely on these broadcasts and instead use `JobScheduler`", name); - context.report(ISSUE, element, context.getValueLocation(attr), message); - } - } - - @Nullable - @Override - public List<String> getApplicableReferenceNames() { - return Collections.singletonList("ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"); - } - - @Override - public void visitReference(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UReferenceExpression reference, @NonNull PsiElement resolved) { - if (resolved instanceof PsiField && - context.getEvaluator().isMemberInSubClassOf((PsiField) resolved, - "android.provider.Settings", false) - && context.getMainProject().getTargetSdkVersion().getFeatureLevel() >= 23) { - String message = getBatteryOptimizationsErrorMessage(); - context.report(ISSUE, reference, context.getUastNameLocation(reference), message); - } - } - - @NonNull - private static String getBatteryOptimizationsErrorMessage() { - return "Use of `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` violates the " - + "Play Store Content Policy regarding acceptable use cases, as described in " - + "http://developer.android.com/training/monitoring-device-state/doze-standby.html"; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java deleted file mode 100644 index 3990ffa..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java +++ /dev/null
@@ -1,208 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.VisibleForTesting; -import com.android.tools.klint.client.api.IssueRegistry; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Scope; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; - -/** Registry which provides a list of checks to be performed on an Android project */ -public class BuiltinIssueRegistry extends IssueRegistry { - private static final List<Issue> sIssues; - - static final int INITIAL_CAPACITY = 262; - - static { - List<Issue> issues = new ArrayList<Issue>(INITIAL_CAPACITY); - - issues.add(AddJavascriptInterfaceDetector.ISSUE); - issues.add(AlarmDetector.ISSUE); - issues.add(AllowAllHostnameVerifierDetector.ISSUE); - issues.add(AlwaysShowActionDetector.ISSUE); - issues.add(AndroidAutoDetector.INVALID_USES_TAG_ISSUE); - issues.add(AndroidAutoDetector.MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH); - issues.add(AndroidAutoDetector.MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE); - issues.add(AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH); - issues.add(AnnotationDetector.ANNOTATION_USAGE); - issues.add(AnnotationDetector.FLAG_STYLE); - issues.add(AnnotationDetector.INSIDE_METHOD); - issues.add(AnnotationDetector.SWITCH_TYPE_DEF); - issues.add(AnnotationDetector.UNIQUE); - issues.add(ApiDetector.INLINED); - issues.add(ApiDetector.OVERRIDE); - issues.add(ApiDetector.UNSUPPORTED); - issues.add(ApiDetector.UNUSED); - issues.add(AppCompatCallDetector.ISSUE); - issues.add(AppIndexingApiDetector.ISSUE_APP_INDEXING_API); - issues.add(AppIndexingApiDetector.ISSUE_URL_ERROR); - issues.add(AppIndexingApiDetector.ISSUE_APP_INDEXING); - issues.add(BadHostnameVerifierDetector.ISSUE); - issues.add(BatteryDetector.ISSUE); - issues.add(CallSuperDetector.ISSUE); - issues.add(CipherGetInstanceDetector.ISSUE); - issues.add(CleanupDetector.COMMIT_FRAGMENT); - issues.add(CleanupDetector.RECYCLE_RESOURCE); - issues.add(CleanupDetector.SHARED_PREF); - issues.add(CommentDetector.EASTER_EGG); - issues.add(CommentDetector.STOP_SHIP); - issues.add(CustomViewDetector.ISSUE); - issues.add(CutPasteDetector.ISSUE); - issues.add(DateFormatDetector.DATE_FORMAT); - issues.add(SetTextDetector.SET_TEXT_I18N); - issues.add(UnsafeNativeCodeDetector.LOAD); - issues.add(UnsafeNativeCodeDetector.UNSAFE_NATIVE_CODE_LOCATION); - issues.add(FragmentDetector.ISSUE); - issues.add(GetSignaturesDetector.ISSUE); - issues.add(HandlerDetector.ISSUE); - issues.add(IconDetector.DUPLICATES_CONFIGURATIONS); - issues.add(IconDetector.DUPLICATES_NAMES); - issues.add(IconDetector.GIF_USAGE); - issues.add(IconDetector.ICON_COLORS); - issues.add(IconDetector.ICON_DENSITIES); - issues.add(IconDetector.ICON_DIP_SIZE); - issues.add(IconDetector.ICON_EXPECTED_SIZE); - issues.add(IconDetector.ICON_EXTENSION); - issues.add(IconDetector.ICON_LAUNCHER_SHAPE); - issues.add(IconDetector.ICON_LOCATION); - issues.add(IconDetector.ICON_MISSING_FOLDER); - issues.add(IconDetector.ICON_MIX_9PNG); - issues.add(IconDetector.ICON_NODPI); - issues.add(IconDetector.ICON_XML_AND_PNG); - issues.add(TrustAllX509TrustManagerDetector.ISSUE); - issues.add(JavaPerformanceDetector.PAINT_ALLOC); - issues.add(JavaPerformanceDetector.USE_SPARSE_ARRAY); - issues.add(JavaPerformanceDetector.USE_VALUE_OF); - issues.add(JavaScriptInterfaceDetector.ISSUE); - issues.add(LayoutConsistencyDetector.INCONSISTENT_IDS); - issues.add(LayoutInflationDetector.ISSUE); - issues.add(LeakDetector.ISSUE); - issues.add(LocaleDetector.STRING_LOCALE); - issues.add(LogDetector.CONDITIONAL); - issues.add(LogDetector.LONG_TAG); - issues.add(LogDetector.WRONG_TAG); - issues.add(MathDetector.ISSUE); - issues.add(MergeRootFrameLayoutDetector.ISSUE); - issues.add(NonInternationalizedSmsDetector.ISSUE); - issues.add(OverdrawDetector.ISSUE); - issues.add(OverrideConcreteDetector.ISSUE); - issues.add(ParcelDetector.ISSUE); - issues.add(PreferenceActivityDetector.ISSUE); - issues.add(PrivateResourceDetector.ISSUE); - issues.add(ReadParcelableDetector.ISSUE); - issues.add(RecyclerViewDetector.DATA_BINDER); - issues.add(RecyclerViewDetector.FIXED_POSITION); - issues.add(RegistrationDetector.ISSUE); - issues.add(RequiredAttributeDetector.ISSUE); - issues.add(RtlDetector.COMPAT); - issues.add(RtlDetector.ENABLED); - issues.add(RtlDetector.SYMMETRY); - issues.add(RtlDetector.USE_START); - issues.add(SdCardDetector.ISSUE); - issues.add(SecureRandomDetector.ISSUE); - issues.add(SecurityDetector.EXPORTED_PROVIDER); - issues.add(SecurityDetector.EXPORTED_RECEIVER); - issues.add(SecurityDetector.EXPORTED_SERVICE); - issues.add(SecurityDetector.SET_READABLE); - issues.add(SecurityDetector.SET_WRITABLE); - issues.add(SecurityDetector.OPEN_PROVIDER); - issues.add(SecurityDetector.WORLD_READABLE); - issues.add(SecurityDetector.WORLD_WRITEABLE); - issues.add(ServiceCastDetector.ISSUE); - issues.add(SetJavaScriptEnabledDetector.ISSUE); - issues.add(SQLiteDetector.ISSUE); - issues.add(SslCertificateSocketFactoryDetector.CREATE_SOCKET); - issues.add(SslCertificateSocketFactoryDetector.GET_INSECURE); - issues.add(StringAuthLeakDetector.AUTH_LEAK); - issues.add(StringFormatDetector.ARG_COUNT); - issues.add(StringFormatDetector.ARG_TYPES); - issues.add(StringFormatDetector.INVALID); - issues.add(StringFormatDetector.POTENTIAL_PLURAL); - issues.add(SupportAnnotationDetector.CHECK_PERMISSION); - issues.add(SupportAnnotationDetector.CHECK_RESULT); - issues.add(SupportAnnotationDetector.COLOR_USAGE); - issues.add(SupportAnnotationDetector.MISSING_PERMISSION); - issues.add(SupportAnnotationDetector.RANGE); - issues.add(SupportAnnotationDetector.RESOURCE_TYPE); - issues.add(SupportAnnotationDetector.THREAD); - issues.add(SupportAnnotationDetector.TYPE_DEF); - issues.add(ToastDetector.ISSUE); - issues.add(UnsafeBroadcastReceiverDetector.ACTION_STRING); - issues.add(UnsafeBroadcastReceiverDetector.BROADCAST_SMS); - issues.add(ViewConstructorDetector.ISSUE); - issues.add(ViewHolderDetector.ISSUE); - issues.add(ViewTagDetector.ISSUE); - issues.add(ViewTypeDetector.ISSUE); - issues.add(WrongCallDetector.ISSUE); - issues.add(WrongImportDetector.ISSUE); - - sIssues = Collections.unmodifiableList(issues); - } - - /** - * Constructs a new {@link BuiltinIssueRegistry} - */ - public BuiltinIssueRegistry() { - } - - @NonNull - @Override - public List<Issue> getIssues() { - return sIssues; - } - - @Override - protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) { - if (scope.equals(Scope.ALL)) { - return getIssues().size(); - } else { - int initialSize = 12; - if (scope.contains(Scope.RESOURCE_FILE)) { - initialSize += 80; - } else if (scope.contains(Scope.ALL_RESOURCE_FILES)) { - initialSize += 12; - } - - if (scope.contains(Scope.JAVA_FILE)) { - initialSize += 74; - } else if (scope.contains(Scope.CLASS_FILE)) { - initialSize += 15; - } else if (scope.contains(Scope.MANIFEST)) { - initialSize += 38; - } else if (scope.contains(Scope.GRADLE_FILE)) { - initialSize += 5; - } - return initialSize; - } - } - - /** - * Reset the registry such that it recomputes its available issues. - * <p> - * NOTE: This is only intended for testing purposes. - */ - @VisibleForTesting - public static void reset() { - IssueRegistry.reset(); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CallSuperDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CallSuperDetector.java deleted file mode 100644 index a62707d..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CallSuperDetector.java +++ /dev/null
@@ -1,191 +0,0 @@ -/* - * 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.*; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.USuperExpression; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -import static com.android.SdkConstants.CLASS_VIEW; -import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX; -import static com.android.tools.klint.checks.SupportAnnotationDetector.filterRelevantAnnotations; -import static com.android.tools.klint.detector.api.LintUtils.skipParentheses; - -/** - * Makes sure that methods call super when overriding methods. - */ -public class CallSuperDetector extends Detector implements Detector.UastScanner { - private static final String CALL_SUPER_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "CallSuper"; //$NON-NLS-1$ - private static final String ON_DETACHED_FROM_WINDOW = "onDetachedFromWindow"; //$NON-NLS-1$ - private static final String ON_VISIBILITY_CHANGED = "onVisibilityChanged"; //$NON-NLS-1$ - - private static final Implementation IMPLEMENTATION = new Implementation( - CallSuperDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Missing call to super */ - public static final Issue ISSUE = Issue.create( - "MissingSuperCall", //$NON-NLS-1$ - "Missing Super Call", - - "Some methods, such as `View#onDetachedFromWindow`, require that you also " + - "call the super implementation as part of your method.", - - Category.CORRECTNESS, - 9, - Severity.ERROR, - IMPLEMENTATION); - - /** Constructs a new {@link CallSuperDetector} check */ - public CallSuperDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - return Collections.<Class<? extends UElement>>singletonList(UMethod.class); - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull final JavaContext context) { - return new AbstractUastVisitor() { - @Override - public boolean visitMethod(UMethod method) { - checkCallSuper(context, method); - return super.visitMethod(method); - } - }; - } - - private static void checkCallSuper(@NonNull JavaContext context, - @NonNull UMethod method) { - - PsiMethod superMethod = getRequiredSuperMethod(context, method); - if (superMethod != null) { - if (!SuperCallVisitor.callsSuper(method, superMethod)) { - String methodName = method.getName(); - String message = "Overriding method should call `super." - + methodName + "`"; - Location location = context.getUastNameLocation(method); - context.reportUast(ISSUE, method, location, message); - } - } - } - - /** - * Checks whether the given method overrides a method which requires the super method - * to be invoked, and if so, returns it (otherwise returns null) - */ - @Nullable - private static PsiMethod getRequiredSuperMethod(@NonNull JavaContext context, - @NonNull PsiMethod method) { - - JavaEvaluator evaluator = context.getEvaluator(); - PsiMethod directSuper = evaluator.getSuperMethod(method); - if (directSuper == null) { - return null; - } - - String name = method.getName(); - if (ON_DETACHED_FROM_WINDOW.equals(name)) { - // No longer annotated on the framework method since it's - // now handled via onDetachedFromWindowInternal, but overriding - // is still dangerous if supporting older versions so flag - // this for now (should make annotation carry metadata like - // compileSdkVersion >= N). - if (!evaluator.isMemberInSubClassOf(method, CLASS_VIEW, false)) { - return null; - } - return directSuper; - } else if (ON_VISIBILITY_CHANGED.equals(name)) { - // From Android Wear API; doesn't yet have an annotation - // but we want to enforce this right away until the AAR - // is updated to supply it once @CallSuper is available in - // the support library - if (!evaluator.isMemberInSubClassOf(method, - "android.support.wearable.watchface.WatchFaceService.Engine", false)) { - return null; - } - return directSuper; - } - - // Look up annotations metadata - PsiMethod superMethod = directSuper; - while (superMethod != null) { - PsiAnnotation[] annotations = superMethod.getModifierList().getAnnotations(); - annotations = filterRelevantAnnotations(context.getEvaluator(), annotations); - for (PsiAnnotation annotation : annotations) { - String signature = annotation.getQualifiedName(); - if (CALL_SUPER_ANNOTATION.equals(signature)) { - return directSuper; - } else if (signature != null && signature.endsWith(".OverrideMustInvoke")) { - // Handle findbugs annotation on the fly too - return directSuper; - } - } - superMethod = evaluator.getSuperMethod(superMethod); - } - - return null; - } - - /** Visits a method and determines whether the method calls its super method */ - private static class SuperCallVisitor extends AbstractUastVisitor { - private final PsiMethod mMethod; - private boolean mCallsSuper; - - public static boolean callsSuper(@NonNull UMethod method, - @NonNull PsiMethod superMethod) { - SuperCallVisitor visitor = new SuperCallVisitor(superMethod); - method.accept(visitor); - return visitor.mCallsSuper; - } - - private SuperCallVisitor(@NonNull PsiMethod method) { - mMethod = method; - } - - @Override - public boolean visitSuperExpression(USuperExpression node) { - UElement parent = skipParentheses(node.getUastParent()); - if (parent instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) parent).resolve(); - if (mMethod.equals(resolved)) { - mCallsSuper = true; - } - } - - return super.visitSuperExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CipherGetInstanceDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CipherGetInstanceDetector.java deleted file mode 100644 index cdb9f53..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CipherGetInstanceDetector.java +++ /dev/null
@@ -1,105 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.google.common.collect.Sets; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * Ensures that Cipher.getInstance is not called with AES as the parameter. - */ -public class CipherGetInstanceDetector extends Detector implements Detector.UastScanner { - public static final Issue ISSUE = Issue.create( - "GetInstance", //$NON-NLS-1$ - "Cipher.getInstance with ECB", - "`Cipher#getInstance` should not be called with ECB as the cipher mode or without " + - "setting the cipher mode because the default mode on android is ECB, which " + - "is insecure.", - Category.SECURITY, - 9, - Severity.WARNING, - new Implementation( - CipherGetInstanceDetector.class, - Scope.JAVA_FILE_SCOPE)); - - private static final String CIPHER = "javax.crypto.Cipher"; //$NON-NLS-1$ - private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$ - private static final Set<String> ALGORITHM_ONLY = - Sets.newHashSet("AES", "DES", "DESede"); //$NON-NLS-1$ - private static final String ECB = "ECB"; //$NON-NLS-1$ - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList(GET_INSTANCE); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - if (!context.getEvaluator().isMemberInSubClassOf(method, CIPHER, false)) { - return; - } - List<UExpression> arguments = node.getValueArguments(); - if (arguments.size() == 1) { - UExpression expression = arguments.get(0); - Object value = ConstantEvaluator.evaluate(context, expression); - if (value instanceof String) { - checkParameter(context, node, expression, (String)value, - !(expression instanceof ULiteralExpression)); - } - } - } - - private static void checkParameter(@NonNull JavaContext context, - @NonNull UCallExpression call, @NonNull UElement node, @NonNull String value, - boolean includeValue) { - if (ALGORITHM_ONLY.contains(value)) { - String message = "`Cipher.getInstance` should not be called without setting the" - + " encryption mode and padding"; - context.report(ISSUE, call, context.getUastLocation(node), message); - } else if (value.contains(ECB)) { - String message = "ECB encryption mode should not be used"; - if (includeValue) { - message += " (was \"" + value + "\")"; - } - context.report(ISSUE, call, context.getUastLocation(node), message); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java deleted file mode 100644 index c55d7cb..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java +++ /dev/null
@@ -1,935 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.*; -import com.google.common.collect.Lists; -import com.intellij.psi.*; -import com.intellij.psi.util.InheritanceUtil; -import com.intellij.util.containers.Predicate; -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.List; - -import static com.android.SdkConstants.CLASS_CONTENTPROVIDER; -import static com.android.SdkConstants.CLASS_CONTEXT; -import static com.android.tools.klint.detector.api.LintUtils.skipParentheses; -import static org.jetbrains.uast.UastUtils.*; - -/** - * Checks for missing {@code recycle} calls on resources that encourage it, and - * for missing {@code commit} calls on FragmentTransactions, etc. - */ -public class CleanupDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - CleanupDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Problems with missing recycle calls */ - public static final Issue RECYCLE_RESOURCE = Issue.create( - "Recycle", //$NON-NLS-1$ - "Missing `recycle()` calls", - - "Many resources, such as TypedArrays, VelocityTrackers, etc., " + - "should be recycled (with a `recycle()` call) after use. This lint check looks " + - "for missing `recycle()` calls.", - - Category.PERFORMANCE, - 7, - Severity.WARNING, - IMPLEMENTATION); - - /** Problems with missing commit calls. */ - public static final Issue COMMIT_FRAGMENT = Issue.create( - "CommitTransaction", //$NON-NLS-1$ - "Missing `commit()` calls", - - "After creating a `FragmentTransaction`, you typically need to commit it as well", - - Category.CORRECTNESS, - 7, - Severity.WARNING, - IMPLEMENTATION); - - /** The main issue discovered by this detector */ - public static final Issue SHARED_PREF = Issue.create( - "CommitPrefEdits", //$NON-NLS-1$ - "Missing `commit()` on `SharedPreference` editor", - - "After calling `edit()` on a `SharedPreference`, you must call `commit()` " + - "or `apply()` on the editor to save the results.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - CleanupDetector.class, - Scope.JAVA_FILE_SCOPE)); - - // Target method names - private static final String RECYCLE = "recycle"; //$NON-NLS-1$ - private static final String RELEASE = "release"; //$NON-NLS-1$ - private static final String OBTAIN = "obtain"; //$NON-NLS-1$ - private static final String SHOW = "show"; //$NON-NLS-1$ - private static final String ACQUIRE_CPC = "acquireContentProviderClient"; //$NON-NLS-1$ - private static final String OBTAIN_NO_HISTORY = "obtainNoHistory"; //$NON-NLS-1$ - private static final String OBTAIN_ATTRIBUTES = "obtainAttributes"; //$NON-NLS-1$ - private static final String OBTAIN_TYPED_ARRAY = "obtainTypedArray"; //$NON-NLS-1$ - private static final String OBTAIN_STYLED_ATTRIBUTES = "obtainStyledAttributes"; //$NON-NLS-1$ - private static final String BEGIN_TRANSACTION = "beginTransaction"; //$NON-NLS-1$ - private static final String COMMIT = "commit"; //$NON-NLS-1$ - private static final String APPLY = "apply"; //$NON-NLS-1$ - private static final String COMMIT_ALLOWING_LOSS = "commitAllowingStateLoss"; //$NON-NLS-1$ - private static final String QUERY = "query"; //$NON-NLS-1$ - private static final String RAW_QUERY = "rawQuery"; //$NON-NLS-1$ - private static final String QUERY_WITH_FACTORY = "queryWithFactory"; //$NON-NLS-1$ - private static final String RAW_QUERY_WITH_FACTORY = "rawQueryWithFactory"; //$NON-NLS-1$ - private static final String CLOSE = "close"; //$NON-NLS-1$ - private static final String USE = "use"; //$NON-NLS-1$ - private static final String EDIT = "edit"; //$NON-NLS-1$ - - private static final String MOTION_EVENT_CLS = "android.view.MotionEvent"; //$NON-NLS-1$ - private static final String PARCEL_CLS = "android.os.Parcel"; //$NON-NLS-1$ - private static final String VELOCITY_TRACKER_CLS = "android.view.VelocityTracker";//$NON-NLS-1$ - private static final String DIALOG_FRAGMENT = "android.app.DialogFragment"; //$NON-NLS-1$ - private static final String DIALOG_V4_FRAGMENT = - "android.support.v4.app.DialogFragment"; //$NON-NLS-1$ - private static final String FRAGMENT_MANAGER_CLS = "android.app.FragmentManager"; //$NON-NLS-1$ - private static final String FRAGMENT_MANAGER_V4_CLS = - "android.support.v4.app.FragmentManager"; //$NON-NLS-1$ - private static final String FRAGMENT_TRANSACTION_CLS = - "android.app.FragmentTransaction"; //$NON-NLS-1$ - private static final String FRAGMENT_TRANSACTION_V4_CLS = - "android.support.v4.app.FragmentTransaction"; //$NON-NLS-1$ - - public static final String SURFACE_CLS = "android.view.Surface"; - public static final String SURFACE_TEXTURE_CLS = "android.graphics.SurfaceTexture"; - - public static final String CONTENT_PROVIDER_CLIENT_CLS - = "android.content.ContentProviderClient"; - - public static final String CONTENT_RESOLVER_CLS = "android.content.ContentResolver"; - - @SuppressWarnings("SpellCheckingInspection") - public static final String SQLITE_DATABASE_CLS = "android.database.sqlite.SQLiteDatabase"; - public static final String CURSOR_CLS = "android.database.Cursor"; - - public static final String ANDROID_CONTENT_SHARED_PREFERENCES = - "android.content.SharedPreferences"; //$NON-NLS-1$ - private static final String ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR = - "android.content.SharedPreferences.Editor"; //$NON-NLS-1$ - private static final String CLOSABLE = "java.io.Closeable"; //$NON-NLS-1$ - - - /** Constructs a new {@link CleanupDetector} */ - public CleanupDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Arrays.asList( - // FragmentManager commit check - BEGIN_TRANSACTION, - - // Recycle check - OBTAIN, OBTAIN_NO_HISTORY, - OBTAIN_STYLED_ATTRIBUTES, - OBTAIN_ATTRIBUTES, - OBTAIN_TYPED_ARRAY, - - // Release check - ACQUIRE_CPC, - - // Cursor close check - QUERY, RAW_QUERY, QUERY_WITH_FACTORY, RAW_QUERY_WITH_FACTORY, - - // SharedPreferences check - EDIT - ); - } - - @Nullable - @Override - public List<String> getApplicableConstructorTypes() { - return Arrays.asList(SURFACE_TEXTURE_CLS, SURFACE_CLS); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - String name = method.getName(); - if (BEGIN_TRANSACTION.equals(name)) { - checkTransactionCommits(context, call, method); - } else if (EDIT.equals(name)) { - checkEditorApplied(context, call, method); - } else { - checkResourceRecycled(context, call, method); - } - } - - @Override - public void visitConstructor(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod constructor) { - PsiClass containingClass = constructor.getContainingClass(); - if (containingClass != null) { - String type = containingClass.getQualifiedName(); - if (type != null) { - checkRecycled(context, node, type, RELEASE); - } - } - } - - private static void checkResourceRecycled(@NonNull JavaContext context, - @NonNull UCallExpression node, @NonNull PsiMethod method) { - String name = method.getName(); - // Recycle detector - PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - return; - } - JavaEvaluator evaluator = context.getEvaluator(); - if ((OBTAIN.equals(name) || OBTAIN_NO_HISTORY.equals(name)) && - InheritanceUtil.isInheritor(containingClass, false, MOTION_EVENT_CLS)) { - checkRecycled(context, node, MOTION_EVENT_CLS, RECYCLE); - } else if (OBTAIN.equals(name) && InheritanceUtil.isInheritor(containingClass, false, PARCEL_CLS)) { - checkRecycled(context, node, PARCEL_CLS, RECYCLE); - } else if (OBTAIN.equals(name) && - InheritanceUtil.isInheritor(containingClass, false, VELOCITY_TRACKER_CLS)) { - checkRecycled(context, node, VELOCITY_TRACKER_CLS, RECYCLE); - } else if ((OBTAIN_STYLED_ATTRIBUTES.equals(name) - || OBTAIN_ATTRIBUTES.equals(name) - || OBTAIN_TYPED_ARRAY.equals(name)) && - (InheritanceUtil.isInheritor(containingClass, false, CLASS_CONTEXT) || - InheritanceUtil.isInheritor(containingClass, false, SdkConstants.CLASS_RESOURCES))) { - PsiType returnType = method.getReturnType(); - if (returnType instanceof PsiClassType) { - PsiClass cls = ((PsiClassType)returnType).resolve(); - if (cls != null && "android.content.res.TypedArray".equals(cls.getQualifiedName())) { - checkRecycled(context, node, "android.content.res.TypedArray", RECYCLE); - } - } - } else if (ACQUIRE_CPC.equals(name) && InheritanceUtil.isInheritor(containingClass, - false, CONTENT_RESOLVER_CLS)) { - checkRecycled(context, node, CONTENT_PROVIDER_CLIENT_CLS, RELEASE); - } else if ((QUERY.equals(name) - || RAW_QUERY.equals(name) - || QUERY_WITH_FACTORY.equals(name) - || RAW_QUERY_WITH_FACTORY.equals(name)) - && (InheritanceUtil.isInheritor(containingClass, false, SQLITE_DATABASE_CLS) || - InheritanceUtil.isInheritor(containingClass, false, CONTENT_RESOLVER_CLS) || - InheritanceUtil.isInheritor(containingClass, false, CLASS_CONTENTPROVIDER) || - InheritanceUtil.isInheritor(containingClass, false, CONTENT_PROVIDER_CLIENT_CLS))) { - // Other potential cursors-returning methods that should be tracked: - // android.app.DownloadManager#query - // android.content.ContentProviderClient#query - // android.content.ContentResolver#query - // android.database.sqlite.SQLiteQueryBuilder#query - // android.provider.Browser#getAllBookmarks - // android.provider.Browser#getAllVisitedUrls - // android.provider.DocumentsProvider#queryChildDocuments - // android.provider.DocumentsProvider#qqueryDocument - // android.provider.DocumentsProvider#queryRecentDocuments - // android.provider.DocumentsProvider#queryRoots - // android.provider.DocumentsProvider#querySearchDocuments - // android.provider.MediaStore$Images$Media#query - // android.widget.FilterQueryProvider#runQuery - checkClosedOrUsed(context, node, CURSOR_CLS); - } - } - - private static void reportRecycleResource(JavaContext context, String recycleType, String recycleName, @NonNull UCallExpression node) { - String className = recycleType.substring(recycleType.lastIndexOf('.') + 1); - String message; - if (RECYCLE.equals(recycleName)) { - message = String.format( - "This `%1$s` should be recycled after use with `#recycle()`", className); - } else { - message = String.format( - "This `%1$s` should be freed up after use with `#%2$s()`", className, - recycleName); - } - - UElement locationNode = node.getMethodIdentifier(); - if (locationNode == null) { - locationNode = node; - } - Location location = context.getUastLocation(locationNode); - context.report(RECYCLE_RESOURCE, node, location, message); - } - - private static void checkClosedOrUsed(@NonNull final JavaContext context, @NonNull UCallExpression node, - @NonNull final String recycleType) { - - if (isCleanedUpInChain(node, new Predicate<UCallExpression>() { - @Override - public boolean apply(@org.jetbrains.annotations.Nullable UCallExpression call) { - return isCloseMethodCall(call) || isUseMethodCall(call); - } - })) { - return; - } - - PsiVariable boundVariable = getVariableElement(node); - if (boundVariable == null) { - reportRecycleResource(context, recycleType, CLOSE, node); - return; - } - - UMethod method = getParentOfType(node, UMethod.class, true); - if (method == null) { - return; - } - - FinishVisitor visitor = new FinishVisitor(context, boundVariable) { - @Override - protected boolean isCleanupCall(@NonNull UCallExpression call) { - if (isUseMethodCall(call) || isCloseMethodCall(call)) { - UExpression receiver = call.getReceiver(); - if (receiver instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) receiver).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - return true; - } - } - } - return false; - } - }; - - method.accept(visitor); - if (visitor.isCleanedUp() || visitor.variableEscapes()) { - return; - } - - reportRecycleResource(context, recycleType, CLOSE, node); - } - - private static boolean isCloseMethodCall(UCallExpression call) { - return isValidCleanupMethodCall(call, CLOSE, CLOSABLE); - } - - private static boolean isUseMethodCall(UCallExpression call) { - return USE.equals(call.getMethodName()); - } - - private static boolean isValidCleanupMethodCall(@NonNull UCallExpression call, @NonNull String methodName, @NonNull String className) { - if (!methodName.equals(call.getMethodName())) { - return false; - } - - PsiMethod method = call.resolve(); - if (method == null) { - return false; - } - - return InheritanceUtil.isInheritor(method.getContainingClass(), false, className); - } - - private static boolean isCleanedUpInChain(UExpression expression, Predicate<UCallExpression> isCleanupCallPredicate) { - List<UExpression> chain = getQualifiedChain(getOutermostQualified(expression)); - boolean skip = true; - for (UExpression e : chain) { - if (e == expression) { - skip = false; - continue; - } - - if (skip) { - continue; - } - - if (e instanceof UCallExpression) { - UCallExpression call = (UCallExpression) e; - if (isCleanupCallPredicate.apply(call)) { - return true; - } - } - } - return false; - } - - private static void checkRecycled(@NonNull final JavaContext context, @NonNull UCallExpression node, - @NonNull final String recycleType, @NonNull final String recycleName) { - - if (isCleanedUpInChain(node, new Predicate<UCallExpression>() { - @Override - public boolean apply(@org.jetbrains.annotations.Nullable UCallExpression call) { - return isValidCleanupMethodCall(call, recycleName, recycleType); - } - })) { - return; - } - - PsiVariable boundVariable = getVariableElement(node); - if (boundVariable == null) { - reportRecycleResource(context, recycleType, recycleName, node); - return; - } - - UMethod method = getParentOfType(node, UMethod.class, true); - if (method == null) { - return; - } - - FinishVisitor visitor = new FinishVisitor(context, boundVariable) { - @Override - protected boolean isCleanupCall(@NonNull UCallExpression call) { - if (isValidCleanupMethodCall(call, recycleName, recycleType)) { - // Yes, called the right recycle() method; now make sure - // we're calling it on the right variable - UExpression operand = call.getReceiver(); - if (operand instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) operand).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - return true; - } - } - } - return false; - } - }; - - method.accept(visitor); - if (visitor.isCleanedUp() || visitor.variableEscapes()) { - return; - } - - reportRecycleResource(context, recycleType, recycleName, node); - } - - private static void checkTransactionCommits(@NonNull JavaContext context, - @NonNull UCallExpression node, @NonNull PsiMethod calledMethod) { - if (isBeginTransaction(context, calledMethod)) { - if (isCommittedInChainedCalls(context, node)) { - return; - } - - PsiVariable boundVariable = getVariableElement(node, true); - if (boundVariable != null) { - UMethod method = getParentOfType(node, UMethod.class, true); - if (method == null) { - return; - } - - FinishVisitor commitVisitor = new FinishVisitor(context, boundVariable) { - @Override - protected boolean isCleanupCall(@NonNull UCallExpression call) { - if (isTransactionCommitMethodCall(mContext, call)) { - List<UExpression> chain = getQualifiedChain(getOutermostQualified(call)); - if (chain.isEmpty()) { - return false; - } - - UExpression operand = chain.get(0); - if (operand != null) { - PsiElement resolved = UastUtils.tryResolve(operand); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - return true; - } else if (resolved instanceof PsiMethod - && operand instanceof UCallExpression - && isCommittedInChainedCalls(mContext, - (UCallExpression) operand)) { - // Check that the target of the committed chains is the - // right variable! - while (operand instanceof UCallExpression) { - operand = ((UCallExpression) operand).getReceiver(); - } - if (operand instanceof UReferenceExpression) { - resolved = ((UReferenceExpression) operand).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - return true; - } - } - } - } - } else if (isShowFragmentMethodCall(mContext, call)) { - List<UExpression> arguments = call.getValueArguments(); - if (arguments.size() == 2) { - UExpression first = arguments.get(0); - PsiElement resolved = UastUtils.tryResolve(first); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - return true; - } - } - } - return false; - } - }; - - method.accept(commitVisitor); - if (commitVisitor.isCleanedUp() || commitVisitor.variableEscapes()) { - return; - } - } - - String message = "This transaction should be completed with a `commit()` call"; - context.report(COMMIT_FRAGMENT, node, context.getUastNameLocation(node), message); - } - } - - private static boolean isCommittedInChainedCalls(@NonNull final JavaContext context, - @NonNull UCallExpression node) { - // Look for chained calls since the FragmentManager methods all return "this" - // to allow constructor chaining, e.g. - // getFragmentManager().beginTransaction().addToBackStack("test") - // .disallowAddToBackStack().hide(mFragment2).setBreadCrumbShortTitle("test") - // .show(mFragment2).setCustomAnimations(0, 0).commit(); - - return isCleanedUpInChain(node, new Predicate<UCallExpression>() { - @Override - public boolean apply(@org.jetbrains.annotations.Nullable UCallExpression call) { - return isTransactionCommitMethodCall(context, call) || isShowFragmentMethodCall(context, call); - } - }); - } - - private static boolean isTransactionCommitMethodCall(@NonNull JavaContext context, - @NonNull UCallExpression call) { - - String methodName = call.getMethodName(); - return (COMMIT.equals(methodName) || COMMIT_ALLOWING_LOSS.equals(methodName)) && - isMethodOnFragmentClass(context, call, - FRAGMENT_TRANSACTION_CLS, - FRAGMENT_TRANSACTION_V4_CLS, - true); - } - - private static boolean isShowFragmentMethodCall(@NonNull JavaContext context, - @NonNull UCallExpression call) { - String methodName = call.getMethodName(); - return SHOW.equals(methodName) - && isMethodOnFragmentClass(context, call, - DIALOG_FRAGMENT, DIALOG_V4_FRAGMENT, true); - } - - private static boolean isMethodOnFragmentClass( - @NonNull JavaContext context, - @NonNull UCallExpression call, - @NonNull String fragmentClass, - @NonNull String v4FragmentClass, - boolean returnForUnresolved) { - PsiMethod method = call.resolve(); - if (method != null) { - PsiClass containingClass = method.getContainingClass(); - JavaEvaluator evaluator = context.getEvaluator(); - return InheritanceUtil.isInheritor(containingClass, false, fragmentClass) || - InheritanceUtil.isInheritor(containingClass, false, v4FragmentClass); - } else { - // If we *can't* resolve the method call, caller can decide - // whether to consider the method called or not - return returnForUnresolved; - } - } - - private static void checkEditorApplied(@NonNull JavaContext context, - @NonNull UCallExpression node, @NonNull PsiMethod calledMethod) { - if (isSharedEditorCreation(context, calledMethod)) { - PsiVariable boundVariable = getVariableElement(node, true); - if (isEditorCommittedInChainedCalls(context, node)) { - return; - } - - if (boundVariable != null) { - UMethod method = getParentOfType(node, UMethod.class, true); - if (method == null) { - return; - } - - FinishVisitor commitVisitor = new FinishVisitor(context, boundVariable) { - @Override - protected boolean isCleanupCall(@NonNull UCallExpression call) { - if (isEditorApplyMethodCall(mContext, call) - || isEditorCommitMethodCall(mContext, call)) { - List<UExpression> chain = getQualifiedChain(getOutermostQualified(call)); - if (chain.isEmpty()) { - return false; - } - - UExpression operand = chain.get(0); - if (operand != null) { - PsiElement resolved = UastUtils.tryResolve(operand); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - return true; - } else if (resolved instanceof PsiMethod - && operand instanceof UCallExpression - && isEditorCommittedInChainedCalls(mContext, - (UCallExpression) operand)) { - // Check that the target of the committed chains is the - // right variable! - while (operand instanceof UCallExpression) { - operand = ((UCallExpression)operand).getReceiver(); - } - if (operand instanceof UReferenceExpression) { - resolved = ((UReferenceExpression) operand).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - return true; - } - } - } - } - } - return false; - } - }; - - method.accept(commitVisitor); - if (commitVisitor.isCleanedUp() || commitVisitor.variableEscapes()) { - return; - } - } else if (UastUtils.getParentOfType(node, UReturnExpression.class) != null) { - // Allocation is in a return statement - return; - } - - String message = "`SharedPreferences.edit()` without a corresponding `commit()` or " - + "`apply()` call"; - context.report(SHARED_PREF, node, context.getUastLocation(node), message); - } - } - - private static boolean isSharedEditorCreation(@NonNull JavaContext context, - @NonNull PsiMethod method) { - String methodName = method.getName(); - if (EDIT.equals(methodName)) { - PsiClass containingClass = method.getContainingClass(); - JavaEvaluator evaluator = context.getEvaluator(); - return InheritanceUtil.isInheritor( - containingClass, false, ANDROID_CONTENT_SHARED_PREFERENCES); - } - - return false; - } - - private static boolean isEditorCommittedInChainedCalls(@NonNull final JavaContext context, - @NonNull UCallExpression node) { - return isCleanedUpInChain(node, new Predicate<UCallExpression>() { - @Override - public boolean apply(@org.jetbrains.annotations.Nullable UCallExpression call) { - return isEditorCommitMethodCall(context, call) || isEditorApplyMethodCall(context, call); - } - }); - } - - private static boolean isEditorCommitMethodCall(@NonNull JavaContext context, - @NonNull UCallExpression call) { - String methodName = call.getMethodName(); - if (COMMIT.equals(methodName)) { - PsiMethod method = call.resolve(); - if (method != null) { - PsiClass containingClass = method.getContainingClass(); - JavaEvaluator evaluator = context.getEvaluator(); - if (InheritanceUtil.isInheritor(containingClass, false, - ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR)) { - suggestApplyIfApplicable(context, call); - return true; - } - } - } - - return false; - } - - private static boolean isEditorApplyMethodCall(@NonNull JavaContext context, - @NonNull UCallExpression call) { - String methodName = call.getMethodName(); - if (APPLY.equals(methodName)) { - PsiMethod method = call.resolve(); - if (method != null) { - PsiClass containingClass = method.getContainingClass(); - JavaEvaluator evaluator = context.getEvaluator(); - return InheritanceUtil.isInheritor(containingClass, false, - ANDROID_CONTENT_SHARED_PREFERENCES_EDITOR); - } - } - - return false; - } - - private static void suggestApplyIfApplicable(@NonNull JavaContext context, - @NonNull UCallExpression node) { - if (context.getProject().getMinSdkVersion().getApiLevel() >= 9) { - // See if the return value is read: can only replace commit with - // apply if the return value is not considered - - UElement qualifiedNode = node; - UElement parent = skipParentheses(node.getUastParent()); - while (parent instanceof UReferenceExpression) { - qualifiedNode = parent; - parent = skipParentheses(parent.getUastParent()); - } - boolean returnValueIgnored = true; - - if (parent instanceof UCallExpression - || parent instanceof UVariable - || parent instanceof UBinaryExpression - || parent instanceof UUnaryExpression - || parent instanceof UReturnExpression) { - returnValueIgnored = false; - } else if (parent instanceof UIfExpression) { - UExpression condition = ((UIfExpression) parent).getCondition(); - returnValueIgnored = !condition.equals(qualifiedNode); - } else if (parent instanceof UWhileExpression) { - UExpression condition = ((UWhileExpression) parent).getCondition(); - returnValueIgnored = !condition.equals(qualifiedNode); - } else if (parent instanceof UDoWhileExpression) { - UExpression condition = ((UDoWhileExpression) parent).getCondition(); - returnValueIgnored = !condition.equals(qualifiedNode); - } - - if (returnValueIgnored) { - String message = "Consider using `apply()` instead; `commit` writes " - + "its data to persistent storage immediately, whereas " - + "`apply` will handle it in the background"; - context.report(SHARED_PREF, node, context.getUastLocation(node), message); - } - } - } - - /** Returns the variable the expression is assigned to, if any */ - @Nullable - public static PsiVariable getVariableElement(@NonNull UCallExpression rhs) { - return getVariableElement(rhs, false); - } - - @Nullable - public static PsiVariable getVariableElement(@NonNull UCallExpression rhs, - boolean allowChainedCalls) { - UElement parent = skipParentheses( - UastUtils.getQualifiedParentOrThis(rhs).getUastParent()); - - // Handle some types of chained calls; e.g. you might have - // var = prefs.edit().put(key,value) - // and here we want to skip past the put call - if (allowChainedCalls) { - while (true) { - if ((parent instanceof UQualifiedReferenceExpression)) { - UElement parentParent = skipParentheses(parent.getUastParent()); - if ((parentParent instanceof UQualifiedReferenceExpression)) { - parent = skipParentheses(parentParent.getUastParent()); - } else if (parentParent instanceof UVariable - || parentParent instanceof UBinaryExpression) { - parent = parentParent; - break; - } else { - break; - } - } else { - break; - } - } - } - - if (UastExpressionUtils.isAssignment(parent)) { - UBinaryExpression assignment = (UBinaryExpression) parent; - assert assignment != null; - UExpression lhs = assignment.getLeftOperand(); - if (lhs instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) lhs).resolve(); - if (resolved instanceof PsiVariable && !(resolved instanceof PsiField)) { - // e.g. local variable, parameter - but not a field - return ((PsiVariable) resolved); - } - } - } else if (parent instanceof UVariable && !(parent instanceof UField)) { - return ((UVariable) parent).getPsi(); - } - - return null; - } - - private static boolean isBeginTransaction(@NonNull JavaContext context, @NonNull PsiMethod method) { - String methodName = method.getName(); - if (BEGIN_TRANSACTION.equals(methodName)) { - PsiClass containingClass = method.getContainingClass(); - JavaEvaluator evaluator = context.getEvaluator(); - if (InheritanceUtil.isInheritor(containingClass, false, FRAGMENT_MANAGER_CLS) - || InheritanceUtil.isInheritor(containingClass, false, FRAGMENT_MANAGER_V4_CLS)) { - return true; - } - } - - return false; - } - - /** - * Visitor which checks whether an operation is "finished"; in the case - * of a FragmentTransaction we're looking for a "commit" call; in the - * case of a TypedArray we're looking for a "recycle", call, in the - * case of a database cursor we're looking for a "close" call, etc. - */ - private abstract static class FinishVisitor extends AbstractUastVisitor { - protected final JavaContext mContext; - protected final List<PsiVariable> mVariables; - private final PsiVariable mOriginalVariableNode; - - private boolean mContainsCleanup; - private boolean mEscapes; - - public FinishVisitor(JavaContext context, @NonNull PsiVariable variableNode) { - mContext = context; - mOriginalVariableNode = variableNode; - mVariables = Lists.newArrayList(variableNode); - } - - public boolean isCleanedUp() { - return mContainsCleanup; - } - - public boolean variableEscapes() { - return mEscapes; - } - - @Override - public boolean visitElement(UElement node) { - return mContainsCleanup || super.visitElement(node); - } - - protected abstract boolean isCleanupCall(@NonNull UCallExpression call); - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (node.getKind() == UastCallKind.METHOD_CALL) { - visitMethodCallExpression(node); - } - return super.visitCallExpression(node); - } - - private void visitMethodCallExpression(UCallExpression call) { - if (mContainsCleanup) { - return; - } - - // Look for escapes - if (!mEscapes) { - for (UExpression expression : call.getValueArguments()) { - if (expression instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) expression).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - boolean wasEscaped = mEscapes; - mEscapes = true; - - // Special case: MotionEvent.obtain(MotionEvent): passing in an - // event here does not recycle the event, and we also know it - // doesn't escape - if (OBTAIN.equals(call.getMethodName())) { - PsiMethod method = call.resolve(); - if (JavaEvaluator.isMemberInClass(method, MOTION_EVENT_CLS)) { - mEscapes = wasEscaped; - } - } - } - } - } - } - - if (isCleanupCall(call)) { - mContainsCleanup = true; - } - } - - @Override - public boolean visitVariable(UVariable variable) { - if (variable instanceof ULocalVariable) { - UExpression initializer = variable.getUastInitializer(); - if (initializer instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) initializer).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - mVariables.add(variable.getPsi()); - } - } - } - - return super.visitVariable(variable); - } - - @Override - public boolean visitBinaryExpression(UBinaryExpression expression) { - if (!UastExpressionUtils.isAssignment(expression)) { - return super.visitBinaryExpression(expression); - } - - // TEMPORARILY DISABLED; see testDatabaseCursorReassignment - // This can result in some false positives right now. Play it - // safe instead. - boolean clearLhs = false; - - UExpression rhs = expression.getRightOperand(); - if (rhs instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) rhs).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - clearLhs = false; - PsiElement lhs = UastUtils.tryResolve(expression.getLeftOperand()); - if (lhs instanceof PsiLocalVariable) { - mVariables.add(((PsiLocalVariable) lhs)); - } else if (lhs instanceof PsiField) { - mEscapes = true; - } - } - } - - //noinspection ConstantConditions - if (clearLhs) { - // If we reassign one of the variables, clear it out - PsiElement lhs = UastUtils.tryResolve(expression.getLeftOperand()); - //noinspection SuspiciousMethodCalls - if (lhs != null && !lhs.equals(mOriginalVariableNode) - && mVariables.contains(lhs)) { - //noinspection SuspiciousMethodCalls - mVariables.remove(lhs); - } - } - - return super.visitBinaryExpression(expression); - } - - @Override - public boolean visitReturnExpression(UReturnExpression node) { - UExpression returnValue = node.getReturnExpression(); - if (returnValue instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) returnValue).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - mEscapes = true; - } - } - - return super.visitReturnExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CommentDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CommentDetector.java deleted file mode 100644 index 0ba5f18..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CommentDetector.java +++ /dev/null
@@ -1,199 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UComment; -import org.jetbrains.uast.UFile; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for issues in Java comments - */ -public class CommentDetector extends Detector implements Detector.UastScanner { - private static final String STOPSHIP_COMMENT = "STOPSHIP"; //$NON-NLS-1$ - - private static final Implementation IMPLEMENTATION = new Implementation( - CommentDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Looks for hidden code */ - public static final Issue EASTER_EGG = Issue.create( - "EasterEgg", //$NON-NLS-1$ - "Code contains easter egg", - "An \"easter egg\" is code deliberately hidden in the code, both from potential " + - "users and even from other developers. This lint check looks for code which " + - "looks like it may be hidden from sight.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION) - .setEnabledByDefault(false); - - /** Looks for special comment markers intended to stop shipping the code */ - public static final Issue STOP_SHIP = Issue.create( - "StopShip", //$NON-NLS-1$ - "Code contains `STOPSHIP` marker", - - "Using the comment `// STOPSHIP` can be used to flag code that is incomplete but " + - "checked in. This comment marker can be used to indicate that the code should not " + - "be shipped until the issue is addressed, and lint will look for these.", - Category.CORRECTNESS, - 10, - Severity.WARNING, - IMPLEMENTATION) - .setEnabledByDefault(false); - - private static final String ESCAPE_STRING = "\\u002a\\u002f"; //$NON-NLS-1$ - - /** The current AST only passes comment nodes for Javadoc so I need to do manual token scanning - instead */ - private static final boolean USE_AST = false; - - - /** Constructs a new {@link CommentDetector} check */ - public CommentDetector() { - } - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - if (USE_AST) { - return Collections.<Class<? extends UElement>>singletonList( - UFile.class); - } else { - return null; - } - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - if (USE_AST) { - return new CommentChecker(context); - } else { - String source = context.getContents(); - if (source == null) { - return null; - } - // Process the Java source such that we pass tokens to it - - for (int i = 0, n = source.length() - 1; i < n; i++) { - char c = source.charAt(i); - if (c == '\\') { - i += 1; - } else if (c == '/') { - char next = source.charAt(i + 1); - if (next == '/') { - // Line comment - int start = i + 2; - int end = source.indexOf('\n', start); - if (end == -1) { - end = n; - } - checkComment(context, null, source, 0, start, end); - } else if (next == '*') { - // Block comment - int start = i + 2; - int end = source.indexOf("*/", start); - if (end == -1) { - end = n; - } - checkComment(context, null, source, 0, start, end); - } - } - } - return null; - } - } - - private static class CommentChecker extends AbstractUastVisitor { - private final JavaContext mContext; - - public CommentChecker(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitFile(UFile node) { - for (UComment comment : node.getAllCommentsInFile()) { - String contents = comment.getText(); - checkComment(mContext, comment, contents, - comment.getPsi().getTextRange().getStartOffset(), 0, contents.length()); - } - return super.visitFile(node); - } - } - - private static void checkComment( - @NonNull JavaContext context, - @Nullable UComment node, - @NonNull String source, - int offset, - int start, - int end) { - char prev = 0; - char c; - for (int i = start; i < end - 2; i++, prev = c) { - c = source.charAt(i); - if (prev == '\\') { - if (c == 'u' || c == 'U') { - if (source.regionMatches(true, i - 1, ESCAPE_STRING, - 0, ESCAPE_STRING.length())) { - Location location = Location.create(context.file, source, - offset + i - 1, offset + i - 1 + ESCAPE_STRING.length()); - context.report(EASTER_EGG, node, location, - "Code might be hidden here; found unicode escape sequence " + - "which is interpreted as comment end, compiled code follows"); - } - } else { - i++; - } - } else if (prev == 'S' && c == 'T' && - source.regionMatches(i - 1, STOPSHIP_COMMENT, 0, STOPSHIP_COMMENT.length())) { - - // TODO: Only flag this issue in release mode?? - Location location; - if (node != null) { - location = context.getUastLocation(node); - } else { - location = Location.create(context.file, source, - offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length()); - } - - context.report(STOP_SHIP, node, location, - "`STOPSHIP` comment found; points to code which must be fixed prior " + - "to release"); - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ControlFlowGraph.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ControlFlowGraph.java deleted file mode 100644 index e524f52..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ControlFlowGraph.java +++ /dev/null
@@ -1,537 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import org.jetbrains.org.objectweb.asm.Opcodes; -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode; -import org.jetbrains.org.objectweb.asm.tree.FrameNode; -import org.jetbrains.org.objectweb.asm.tree.InsnList; -import org.jetbrains.org.objectweb.asm.tree.IntInsnNode; -import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode; -import org.jetbrains.org.objectweb.asm.tree.LabelNode; -import org.jetbrains.org.objectweb.asm.tree.LdcInsnNode; -import org.jetbrains.org.objectweb.asm.tree.LineNumberNode; -import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; -import org.jetbrains.org.objectweb.asm.tree.MethodNode; -import org.jetbrains.org.objectweb.asm.tree.TryCatchBlockNode; -import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode; -import org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer; -import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException; -import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -//import org.jetbrains.org.objectweb.asm.util.Printer; - -/** - * A {@linkplain ControlFlowGraph} is a graph containing a node for each - * instruction in a method, and an edge for each possible control flow; usually - * just "next" for the instruction following the current instruction, but in the - * case of a branch such as an "if", multiple edges to each successive location, - * or with a "goto", a single edge to the jumped-to instruction. - * <p> - * It also adds edges for abnormal control flow, such as the possibility of a - * method call throwing a runtime exception. - */ -public class ControlFlowGraph { - /** Map from instructions to nodes */ - private Map<AbstractInsnNode, Node> mNodeMap; - private MethodNode mMethod; - - /** - * Creates a new {@link ControlFlowGraph} and populates it with the flow - * control for the given method. If the optional {@code initial} parameter is - * provided with an existing graph, then the graph is simply populated, not - * created. This allows subclassing of the graph instance, if necessary. - * - * @param initial usually null, but can point to an existing instance of a - * {@link ControlFlowGraph} in which that graph is reused (but - * populated with new edges) - * @param classNode the class containing the method to be analyzed - * @param method the method to be analyzed - * @return a {@link ControlFlowGraph} with nodes for the control flow in the - * given method - * @throws AnalyzerException if the underlying bytecode library is unable to - * analyze the method bytecode - */ - @NonNull - public static ControlFlowGraph create( - @Nullable ControlFlowGraph initial, - @NonNull ClassNode classNode, - @NonNull MethodNode method) throws AnalyzerException { - final ControlFlowGraph graph = initial != null ? initial : new ControlFlowGraph(); - final InsnList instructions = method.instructions; - graph.mNodeMap = Maps.newHashMapWithExpectedSize(instructions.size()); - graph.mMethod = method; - - // Create a flow control graph using ASM5's analyzer. According to the ASM 4 guide - // (download.forge.objectweb.org/asm/asm4-guide.pdf) there are faster ways to construct - // it, but those require a lot more code. - Analyzer analyzer = new Analyzer(new BasicInterpreter()) { - @Override - protected void newControlFlowEdge(int insn, int successor) { - // Update the information as of whether the this object has been - // initialized at the given instruction. - AbstractInsnNode from = instructions.get(insn); - AbstractInsnNode to = instructions.get(successor); - graph.add(from, to); - } - - @Override - protected boolean newControlFlowExceptionEdge(int insn, TryCatchBlockNode tcb) { - AbstractInsnNode from = instructions.get(insn); - graph.exception(from, tcb); - return super.newControlFlowExceptionEdge(insn, tcb); - } - - @Override - protected boolean newControlFlowExceptionEdge(int insn, int successor) { - AbstractInsnNode from = instructions.get(insn); - AbstractInsnNode to = instructions.get(successor); - graph.exception(from, to); - return super.newControlFlowExceptionEdge(insn, successor); - } - }; - - analyzer.analyze(classNode.name, method); - return graph; - } - - /** - * Checks whether there is a path from the given source node to the given - * destination node - */ - @SuppressWarnings("MethodMayBeStatic") - private boolean isConnected(@NonNull Node from, - @NonNull Node to, @NonNull Set<Node> seen) { - if (from == to) { - return true; - } else if (seen.contains(from)) { - return false; - } - seen.add(from); - - List<Node> successors = from.successors; - List<Node> exceptions = from.exceptions; - if (exceptions != null) { - for (Node successor : exceptions) { - if (isConnected(successor, to, seen)) { - return true; - } - } - } - - if (successors != null) { - for (Node successor : successors) { - if (isConnected(successor, to, seen)) { - return true; - } - } - } - - return false; - } - - /** - * Checks whether there is a path from the given source node to the given - * destination node - */ - public boolean isConnected(@NonNull Node from, @NonNull Node to) { - return isConnected(from, to, Sets.<Node>newIdentityHashSet()); - } - - /** - * Checks whether there is a path from the given instruction to the given - * instruction node - */ - public boolean isConnected(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) { - return isConnected(getNode(from), getNode(to)); - } - - /** A {@link Node} is a node in the control flow graph for a method, pointing to - * the instruction and its possible successors */ - public static class Node { - /** The instruction */ - public final AbstractInsnNode instruction; - /** Any normal successors (e.g. following instruction, or goto or conditional flow) */ - public final List<Node> successors = new ArrayList<Node>(2); - /** Any abnormal successors (e.g. the handler to go to following an exception) */ - public final List<Node> exceptions = new ArrayList<Node>(1); - - /** A tag for use during depth-first-search iteration of the graph etc */ - public int visit; - - /** - * Constructs a new control graph node - * - * @param instruction the instruction to associate with this node - */ - public Node(@NonNull AbstractInsnNode instruction) { - this.instruction = instruction; - } - - void addSuccessor(@NonNull Node node) { - if (!successors.contains(node)) { - successors.add(node); - } - } - - void addExceptionPath(@NonNull Node node) { - if (!exceptions.contains(node)) { - exceptions.add(node); - } - } - - /** - * Represents this instruction as a string, for debugging purposes - * - * @param includeAdjacent whether it should include a display of - * adjacent nodes as well - * @return a string representation - */ - @NonNull - public String toString(boolean includeAdjacent) { - StringBuilder sb = new StringBuilder(100); - - sb.append(getId(instruction)); - sb.append(':'); - - if (instruction instanceof LabelNode) { - //LabelNode l = (LabelNode) instruction; - //sb.append('L' + l.getLabel().getOffset() + ":"); - //sb.append('L' + l.getLabel().info + ":"); - sb.append("LABEL"); - } else if (instruction instanceof LineNumberNode) { - sb.append("LINENUMBER ").append(((LineNumberNode)instruction).line); - } else if (instruction instanceof FrameNode) { - sb.append("FRAME"); - } else { - int opcode = instruction.getOpcode(); - String opcodeName = getOpcodeName(opcode); - sb.append(opcodeName); - if (instruction.getType() == AbstractInsnNode.METHOD_INSN) { - sb.append('(').append(((MethodInsnNode)instruction).name).append(')'); - } - } - - if (includeAdjacent) { - if (successors != null && !successors.isEmpty()) { - sb.append(" Next:"); - for (Node successor : successors) { - sb.append(' '); - sb.append(successor.toString(false)); - } - } - - if (exceptions != null && !exceptions.isEmpty()) { - sb.append(" Exceptions:"); - for (Node exception : exceptions) { - sb.append(' '); - sb.append(exception.toString(false)); - } - } - sb.append('\n'); - } - - return sb.toString(); - } - } - - /** Adds an exception flow to this graph */ - protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) { - getNode(from).addSuccessor(getNode(to)); - } - - /** Adds an exception flow to this graph */ - protected void exception(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) { - // For now, these edges appear useless; we also get more specific - // information via the TryCatchBlockNode which we use instead. - //getNode(from).addExceptionPath(getNode(to)); - } - - /** Adds an exception try block node to this graph */ - protected void exception(@NonNull AbstractInsnNode from, @NonNull TryCatchBlockNode tcb) { - // Add tcb's to all instructions in the range - LabelNode start = tcb.start; - LabelNode end = tcb.end; // exclusive - - // Add exception edges for all method calls in the range - AbstractInsnNode curr = start; - Node handlerNode = getNode(tcb.handler); - while (curr != end && curr != null) { - // A method can throw can exception, or a throw instruction directly - if (curr.getType() == AbstractInsnNode.METHOD_INSN - || (curr.getType() == AbstractInsnNode.INSN - && curr.getOpcode() == Opcodes.ATHROW)) { - // Method call; add exception edge to handler - if (tcb.type == null) { - // finally block: not an exception path - getNode(curr).addSuccessor(handlerNode); - } - getNode(curr).addExceptionPath(handlerNode); - } - curr = curr.getNext(); - } - } - - /** - * Looks up (and if necessary) creates a graph node for the given instruction - * - * @param instruction the instruction - * @return the control flow graph node corresponding to the given - * instruction - */ - @NonNull - public Node getNode(@NonNull AbstractInsnNode instruction) { - Node node = mNodeMap.get(instruction); - if (node == null) { - node = new Node(instruction); - mNodeMap.put(instruction, node); - } - - return node; - } - - /** - * Creates a human readable version of the graph - * - * @param start the starting instruction, or null if not known or to use the - * first instruction - * @return a string version of the graph - */ - @NonNull - public String toString(@Nullable Node start) { - StringBuilder sb = new StringBuilder(400); - - AbstractInsnNode curr; - if (start != null) { - curr = start.instruction; - } else { - if (mNodeMap.isEmpty()) { - return "<empty>"; - } else { - curr = mNodeMap.keySet().iterator().next(); - while (curr.getPrevious() != null) { - curr = curr.getPrevious(); - } - } - } - - while (curr != null) { - Node node = mNodeMap.get(curr); - if (node != null) { - sb.append(node.toString(true)); - } - curr = curr.getNext(); - } - - return sb.toString(); - } - - @Override - public String toString() { - return toString(null); - } - - // ---- For debugging only ---- - - private static Map<Object, String> sIds = null; - private static int sNextId = 1; - private static String getId(Object object) { - if (sIds == null) { - sIds = Maps.newHashMap(); - } - String id = sIds.get(object); - if (id == null) { - id = Integer.toString(sNextId++); - sIds.put(object, id); - } - return id; - } - - /** - * Generates dot output of the graph. This can be used with - * graphwiz to visualize the graph. For example, if you - * save the output as graph1.gv you can run - * <pre> - * $ dot -Tps graph1.gv -o graph1.ps - * </pre> - * to generate a postscript file, which you can then view - * with "gv graph1.ps". - * - * (There are also some online web sites where you can - * paste in dot graphs and see the visualization right - * there in the browser.) - * - * @return a dot description of this control flow graph, - * useful for debugging - */ - @SuppressWarnings("unused") - public String toDot(@Nullable Set<Node> highlight) { - StringBuilder sb = new StringBuilder(); - sb.append("digraph G {\n"); - - - AbstractInsnNode instruction = mMethod.instructions.getFirst(); - - // Special start node - sb.append(" start -> ").append(getId(mNodeMap.get(instruction))).append(";\n"); - sb.append(" start [shape=plaintext];\n"); - - while (instruction != null) { - Node node = mNodeMap.get(instruction); - if (node != null) { - if (node.successors != null) { - for (Node to : node.successors) { - sb.append(" ").append(getId(node)).append(" -> ").append(getId(to)); - if (node.instruction instanceof JumpInsnNode) { - sb.append(" [label=\""); - if (((JumpInsnNode)node.instruction).label == to.instruction) { - sb.append("yes"); - } else { - sb.append("no"); - } - sb.append("\"]"); - } - sb.append(";\n"); - } - } - if (node.exceptions != null) { - for (Node to : node.exceptions) { - sb.append(getId(node)).append(" -> ").append(getId(to)); - sb.append(" [label=\"exception\"];\n"); - } - } - } - - instruction = instruction.getNext(); - } - - - // Labels - sb.append("\n"); - for (Node node : mNodeMap.values()) { - instruction = node.instruction; - sb.append(" ").append(getId(node)).append(" "); - sb.append("[label=\"").append(dotDescribe(node)).append("\""); - if (highlight != null && highlight.contains(node)) { - sb.append(",shape=box,style=filled"); - } else if (instruction instanceof LineNumberNode || - instruction instanceof LabelNode || - instruction instanceof FrameNode) { - sb.append(",shape=oval,style=dotted"); - } else { - sb.append(",shape=box"); - } - sb.append("];\n"); - } - - sb.append("}"); - return sb.toString(); - } - - private static String dotDescribe(Node node) { - AbstractInsnNode instruction = node.instruction; - if (instruction instanceof LabelNode) { - return "Label"; - } else if (instruction instanceof LineNumberNode) { - LineNumberNode lineNode = (LineNumberNode)instruction; - return "Line " + lineNode.line; - } else if (instruction instanceof FrameNode) { - return "Stack Frame"; - } else if (instruction instanceof MethodInsnNode) { - MethodInsnNode method = (MethodInsnNode)instruction; - String cls = method.owner.substring(method.owner.lastIndexOf('/') + 1); - cls = cls.replace('$','.'); - return "Call " + cls + "#" + method.name; - } else if (instruction instanceof FieldInsnNode) { - FieldInsnNode field = (FieldInsnNode) instruction; - String cls = field.owner.substring(field.owner.lastIndexOf('/') + 1); - cls = cls.replace('$','.'); - return "Field " + cls + "#" + field.name; - } else if (instruction instanceof TypeInsnNode && instruction.getOpcode() == Opcodes.NEW) { - return "New " + ((TypeInsnNode)instruction).desc; - } - StringBuilder sb = new StringBuilder(); - String opcodeName = getOpcodeName(instruction.getOpcode()); - sb.append(opcodeName); - - if (instruction instanceof IntInsnNode) { - IntInsnNode in = (IntInsnNode) instruction; - sb.append(" ").append(Integer.toString(in.operand)); - } else if (instruction instanceof LdcInsnNode) { - LdcInsnNode ldc = (LdcInsnNode) instruction; - sb.append(" "); - if (ldc.cst instanceof String) { - sb.append("\\\""); - } - sb.append(ldc.cst); - if (ldc.cst instanceof String) { - sb.append("\\\""); - } - } - return sb.toString(); - } - - private static String getOpcodeName(int opcode) { - if (sOpcodeNames == null) { - sOpcodeNames = new String[255]; - try { - Field[] fields = Opcodes.class.getDeclaredFields(); - for (Field field : fields) { - if (field.getType() == int.class) { - String name = field.getName(); - if (name.startsWith("ASM") || name.startsWith("V1_") || - name.startsWith("ACC_") || name.startsWith("T_") || - name.startsWith("H_") || name.startsWith("F_")) { - continue; - } - int val = field.getInt(null); - if (val >= 0 && val < sOpcodeNames.length) { - sOpcodeNames[val] = field.getName(); - } - - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - if (opcode >= 0 && opcode < sOpcodeNames.length) { - String name = sOpcodeNames[opcode]; - if (name != null) { - return name; - } - } - - return Integer.toString(opcode); - } - - private static String[] sOpcodeNames; -} -
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CustomViewDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CustomViewDetector.java deleted file mode 100644 index 934fb6f..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CustomViewDetector.java +++ /dev/null
@@ -1,140 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.*; -import com.intellij.psi.PsiClass; -import com.intellij.psi.util.InheritanceUtil; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.uast.*; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -import static com.android.SdkConstants.*; -import static com.android.tools.klint.detector.api.LintUtils.skipParentheses; - -/** - * Makes sure that custom views use a declare styleable that matches - * the name of the custom view - */ -public class CustomViewDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - CustomViewDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Mismatched style and class names */ - public static final Issue ISSUE = Issue.create( - "CustomViewStyleable", //$NON-NLS-1$ - "Mismatched Styleable/Custom View Name", - - "The convention for custom views is to use a `declare-styleable` whose name " + - "matches the custom view class name. The IDE relies on this convention such that " + - "for example code completion can be offered for attributes in a custom view " + - "in layout XML resource files.\n" + - "\n" + - "(Similarly, layout parameter classes should use the suffix `_Layout`.)", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - IMPLEMENTATION); - - private static final String OBTAIN_STYLED_ATTRIBUTES = "obtainStyledAttributes"; //$NON-NLS-1$ - - /** Constructs a new {@link CustomViewDetector} check */ - public CustomViewDetector() { - } - - // ---- Implements UastScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList(OBTAIN_STYLED_ATTRIBUTES); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - if (skipParentheses(node.getUastParent()) instanceof UExpression) { - if (!context.getEvaluator().isMemberInSubClassOf(method, CLASS_CONTEXT, false)) { - return; - } - List<UExpression> arguments = node.getValueArguments(); - int size = arguments.size(); - // Which parameter contains the styleable (attrs) ? - int parameterIndex; - if (size == 1) { - // obtainStyledAttributes(int[] attrs) - parameterIndex = 0; - } else { - // obtainStyledAttributes(int resid, int[] attrs) - // obtainStyledAttributes(AttributeSet set, int[] attrs) - // obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) - parameterIndex = 1; - } - UExpression expression = arguments.get(parameterIndex); - if (!UastUtils.startsWithQualified(expression, R_STYLEABLE_PREFIX)) { - return; - } - - List<String> path = UastUtils.asQualifiedPath(expression); - if (path == null || path.size() < 3) { - return; - } - - String styleableName = path.get(2); - UClass cls = UastUtils.getParentOfType(node, UClass.class, false); - if (cls == null) { - return; - } - - String className = cls.getName(); - if (InheritanceUtil.isInheritor(cls, false, CLASS_VIEW)) { - if (!styleableName.equals(className)) { - String message = String.format( - "By convention, the custom view (`%1$s`) and the declare-styleable (`%2$s`) " - + "should have the same name (various editor features rely on " - + "this convention)", - className, styleableName); - context.report(ISSUE, node, context.getUastLocation(expression), message); - } - } else if (InheritanceUtil.isInheritor(cls, false, - CLASS_VIEWGROUP + DOT_LAYOUT_PARAMS)) { - PsiClass outer = PsiTreeUtil.getParentOfType(cls, PsiClass.class, true); - if (outer == null) { - return; - } - String layoutClassName = outer.getName(); - String expectedName = layoutClassName + "_Layout"; - if (!styleableName.equals(expectedName)) { - String message = String.format( - "By convention, the declare-styleable (`%1$s`) for a layout parameter " - + "class (`%2$s`) is expected to be the surrounding " - + "class (`%3$s`) plus \"`_Layout`\", e.g. `%4$s`. " - + "(Various editor features rely on this convention.)", - styleableName, className, layoutClassName, expectedName); - context.report(ISSUE, node, context.getUastLocation(expression), message); - } - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CutPasteDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CutPasteDetector.java deleted file mode 100644 index b37e857..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CutPasteDetector.java +++ /dev/null
@@ -1,339 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.*; -import com.google.common.collect.Maps; -import com.intellij.psi.PsiMethod; -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.android.SdkConstants.RESOURCE_CLZ_ID; - -/** - * Detector looking for cut & paste issues - */ -public class CutPasteDetector extends Detector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "CutPasteId", //$NON-NLS-1$ - "Likely cut & paste mistakes", - - "This lint check looks for cases where you have cut & pasted calls to " + - "`findViewById` but have forgotten to update the R.id field. It's possible " + - "that your code is simply (redundantly) looking up the field repeatedly, " + - "but lint cannot distinguish that from a case where you for example want to " + - "initialize fields `prev` and `next` and you cut & pasted `findViewById(R.id.prev)` " + - "and forgot to update the second initialization to `R.id.next`.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - CutPasteDetector.class, - Scope.JAVA_FILE_SCOPE)); - - private PsiMethod mLastMethod; - private Map<String, UCallExpression> mIds; - private Map<String, String> mLhs; - private Map<String, String> mCallOperands; - - /** Constructs a new {@link CutPasteDetector} check */ - public CutPasteDetector() { - } - - // ---- Implements UastScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("findViewById"); //$NON-NLS-1$ - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod calledMethod) { - String lhs = getLhs(call); - if (lhs == null) { - return; - } - - UMethod method = UastUtils.getParentOfType(call, UMethod.class, false); - if (method == null) { - return; // prevent doing the same work for multiple findViewById calls in same method - } else if (method != mLastMethod) { - mIds = Maps.newHashMap(); - mLhs = Maps.newHashMap(); - mCallOperands = Maps.newHashMap(); - mLastMethod = method; - } - - String callOperand = call.getReceiver() != null - ? call.getReceiver().asSourceString() : ""; - - List<UExpression> arguments = call.getValueArguments(); - if (arguments.isEmpty()) { - return; - } - UExpression first = arguments.get(0); - if (first instanceof UReferenceExpression) { - UReferenceExpression psiReferenceExpression = (UReferenceExpression) first; - String id = psiReferenceExpression.getResolvedName(); - UElement operand = (first instanceof UQualifiedReferenceExpression) - ? ((UQualifiedReferenceExpression) first).getReceiver() - : null; - - if (operand instanceof UReferenceExpression) { - UReferenceExpression type = (UReferenceExpression) operand; - if (RESOURCE_CLZ_ID.equals(type.getResolvedName())) { - if (mIds.containsKey(id)) { - if (lhs.equals(mLhs.get(id))) { - return; - } - if (!callOperand.equals(mCallOperands.get(id))) { - return; - } - UCallExpression earlierCall = mIds.get(id); - if (!isReachableFrom(method, earlierCall, call)) { - return; - } - Location location = context.getUastLocation(call); - Location secondary = context.getUastLocation(earlierCall); - secondary.setMessage("First usage here"); - location.setSecondary(secondary); - context.report(ISSUE, call, location, String.format( - "The id `%1$s` has already been looked up in this method; possible " - + - "cut & paste error?", first.asSourceString())); - } else { - mIds.put(id, call); - mLhs.put(id, lhs); - mCallOperands.put(id, callOperand); - } - } - - } - } - } - - @Nullable - private static String getLhs(@NonNull UCallExpression call) { - UElement parent = call.getUastParent(); - while (parent != null && !(parent instanceof UBlockExpression)) { - if (parent instanceof ULocalVariable) { - return ((ULocalVariable) parent).getName(); - } else if (UastExpressionUtils.isAssignment(parent)) { - UExpression left = ((UBinaryExpression) parent).getLeftOperand(); - if (left instanceof UReferenceExpression) { - return left.asSourceString(); - } else if (left instanceof UArrayAccessExpression) { - UArrayAccessExpression aa = (UArrayAccessExpression) left; - return aa.getReceiver().asSourceString(); - } - } - parent = parent.getUastParent(); - } - return null; - } - - static boolean isReachableFrom( - @NonNull UMethod method, - @NonNull UElement from, - @NonNull UElement to) { - ReachabilityVisitor visitor = new ReachabilityVisitor(from, to); - method.accept(visitor); - return visitor.isReachable(); - } - - private static class ReachabilityVisitor extends AbstractUastVisitor { - - private final UElement mFrom; - private final UElement mTarget; - - private boolean mIsFromReached; - private boolean mIsTargetReachable; - private boolean mIsFinished; - - private UExpression mBreakedExpression; - private UExpression mContinuedExpression; - - ReachabilityVisitor(UElement from, UElement target) { - mFrom = from; - mTarget = target; - } - - @Override - public boolean visitElement(UElement node) { - if (mIsFinished || mBreakedExpression != null || mContinuedExpression != null) { - return true; - } - - if (node.equals(mFrom)) { - mIsFromReached = true; - } - - if (node.equals(mTarget)) { - mIsFinished = true; - if (mIsFromReached) { - mIsTargetReachable = true; - } - return true; - } - - if (mIsFromReached) { - if (node instanceof UReturnExpression) { - mIsFinished = true; - } else if (node instanceof UBreakExpression) { - mBreakedExpression = getBreakedExpression((UBreakExpression) node); - } else if (node instanceof UContinueExpression) { - UExpression expression = getContinuedExpression((UContinueExpression) node); - if (expression != null && UastUtils.isChildOf(mTarget, expression, false)) { - mIsTargetReachable = true; - mIsFinished = true; - } else { - mContinuedExpression = expression; - } - } else if (UastUtils.isChildOf(mTarget, node, false)) { - mIsTargetReachable = true; - mIsFinished = true; - } - return true; - } else { - if (node instanceof UIfExpression) { - UIfExpression ifExpression = (UIfExpression) node; - - ifExpression.getCondition().accept(this); - - boolean isFromReached = mIsFromReached; - - UExpression thenExpression = ifExpression.getThenExpression(); - if (thenExpression != null) { - thenExpression.accept(this); - } - - UExpression elseExpression = ifExpression.getElseExpression(); - if (elseExpression != null && isFromReached == mIsFromReached) { - elseExpression.accept(this); - } - return true; - } else if (node instanceof ULoopExpression) { - visitLoopExpressionHeader(node); - boolean isFromReached = mIsFromReached; - - ((ULoopExpression) node).getBody().accept(this); - - if (isFromReached != mIsFromReached - && UastUtils.isChildOf(mTarget, node, false)) { - mIsTargetReachable = true; - mIsFinished = true; - } - return true; - } - } - - return false; - } - - @Override - public void afterVisitElement(UElement node) { - if (node.equals(mBreakedExpression)) { - mBreakedExpression = null; - } else if (node.equals(mContinuedExpression)) { - mContinuedExpression = null; - } - } - - private void visitLoopExpressionHeader(UElement node) { - if (node instanceof UWhileExpression) { - ((UWhileExpression) node).getCondition().accept(this); - } else if (node instanceof UDoWhileExpression) { - ((UDoWhileExpression) node).getCondition().accept(this); - } else if (node instanceof UForExpression) { - UForExpression forExpression = (UForExpression) node; - - if (forExpression.getDeclaration() != null) { - forExpression.getDeclaration().accept(this); - } - - if (forExpression.getCondition() != null) { - forExpression.getCondition().accept(this); - } - - if (forExpression.getUpdate() != null) { - forExpression.getUpdate().accept(this); - } - } else if (node instanceof UForEachExpression) { - UForEachExpression forEachExpression = (UForEachExpression) node; - forEachExpression.getForIdentifier().accept(this); - forEachExpression.getIteratedValue().accept(this); - } - } - - private static UExpression getBreakedExpression(UBreakExpression node) { - UElement parent = node.getUastParent(); - String label = node.getLabel(); - while (parent != null) { - if (label != null) { - if (parent instanceof ULabeledExpression) { - ULabeledExpression labeledExpression = (ULabeledExpression) parent; - if (labeledExpression.getLabel().equals(label)) { - return labeledExpression.getExpression(); - } - } - } else { - if (parent instanceof ULoopExpression || parent instanceof USwitchExpression) { - return (UExpression) parent; - } - } - parent = parent.getUastParent(); - } - return null; - } - - private static UExpression getContinuedExpression(UContinueExpression node) { - UElement parent = node.getUastParent(); - String label = node.getLabel(); - while (parent != null) { - if (label != null) { - if (parent instanceof ULabeledExpression) { - ULabeledExpression labeledExpression = (ULabeledExpression) parent; - if (labeledExpression.getLabel().equals(label)) { - return labeledExpression.getExpression(); - } - } - } else { - if (parent instanceof ULoopExpression) { - return (UExpression) parent; - } - } - parent = parent.getUastParent(); - } - return null; - } - - public boolean isReachable() { - return mIsTargetReachable; - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/DateFormatDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/DateFormatDetector.java deleted file mode 100644 index b5bc84e..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/DateFormatDetector.java +++ /dev/null
@@ -1,113 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiParameterList; -import com.intellij.psi.PsiType; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Checks for errors related to Date Formats - */ -public class DateFormatDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - DateFormatDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Constructing SimpleDateFormat without an explicit locale */ - public static final Issue DATE_FORMAT = Issue.create( - "SimpleDateFormat", //$NON-NLS-1$ - "Implied locale in date format", - - "Almost all callers should use `getDateInstance()`, `getDateTimeInstance()`, or " + - "`getTimeInstance()` to get a ready-made instance of SimpleDateFormat suitable " + - "for the user's locale. The main reason you'd create an instance this class " + - "directly is because you need to format/parse a specific machine-readable format, " + - "in which case you almost certainly want to explicitly ask for US to ensure that " + - "you get ASCII digits (rather than, say, Arabic digits).\n" + - "\n" + - "Therefore, you should either use the form of the SimpleDateFormat constructor " + - "where you pass in an explicit locale, such as Locale.US, or use one of the " + - "get instance methods, or suppress this error if really know what you are doing.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - IMPLEMENTATION) - .addMoreInfo( - "http://developer.android.com/reference/java/text/SimpleDateFormat.html");//$NON-NLS-1$ - - public static final String LOCALE_CLS = "java.util.Locale"; //$NON-NLS-1$ - public static final String SIMPLE_DATE_FORMAT_CLS = "java.text.SimpleDateFormat"; //$NON-NLS-1$ - - /** Constructs a new {@link DateFormatDetector} */ - public DateFormatDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableConstructorTypes() { - return Collections.singletonList(SIMPLE_DATE_FORMAT_CLS); - } - - @Override - public void visitConstructor(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod constructor) { - if (!specifiesLocale(constructor)) { - Location location = context.getUastLocation(node); - String message = - "To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, " + - "or `getTimeInstance()`, or use `new SimpleDateFormat(String template, " + - "Locale locale)` with for example `Locale.US` for ASCII dates."; - context.report(DATE_FORMAT, node, location, message); - } - } - - private static boolean specifiesLocale(@NonNull PsiMethod method) { - PsiParameterList parameterList = method.getParameterList(); - PsiParameter[] parameters = parameterList.getParameters(); - for (PsiParameter parameter : parameters) { - PsiType type = parameter.getType(); - if (type.getCanonicalText().equals(LOCALE_CLS)) { - return true; - } - } - - return false; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/FragmentDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/FragmentDetector.java deleted file mode 100644 index 79d9756..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/FragmentDetector.java +++ /dev/null
@@ -1,151 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.CLASS_FRAGMENT; -import static com.android.SdkConstants.CLASS_V4_FRAGMENT; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiAnonymousClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.UAnonymousClass; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; - -import java.util.Arrays; -import java.util.List; - -/** - * Checks that Fragment subclasses can be instantiated via - * {link {@link Class#newInstance()}}: the class is public, static, and has - * a public null constructor. - * <p> - * This helps track down issues like - * http://stackoverflow.com/questions/8058809/fragment-activity-crashes-on-screen-rotate - * (and countless duplicates) - */ -public class FragmentDetector extends Detector implements Detector.UastScanner { - /** Are fragment subclasses instantiatable? */ - public static final Issue ISSUE = Issue.create( - "ValidFragment", //$NON-NLS-1$ - "Fragment not instantiatable", - - "From the Fragment documentation:\n" + - "*Every* fragment must have an empty constructor, so it can be instantiated when " + - "restoring its activity's state. It is strongly recommended that subclasses do not " + - "have other constructors with parameters, since these constructors will not be " + - "called when the fragment is re-instantiated; instead, arguments can be supplied " + - "by the caller with `setArguments(Bundle)` and later retrieved by the Fragment " + - "with `getArguments()`.", - - Category.CORRECTNESS, - 6, - Severity.FATAL, - new Implementation( - FragmentDetector.class, - Scope.JAVA_FILE_SCOPE) - ).addMoreInfo( - "http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$ - - - /** Constructs a new {@link FragmentDetector} */ - public FragmentDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Arrays.asList(CLASS_FRAGMENT, CLASS_V4_FRAGMENT); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass node) { - if (node instanceof UAnonymousClass) { - String message = "Fragments should be static such that they can be re-instantiated by " + - "the system, and anonymous classes are not static"; - context.reportUast(ISSUE, node, context.getUastNameLocation(node), message); - return; - } - - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.isAbstract(node)) { - return; - } - - if (!evaluator.isPublic(node)) { - String message = String.format("This fragment class should be public (%1$s)", - node.getQualifiedName()); - context.reportUast(ISSUE, node, context.getUastNameLocation(node), message); - return; - } - - if (node.getContainingClass() != null && !evaluator.isStatic(node)) { - String message = String.format( - "This fragment inner class should be static (%1$s)", node.getQualifiedName()); - context.reportUast(ISSUE, node, context.getUastNameLocation(node), message); - return; - } - - boolean hasDefaultConstructor = false; - boolean hasConstructor = false; - for (PsiMethod constructor : node.getConstructors()) { - hasConstructor = true; - if (constructor.getParameterList().getParametersCount() == 0) { - if (evaluator.isPublic(constructor)) { - hasDefaultConstructor = true; - } else { - Location location = context.getNameLocation(constructor); - context.report(ISSUE, constructor, location, - "The default constructor must be public"); - // Also mark that we have a constructor so we don't complain again - // below since we've already emitted a more specific error related - // to the default constructor - hasDefaultConstructor = true; - } - } else { - Location location = context.getNameLocation(constructor); - // TODO: Use separate issue for this which isn't an error - String message = "Avoid non-default constructors in fragments: " - + "use a default constructor plus " - + "`Fragment#setArguments(Bundle)` instead"; - context.report(ISSUE, constructor, location, message); - } - } - - if (!hasDefaultConstructor && hasConstructor) { - String message = String.format( - "This fragment should provide a default constructor (a public " + - "constructor with no arguments) (`%1$s`)", - node.getQualifiedName()); - context.reportUast(ISSUE, node, context.getNameLocation(node), message); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/GetSignaturesDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/GetSignaturesDetector.java deleted file mode 100644 index a43817a..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/GetSignaturesDetector.java +++ /dev/null
@@ -1,99 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.JavaParser; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -public class GetSignaturesDetector extends Detector implements Detector.UastScanner { - public static final Issue ISSUE = Issue.create( - "PackageManagerGetSignatures", //$NON-NLS-1$ - "Potential Multiple Certificate Exploit", - "Improper validation of app signatures could lead to issues where a malicious app " + - "submits itself to the Play Store with both its real certificate and a fake " + - "certificate and gains access to functionality or information it shouldn't " + - "have due to another application only checking for the fake certificate and " + - "ignoring the rest. Please make sure to validate all signatures returned " + - "by this method.", - Category.SECURITY, - 8, - Severity.INFORMATIONAL, - new Implementation( - GetSignaturesDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo("https://bluebox.com/technical/android-fake-id-vulnerability/"); - - private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager"; //$NON-NLS-1$ - private static final String GET_PACKAGE_INFO = "getPackageInfo"; //$NON-NLS-1$ - private static final int GET_SIGNATURES_FLAG = 0x00000040; //$NON-NLS-1$ - - // ---- Implements JavaScanner ---- - - @Override - @Nullable - public List<String> getApplicableMethodNames() { - return Collections.singletonList(GET_PACKAGE_INFO); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - JavaEvaluator evaluator = context.getEvaluator(); - if (!evaluator.methodMatches(method, PACKAGE_MANAGER_CLASS, true, - JavaParser.TYPE_STRING, - JavaParser.TYPE_INT)) { - return; - } - - List<UExpression> arguments = node.getValueArguments(); - UExpression second = arguments.get(1); - Object number = ConstantEvaluator.evaluate(context, second); - if (number instanceof Number) { - int flagValue = ((Number)number).intValue(); - maybeReportIssue(flagValue, context, node, second); - } - } - - private static void maybeReportIssue( - int flagValue, JavaContext context, UCallExpression node, - UExpression last) { - if ((flagValue & GET_SIGNATURES_FLAG) != 0) { - context.report(ISSUE, node, context.getUastLocation(last), - "Reading app signatures from getPackageInfo: The app signatures " - + "could be exploited if not validated properly; " - + "see issue explanation for details."); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/HandlerDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/HandlerDetector.java deleted file mode 100644 index 59a8517..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/HandlerDetector.java +++ /dev/null
@@ -1,149 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.intellij.psi.util.PsiTreeUtil.getParentOfType; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiType; - -import org.jetbrains.uast.UAnonymousClass; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UObjectLiteralExpression; -import org.jetbrains.uast.UastUtils; - -import java.util.Collections; -import java.util.List; - -/** - * Checks that Handler implementations are top level classes or static. - * See the corresponding check in the android.os.Handler source code. - */ -public class HandlerDetector extends Detector implements Detector.UastScanner { - - /** Potentially leaking handlers */ - public static final Issue ISSUE = Issue.create( - "HandlerLeak", //$NON-NLS-1$ - "Handler reference leaks", - - "Since this Handler is declared as an inner class, it may prevent the outer " + - "class from being garbage collected. If the Handler is using a Looper or " + - "MessageQueue for a thread other than the main thread, then there is no issue. " + - "If the Handler is using the Looper or MessageQueue of the main thread, you " + - "need to fix your Handler declaration, as follows: Declare the Handler as a " + - "static class; In the outer class, instantiate a WeakReference to the outer " + - "class and pass this object to your Handler when you instantiate the Handler; " + - "Make all references to members of the outer class using the WeakReference object.", - - Category.PERFORMANCE, - 4, - Severity.WARNING, - new Implementation( - HandlerDetector.class, - Scope.JAVA_FILE_SCOPE)); - - private static final String LOOPER_CLS = "android.os.Looper"; - private static final String HANDLER_CLS = "android.os.Handler"; - - /** Constructs a new {@link HandlerDetector} */ - public HandlerDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList(HANDLER_CLS); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - // Only consider static inner classes - if (context.getEvaluator().isStatic(declaration)) { - return; - } - boolean isAnonymous = declaration instanceof UAnonymousClass; - if (declaration.getContainingClass() == null && !isAnonymous) { - return; - } - - //// Only flag handlers using the default looper - //noinspection unchecked - UCallExpression invocation = UastUtils.getParentOfType( - declaration, UObjectLiteralExpression.class, true, UMethod.class); - if (invocation != null) { - if (isAnonymous && invocation.getValueArgumentCount() > 0) { - for (UExpression expression : invocation.getValueArguments()) { - PsiType type = expression.getExpressionType(); - if (type instanceof PsiClassType - && LOOPER_CLS.equals(type.getCanonicalText())) { - return; - } - } - } - } else if (hasLooperConstructorParameter(declaration)) { - // This is an inner class which takes a Looper parameter: - // possibly used correctly from elsewhere - return; - } - - Location location = context.getUastNameLocation(declaration); - String name; - if (isAnonymous) { - name = "anonymous " + ((UAnonymousClass)declaration).getBaseClassReference().getQualifiedName(); - } else { - name = declaration.getQualifiedName(); - } - - //noinspection VariableNotUsedInsideIf - context.reportUast(ISSUE, declaration, location, String.format( - "This Handler class should be static or leaks might occur (%1$s)", - name)); - - } - - private static boolean hasLooperConstructorParameter(@NonNull PsiClass cls) { - for (PsiMethod constructor : cls.getConstructors()) { - for (PsiParameter parameter : constructor.getParameterList().getParameters()) { - PsiType type = parameter.getType(); - if (LOOPER_CLS.equals(type.getCanonicalText())) { - return true; - } - } - } - return false; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/IconDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/IconDetector.java deleted file mode 100644 index 1bad2d5..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/IconDetector.java +++ /dev/null
@@ -1,2049 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.builder.model.ProductFlavor; -import com.android.builder.model.ProductFlavorContainer; -import com.android.ide.common.resources.ResourceUrl; -import com.android.resources.Density; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.detector.api.*; -import com.google.common.collect.*; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Element; - -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.List; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.android.SdkConstants.*; -import static com.android.tools.klint.detector.api.LintUtils.endsWith; - -/** - * Checks for common icon problems, such as wrong icon sizes, placing icons in the - * density independent drawable folder, etc. - */ -public class IconDetector extends ResourceXmlDetector implements Detector.UastScanner { - - private static final boolean INCLUDE_LDPI; - static { - boolean includeLdpi = false; - - String value = System.getenv("ANDROID_LINT_INCLUDE_LDPI"); //$NON-NLS-1$ - if (value != null) { - includeLdpi = Boolean.valueOf(value); - } - INCLUDE_LDPI = includeLdpi; - } - - /** Pattern for the expected density folders to be found in the project */ - private static final Pattern DENSITY_PATTERN = Pattern.compile( - "^drawable-(nodpi|xxxhdpi|xxhdpi|xhdpi|hdpi|mdpi" //$NON-NLS-1$ - + (INCLUDE_LDPI ? "|ldpi" : "") + ")$"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - - /** Pattern for icon names that include their dp size as part of the name */ - private static final Pattern DP_NAME_PATTERN = Pattern.compile(".+_(\\d+)dp\\.png"); //$NON-NLS-1$ - - /** Cache for {@link #getRequiredDensityFolders(Context)} */ - private List<String> mCachedRequiredDensities; - /** Cache key for {@link #getRequiredDensityFolders(Context)} */ - private Project mCachedDensitiesForProject; - - // TODO: Convert this over to using the Density enum and FolderConfiguration - // for qualifier lookup - private static final String[] DENSITY_QUALIFIERS = - new String[] { - "-ldpi", //$NON-NLS-1$ - "-mdpi", //$NON-NLS-1$ - "-hdpi", //$NON-NLS-1$ - "-xhdpi", //$NON-NLS-1$ - "-xxhdpi",//$NON-NLS-1$ - "-xxxhdpi",//$NON-NLS-1$ - }; - - /** Scope needed to detect the types of icons (which involves scanning .java files, - * the manifest, menu files etc to see how icons are used - */ - private static final EnumSet<Scope> ICON_TYPE_SCOPE = EnumSet.of(Scope.ALL_RESOURCE_FILES, - Scope.JAVA_FILE, Scope.MANIFEST); - - private static final Implementation IMPLEMENTATION_JAVA = new Implementation( - IconDetector.class, - ICON_TYPE_SCOPE); - - private static final Implementation IMPLEMENTATION_RES_ONLY = new Implementation( - IconDetector.class, - Scope.ALL_RESOURCES_SCOPE); - - /** Wrong icon size according to published conventions */ - public static final Issue ICON_EXPECTED_SIZE = Issue.create( - "IconExpectedSize", //$NON-NLS-1$ - "Icon has incorrect size", - "There are predefined sizes (for each density) for launcher icons. You " + - "should follow these conventions to make sure your icons fit in with the " + - "overall look of the platform.", - Category.ICONS, - 5, - Severity.WARNING, - IMPLEMENTATION_JAVA) - // Still some potential false positives: - .setEnabledByDefault(false) - .addMoreInfo( - "http://developer.android.com/design/style/iconography.html"); //$NON-NLS-1$ - - /** Inconsistent dip size across densities */ - public static final Issue ICON_DIP_SIZE = Issue.create( - "IconDipSize", //$NON-NLS-1$ - "Icon density-independent size validation", - "Checks the all icons which are provided in multiple densities, all compute to " + - "roughly the same density-independent pixel (`dip`) size. This catches errors where " + - "images are either placed in the wrong folder, or icons are changed to new sizes " + - "but some folders are forgotten.", - Category.ICONS, - 5, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY); - - /** Images in res/drawable folder */ - public static final Issue ICON_LOCATION = Issue.create( - "IconLocation", //$NON-NLS-1$ - "Image defined in density-independent drawable folder", - "The res/drawable folder is intended for density-independent graphics such as " + - "shapes defined in XML. For bitmaps, move it to `drawable-mdpi` and consider " + - "providing higher and lower resolution versions in `drawable-ldpi`, `drawable-hdpi` " + - "and `drawable-xhdpi`. If the icon *really* is density independent (for example " + - "a solid color) you can place it in `drawable-nodpi`.", - Category.ICONS, - 5, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY).addMoreInfo( - "http://developer.android.com/guide/practices/screens_support.html"); //$NON-NLS-1$ - - /** Missing density versions of image */ - public static final Issue ICON_DENSITIES = Issue.create( - "IconDensities", //$NON-NLS-1$ - "Icon densities validation", - "Icons will look best if a custom version is provided for each of the " + - "major screen density classes (low, medium, high, extra high). " + - "This lint check identifies icons which do not have complete coverage " + - "across the densities.\n" + - "\n" + - "Low density is not really used much anymore, so this check ignores " + - "the ldpi density. To force lint to include it, set the environment " + - "variable `ANDROID_LINT_INCLUDE_LDPI=true`. For more information on " + - "current density usage, see " + - "http://developer.android.com/resources/dashboard/screens.html", - Category.ICONS, - 4, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY).addMoreInfo( - "http://developer.android.com/guide/practices/screens_support.html"); //$NON-NLS-1$ - - /** Missing density folders */ - public static final Issue ICON_MISSING_FOLDER = Issue.create( - "IconMissingDensityFolder", //$NON-NLS-1$ - "Missing density folder", - "Icons will look best if a custom version is provided for each of the " + - "major screen density classes (low, medium, high, extra-high, extra-extra-high). " + - "This lint check identifies folders which are missing, such as `drawable-hdpi`.\n" + - "\n" + - "Low density is not really used much anymore, so this check ignores " + - "the ldpi density. To force lint to include it, set the environment " + - "variable `ANDROID_LINT_INCLUDE_LDPI=true`. For more information on " + - "current density usage, see " + - "http://developer.android.com/resources/dashboard/screens.html", - Category.ICONS, - 3, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY).addMoreInfo( - "http://developer.android.com/guide/practices/screens_support.html"); //$NON-NLS-1$ - - /** Using .gif bitmaps */ - public static final Issue GIF_USAGE = Issue.create( - "GifUsage", //$NON-NLS-1$ - "Using `.gif` format for bitmaps is discouraged", - "The `.gif` file format is discouraged. Consider using `.png` (preferred) " + - "or `.jpg` (acceptable) instead.", - Category.ICONS, - 5, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY).addMoreInfo( - "http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap"); //$NON-NLS-1$ - - /** Duplicated icons across different names */ - public static final Issue DUPLICATES_NAMES = Issue.create( - "IconDuplicates", //$NON-NLS-1$ - "Duplicated icons under different names", - "If an icon is repeated under different names, you can consolidate and just " + - "use one of the icons and delete the others to make your application smaller. " + - "However, duplicated icons usually are not intentional and can sometimes point " + - "to icons that were accidentally overwritten or accidentally not updated.", - Category.ICONS, - 3, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY); - - /** Duplicated contents across configurations for a given name */ - public static final Issue DUPLICATES_CONFIGURATIONS = Issue.create( - "IconDuplicatesConfig", //$NON-NLS-1$ - "Identical bitmaps across various configurations", - "If an icon is provided under different configuration parameters such as " + - "`drawable-hdpi` or `-v11`, they should typically be different. This detector " + - "catches cases where the same icon is provided in different configuration folder " + - "which is usually not intentional.", - Category.ICONS, - 5, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY); - - /** Icons appearing in both -nodpi and a -Ndpi folder */ - public static final Issue ICON_NODPI = Issue.create( - "IconNoDpi", //$NON-NLS-1$ - "Icon appears in both `-nodpi` and dpi folders", - "Bitmaps that appear in `drawable-nodpi` folders will not be scaled by the " + - "Android framework. If a drawable resource of the same name appears *both* in " + - "a `-nodpi` folder as well as a dpi folder such as `drawable-hdpi`, then " + - "the behavior is ambiguous and probably not intentional. Delete one or the " + - "other, or use different names for the icons.", - Category.ICONS, - 7, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY); - - /** Drawables provided as both .9.png and .png files */ - public static final Issue ICON_MIX_9PNG = Issue.create( - "IconMixedNinePatch", //$NON-NLS-1$ - "Clashing PNG and 9-PNG files", - - "If you accidentally name two separate resources `file.png` and `file.9.png`, " + - "the image file and the nine patch file will both map to the same drawable " + - "resource, `@drawable/file`, which is probably not what was intended.", - Category.ICONS, - 5, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY); - - /** Icons appearing as both drawable xml files and bitmaps */ - public static final Issue ICON_XML_AND_PNG = Issue.create( - "IconXmlAndPng", //$NON-NLS-1$ - "Icon is specified both as `.xml` file and as a bitmap", - "If a drawable resource appears as an `.xml` file in the `drawable/` folder, " + - "it's usually not intentional for it to also appear as a bitmap using the " + - "same name; generally you expect the drawable XML file to define states " + - "and each state has a corresponding drawable bitmap.", - Category.ICONS, - 7, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY); - - /** Wrong filename according to the format */ - public static final Issue ICON_EXTENSION = Issue.create( - "IconExtension", //$NON-NLS-1$ - "Icon format does not match the file extension", - - "Ensures that icons have the correct file extension (e.g. a `.png` file is " + - "really in the PNG format and not for example a GIF file named `.png`.)", - Category.ICONS, - 3, - Severity.WARNING, - IMPLEMENTATION_RES_ONLY); - - /** Wrong filename according to the format */ - public static final Issue ICON_COLORS = Issue.create( - "IconColors", //$NON-NLS-1$ - "Icon colors do not follow the recommended visual style", - - "Notification icons and Action Bar icons should only white and shades of gray. " + - "See the Android Design Guide for more details. " + - "Note that the way Lint decides whether an icon is an action bar icon or " + - "a notification icon is based on the filename prefix: `ic_menu_` for " + - "action bar icons, `ic_stat_` for notification icons etc. These correspond " + - "to the naming conventions documented in " + - "http://developer.android.com/guide/practices/ui_guidelines/icon_design.html", - Category.ICONS, - 6, - Severity.WARNING, - IMPLEMENTATION_JAVA).addMoreInfo( - "http://developer.android.com/design/style/iconography.html"); //$NON-NLS-1$ - - /** Wrong launcher icon shape */ - public static final Issue ICON_LAUNCHER_SHAPE = Issue.create( - "IconLauncherShape", //$NON-NLS-1$ - "The launcher icon shape should use a distinct silhouette", - - "According to the Android Design Guide " + - "(http://developer.android.com/design/style/iconography.html) " + - "your launcher icons should \"use a distinct silhouette\", " + - "a \"three-dimensional, front view, with a slight perspective as if viewed " + - "from above, so that users perceive some depth.\"\n" + - "\n" + - "The unique silhouette implies that your launcher icon should not be a filled " + - "square.", - Category.ICONS, - 6, - Severity.WARNING, - IMPLEMENTATION_JAVA).addMoreInfo( - "http://developer.android.com/design/style/iconography.html"); //$NON-NLS-1$ - - /** Constructs a new {@link IconDetector} check */ - public IconDetector() { - } - - @Override - public void beforeCheckProject(@NonNull Context context) { - mLauncherIcons = null; - mActionBarIcons = null; - mNotificationIcons = null; - } - - @Override - public void afterCheckLibraryProject(@NonNull Context context) { - if (!context.getProject().getReportIssues()) { - // If this is a library project not being analyzed, ignore it - return; - } - - checkResourceFolder(context, context.getProject()); - } - - @Override - public void afterCheckProject(@NonNull Context context) { - checkResourceFolder(context, context.getProject()); - } - - private void checkResourceFolder(Context context, @NonNull Project project) { - List<File> resourceFolders = project.getResourceFolders(); - for (File res : resourceFolders) { - File[] folders = res.listFiles(); - if (folders != null) { - boolean checkFolders = context.isEnabled(ICON_DENSITIES) - || context.isEnabled(ICON_MISSING_FOLDER) - || context.isEnabled(ICON_NODPI) - || context.isEnabled(ICON_MIX_9PNG) - || context.isEnabled(ICON_XML_AND_PNG); - boolean checkDipSizes = context.isEnabled(ICON_DIP_SIZE); - boolean checkDuplicates = context.isEnabled(DUPLICATES_NAMES) - || context.isEnabled(DUPLICATES_CONFIGURATIONS); - - Map<File, Dimension> pixelSizes = null; - Map<File, Long> fileSizes = null; - if (checkDipSizes || checkDuplicates) { - pixelSizes = new HashMap<File, Dimension>(); - fileSizes = new HashMap<File, Long>(); - } - Map<File, Set<String>> folderToNames = new HashMap<File, Set<String>>(); - Map<File, Set<String>> nonDpiFolderNames = new HashMap<File, Set<String>>(); - for (File folder : folders) { - String folderName = folder.getName(); - if (folderName.startsWith(DRAWABLE_FOLDER)) { - File[] files = folder.listFiles(); - if (files != null) { - checkDrawableDir(context, folder, files, pixelSizes, fileSizes); - - if (checkFolders && DENSITY_PATTERN.matcher(folderName).matches()) { - Set<String> names = new HashSet<String>(files.length); - for (File f : files) { - String name = f.getName(); - if (isDrawableFile(name)) { - names.add(name); - } - } - folderToNames.put(folder, names); - } else if (checkFolders) { - Set<String> names = new HashSet<String>(files.length); - for (File f : files) { - String name = f.getName(); - if (isDrawableFile(name)) { - names.add(name); - } - } - nonDpiFolderNames.put(folder, names); - } - } - } - } - - if (checkDipSizes) { - checkDipSizes(context, pixelSizes); - } - - if (checkDuplicates) { - checkDuplicates(context, pixelSizes, fileSizes); - } - - if (checkFolders && !folderToNames.isEmpty()) { - checkDensities(context, res, folderToNames, nonDpiFolderNames); - } - } - } - } - - /** Like {@link LintUtils#isBitmapFile(File)} but (a) operates on Strings instead - * of files and (b) also considers XML drawables as images */ - private static boolean isDrawableFile(String name) { - // endsWith(name, DOT_PNG) is also true for endsWith(name, DOT_9PNG) - return endsWith(name, DOT_PNG)|| endsWith(name, DOT_JPG) || endsWith(name, DOT_GIF) - || endsWith(name, DOT_XML) || endsWith(name, DOT_JPEG) || endsWith(name, DOT_WEBP); - } - - // This method looks for duplicates in the assets. This uses two pieces of information - // (file sizes and image dimensions) to quickly reject candidates, such that it only - // needs to check actual file contents on a small subset of the available files. - private static void checkDuplicates(Context context, Map<File, Dimension> pixelSizes, - Map<File, Long> fileSizes) { - Map<Long, Set<File>> sameSizes = new HashMap<Long, Set<File>>(); - Map<Long, File> seenSizes = new HashMap<Long, File>(fileSizes.size()); - for (Map.Entry<File, Long> entry : fileSizes.entrySet()) { - File file = entry.getKey(); - Long size = entry.getValue(); - if (seenSizes.containsKey(size)) { - Set<File> set = sameSizes.get(size); - if (set == null) { - set = new HashSet<File>(); - set.add(seenSizes.get(size)); - sameSizes.put(size, set); - } - set.add(file); - } else { - seenSizes.put(size, file); - } - } - - if (sameSizes.isEmpty()) { - return; - } - - // Now go through the files that have the same size and check to see if we can - // split them apart based on image dimensions - // Note: we may not have file sizes on all the icons; in particular, - // we don't have file sizes for ninepatch files. - Collection<Set<File>> candidateLists = sameSizes.values(); - for (Set<File> candidates : candidateLists) { - Map<Dimension, Set<File>> sameDimensions = new HashMap<Dimension, Set<File>>( - candidates.size()); - List<File> noSize = new ArrayList<File>(); - for (File file : candidates) { - Dimension dimension = pixelSizes.get(file); - if (dimension != null) { - Set<File> set = sameDimensions.get(dimension); - if (set == null) { - set = new HashSet<File>(); - sameDimensions.put(dimension, set); - } - set.add(file); - } else { - noSize.add(file); - } - } - - - // Files that we have no dimensions for must be compared against everything - Collection<Set<File>> sets = sameDimensions.values(); - if (!noSize.isEmpty()) { - if (!sets.isEmpty()) { - for (Set<File> set : sets) { - set.addAll(noSize); - } - } else { - // Must just test the noSize elements against themselves - HashSet<File> noSizeSet = new HashSet<File>(noSize); - sets = Collections.<Set<File>>singletonList(noSizeSet); - } - } - - // Map from file to actual byte contents of the file. - // We store this in a map such that for repeated files, such as noSize files - // which can appear in multiple buckets, we only need to read them once - Map<File, byte[]> fileContents = new HashMap<File, byte[]>(); - - // Now we're ready for the final check where we actually check the - // bits. We have to partition the files into buckets of files that - // are identical. - for (Set<File> set : sets) { - if (set.size() < 2) { - continue; - } - - // Read all files in this set and store in map - for (File file : set) { - byte[] bits = fileContents.get(file); - if (bits == null) { - try { - bits = context.getClient().readBytes(file); - fileContents.put(file, bits); - } catch (IOException e) { - context.log(e, null); - } - } - } - - // Map where the key file is known to be equal to the value file. - // After we check individual files for equality this will be used - // to look for transitive equality. - Map<File, File> equal = new HashMap<File, File>(); - - // Now go and compare all the files. This isn't an efficient algorithm - // but the number of candidates should be very small - - List<File> files = new ArrayList<File>(set); - Collections.sort(files); - for (int i = 0; i < files.size() - 1; i++) { - for (int j = i + 1; j < files.size(); j++) { - File file1 = files.get(i); - File file2 = files.get(j); - byte[] contents1 = fileContents.get(file1); - byte[] contents2 = fileContents.get(file2); - if (contents1 == null || contents2 == null) { - // File couldn't be read: ignore - continue; - } - if (contents1.length != contents2.length) { - // Sizes differ: not identical. - // This shouldn't happen since we've already partitioned based - // on File.length(), but just make sure here since the file - // system could have lied, or cached a value that has changed - // if the file was just overwritten - continue; - } - boolean same = true; - for (int k = 0; k < contents1.length; k++) { - if (contents1[k] != contents2[k]) { - same = false; - break; - } - } - if (same) { - equal.put(file1, file2); - } - } - } - - if (!equal.isEmpty()) { - Map<File, Set<File>> partitions = new HashMap<File, Set<File>>(); - List<Set<File>> sameSets = new ArrayList<Set<File>>(); - for (Map.Entry<File, File> entry : equal.entrySet()) { - File file1 = entry.getKey(); - File file2 = entry.getValue(); - Set<File> set1 = partitions.get(file1); - Set<File> set2 = partitions.get(file2); - if (set1 != null) { - set1.add(file2); - } else if (set2 != null) { - set2.add(file1); - } else { - set = new HashSet<File>(); - sameSets.add(set); - set.add(file1); - set.add(file2); - partitions.put(file1, set); - partitions.put(file2, set); - } - } - - // We've computed the partitions of equal files. Now sort them - // for stable output. - List<List<File>> lists = new ArrayList<List<File>>(); - for (Set<File> same : sameSets) { - assert !same.isEmpty(); - ArrayList<File> sorted = new ArrayList<File>(same); - Collections.sort(sorted); - lists.add(sorted); - } - // Sort overall partitions by the first item in each list - Collections.sort(lists, new Comparator<List<File>>() { - @Override - public int compare(List<File> list1, List<File> list2) { - return list1.get(0).compareTo(list2.get(0)); - } - }); - - // Allow one specific scenario of duplicated icon contents: - // Checking in different size icons (within a single density - // folder). For now the only pattern we recognize is the - // one advocated by the material design icons: - // https://github.com/google/material-design-icons - // where the pattern is foo_<N>dp.png. (See issue 74584 for more.) - ListIterator<List<File>> iterator = lists.listIterator(); - while (iterator.hasNext()) { - List<File> list = iterator.next(); - boolean remove = true; - for (File file : list) { - String name = file.getName(); - if (!DP_NAME_PATTERN.matcher(name).matches()) { - // One or more pattern in this list does not - // conform to the dp naming pattern, so - remove = false; - break; - } - } - if (remove) { - iterator.remove(); - } - } - - for (List<File> sameFiles : lists) { - Location location = null; - boolean sameNames = true; - String lastName = null; - for (File file : sameFiles) { - if (lastName != null && !lastName.equals(file.getName())) { - sameNames = false; - } - lastName = file.getName(); - // Chain locations together - Location linkedLocation = location; - location = Location.create(file); - location.setSecondary(linkedLocation); - } - - if (sameNames) { - StringBuilder sb = new StringBuilder(sameFiles.size() * 16); - for (File file : sameFiles) { - if (sb.length() > 0) { - sb.append(", "); //$NON-NLS-1$ - } - sb.append(file.getParentFile().getName()); - } - String message = String.format( - "The `%1$s` icon has identical contents in the following configuration folders: %2$s", - lastName, sb.toString()); - if (location != null) { - context.report(DUPLICATES_CONFIGURATIONS, location, message); - } - } else { - StringBuilder sb = new StringBuilder(sameFiles.size() * 16); - for (File file : sameFiles) { - if (sb.length() > 0) { - sb.append(", "); //$NON-NLS-1$ - } - sb.append(file.getName()); - } - String message = String.format( - "The following unrelated icon files have identical contents: %1$s", - sb.toString()); - context.report(DUPLICATES_NAMES, location, message); - } - } - } - } - } - - } - - // This method checks the given map from resource file to pixel dimensions for each - // such image and makes sure that the normalized dip sizes across all the densities - // are mostly the same. - private static void checkDipSizes(Context context, Map<File, Dimension> pixelSizes) { - // Partition up the files such that I can look at a series by name. This - // creates a map from filename (such as foo.png) to a list of files - // providing that icon in various folders: drawable-mdpi/foo.png, drawable-hdpi/foo.png - // etc. - Map<String, List<File>> nameToFiles = new HashMap<String, List<File>>(); - for (File file : pixelSizes.keySet()) { - String name = file.getName(); - List<File> list = nameToFiles.get(name); - if (list == null) { - list = new ArrayList<File>(); - nameToFiles.put(name, list); - } - list.add(file); - } - - ArrayList<String> names = new ArrayList<String>(nameToFiles.keySet()); - Collections.sort(names); - - // We have to partition the files further because it's possible for the project - // to have different configurations for an icon, such as this: - // drawable-large-hdpi/foo.png, drawable-large-mdpi/foo.png, - // drawable-hdpi/foo.png, drawable-mdpi/foo.png, - // drawable-hdpi-v11/foo.png and drawable-mdpi-v11/foo.png. - // In this case we don't want to compare across categories; we want to - // ensure that the drawable-large-{density} icons are consistent, - // that the drawable-{density}-v11 icons are consistent, and that - // the drawable-{density} icons are consistent. - - // Map from name to list of map from parent folder to list of files - Map<String, Map<String, List<File>>> configMap = - new HashMap<String, Map<String,List<File>>>(); - for (Map.Entry<String, List<File>> entry : nameToFiles.entrySet()) { - String name = entry.getKey(); - List<File> files = entry.getValue(); - for (File file : files) { - //noinspection ConstantConditions - String parentName = file.getParentFile().getName(); - // Strip out the density part - int index = -1; - for (String qualifier : DENSITY_QUALIFIERS) { - index = parentName.indexOf(qualifier); - if (index != -1) { - parentName = parentName.substring(0, index) - + parentName.substring(index + qualifier.length()); - break; - } - } - if (index == -1) { - // No relevant qualifier found in the parent directory name, - // e.g. it's just "drawable" or something like "drawable-nodpi". - continue; - } - - Map<String, List<File>> folderMap = configMap.get(name); - if (folderMap == null) { - folderMap = new HashMap<String,List<File>>(); - configMap.put(name, folderMap); - } - // Map from name to a map from parent folder to files - List<File> list = folderMap.get(parentName); - if (list == null) { - list = new ArrayList<File>(); - folderMap.put(parentName, list); - } - list.add(file); - } - } - - for (String name : names) { - //List<File> files = nameToFiles.get(name); - Map<String, List<File>> configurations = configMap.get(name); - if (configurations == null) { - // Nothing in this configuration: probably only found in drawable/ or - // drawable-nodpi etc directories. - continue; - } - - for (Map.Entry<String, List<File>> entry : configurations.entrySet()) { - List<File> files = entry.getValue(); - - // Ensure that all the dip sizes are *roughly* the same - Map<File, Dimension> dipSizes = new HashMap<File, Dimension>(); - int dipWidthSum = 0; // Incremental computation of average - int dipHeightSum = 0; // Incremental computation of average - int count = 0; - for (File file : files) { - //noinspection ConstantConditions - String folderName = file.getParentFile().getName(); - float factor = getMdpiScalingFactor(folderName); - if (factor > 0) { - Dimension size = pixelSizes.get(file); - if (size == null) { - continue; - } - Dimension dip = new Dimension( - Math.round(size.width / factor), - Math.round(size.height / factor)); - dipWidthSum += dip.width; - dipHeightSum += dip.height; - dipSizes.put(file, dip); - count++; - - String fileName = file.getName(); - Matcher matcher = DP_NAME_PATTERN.matcher(fileName); - if (matcher.matches()) { - String dpString = matcher.group(1); - int dp = Integer.parseInt(dpString); - // We're not sure whether the dp size refers to the width - // or the height, so check both. Allow a little bit of rounding - // slop. - if (Math.abs(dip.width - dp) > 2 || Math.abs(dip.height - dp) > 2) { - // Unicode 00D7 is the multiplication sign - String message = String.format("" - + "Suspicious file name `%1$s`: The implied %2$s `dp` " - + "size does not match the actual `dp` size " - + "(pixel size %3$d\u00D7%4$d in a `%5$s` folder " - + "computes to %6$d\u00D7%7$d `dp`)", - fileName, dpString, - size.width, size.height, - folderName, - dip.width, dip.height); - context.report(ICON_DIP_SIZE, Location.create(file), message); - } - } - } - } - if (count == 0) { - // Icons in drawable/ and drawable-nodpi/ - continue; - } - int meanWidth = dipWidthSum / count; - int meanHeight = dipHeightSum / count; - - // Compute standard deviation? - int squareWidthSum = 0; - int squareHeightSum = 0; - for (Dimension size : dipSizes.values()) { - squareWidthSum += (size.width - meanWidth) * (size.width - meanWidth); - squareHeightSum += (size.height - meanHeight) * (size.height - meanHeight); - } - double widthStdDev = Math.sqrt(squareWidthSum / count); - double heightStdDev = Math.sqrt(squareHeightSum / count); - - if (widthStdDev > meanWidth / 10 || heightStdDev > meanHeight) { - Location location = null; - StringBuilder sb = new StringBuilder(100); - - // Sort entries by decreasing dip size - List<Map.Entry<File, Dimension>> entries = - new ArrayList<Map.Entry<File,Dimension>>(); - for (Map.Entry<File, Dimension> entry2 : dipSizes.entrySet()) { - entries.add(entry2); - } - Collections.sort(entries, - new Comparator<Map.Entry<File, Dimension>>() { - @Override - public int compare(Entry<File, Dimension> e1, - Entry<File, Dimension> e2) { - Dimension d1 = e1.getValue(); - Dimension d2 = e2.getValue(); - if (d1.width != d2.width) { - return d2.width - d1.width; - } - - return d2.height - d1.height; - } - }); - for (Map.Entry<File, Dimension> entry2 : entries) { - if (sb.length() > 0) { - sb.append(", "); - } - File file = entry2.getKey(); - - // Chain locations together - Location linkedLocation = location; - location = Location.create(file); - location.setSecondary(linkedLocation); - Dimension dip = entry2.getValue(); - Dimension px = pixelSizes.get(file); - //noinspection ConstantConditions - String fileName = file.getParentFile().getName() + File.separator - + file.getName(); - sb.append(String.format("%1$s: %2$dx%3$d dp (%4$dx%5$d px)", - fileName, dip.width, dip.height, px.width, px.height)); - } - String message = String.format( - "The image `%1$s` varies significantly in its density-independent (dip) " + - "size across the various density versions: %2$s", - name, sb.toString()); - if (location != null) { - context.report(ICON_DIP_SIZE, location, message); - } - } - } - } - } - - private void checkDensities(Context context, File res, - Map<File, Set<String>> folderToNames, - Map<File, Set<String>> nonDpiFolderNames) { - // TODO: Is there a way to look at the manifest and figure out whether - // all densities are expected to be needed? - // Note: ldpi is probably not needed; it has very little usage - // (about 2%; http://developer.android.com/resources/dashboard/screens.html) - // TODO: Use the matrix to check out if we can eliminate densities based - // on the target screens? - - Set<String> definedDensities = new HashSet<String>(); - for (File f : folderToNames.keySet()) { - definedDensities.add(f.getName()); - } - - // Look for missing folders -- if you define say drawable-mdpi then you - // should also define -hdpi and -xhdpi. - if (context.isEnabled(ICON_MISSING_FOLDER)) { - List<String> missing = new ArrayList<String>(); - for (String density : getRequiredDensityFolders(context)) { - if (!definedDensities.contains(density)) { - missing.add(density); - } - } - if (!missing.isEmpty()) { - context.report( - ICON_MISSING_FOLDER, - Location.create(res), - String.format("Missing density variation folders in `%1$s`: %2$s", - context.getProject().getDisplayPath(res), - LintUtils.formatList(missing, -1))); - } - } - - if (context.isEnabled(ICON_NODPI)) { - Set<String> noDpiNames = new HashSet<String>(); - for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) { - if (isNoDpiFolder(entry.getKey())) { - noDpiNames.addAll(entry.getValue()); - } - } - if (!noDpiNames.isEmpty()) { - // Make sure that none of the nodpi names appear in a non-nodpi folder - Set<String> inBoth = new HashSet<String>(); - List<File> files = new ArrayList<File>(); - for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) { - File folder = entry.getKey(); - String folderName = folder.getName(); - if (!isNoDpiFolder(folder)) { - assert DENSITY_PATTERN.matcher(folderName).matches(); - Set<String> overlap = nameIntersection(noDpiNames, entry.getValue()); - inBoth.addAll(overlap); - for (String name : overlap) { - files.add(new File(folder, name)); - } - } - } - - if (!inBoth.isEmpty()) { - List<String> list = new ArrayList<String>(inBoth); - Collections.sort(list); - - // Chain locations together - Location location = chainLocations(files); - - context.report(ICON_NODPI, location, - String.format( - "The following images appear in both `-nodpi` and in a density folder: %1$s", - LintUtils.formatList(list, - context.getDriver().isAbbreviating() ? 10 : -1))); - } - } - } - - if (context.isEnabled(ICON_MIX_9PNG)) { - checkMixedNinePatches(context, folderToNames); - } - - if (context.isEnabled(ICON_XML_AND_PNG)) { - Map<File, Set<String>> folderMap = Maps.newHashMap(folderToNames); - folderMap.putAll(nonDpiFolderNames); - Set<String> xmlNames = Sets.newHashSetWithExpectedSize(100); - Set<String> bitmapNames = Sets.newHashSetWithExpectedSize(100); - - for (Map.Entry<File, Set<String>> entry : folderMap.entrySet()) { - Set<String> names = entry.getValue(); - for (String name : names) { - if (endsWith(name, DOT_XML)) { - xmlNames.add(name); - } else if (isDrawableFile(name)) { - bitmapNames.add(name); - } - } - } - if (!xmlNames.isEmpty() && !bitmapNames.isEmpty()) { - // Make sure that none of the nodpi names appear in a non-nodpi folder - Set<String> overlap = nameIntersection(xmlNames, bitmapNames); - if (!overlap.isEmpty()) { - Multimap<String, File> map = ArrayListMultimap.create(); - Set<String> bases = Sets.newHashSetWithExpectedSize(overlap.size()); - for (String name : overlap) { - bases.add(LintUtils.getBaseName(name)); - } - - for (String base : bases) { - for (Map.Entry<File, Set<String>> entry : folderMap.entrySet()) { - File folder = entry.getKey(); - for (String n : entry.getValue()) { - if (base.equals(LintUtils.getBaseName(n))) { - map.put(base, new File(folder, n)); - } - } - } - } - List<String> sorted = new ArrayList<String>(map.keySet()); - Collections.sort(sorted); - for (String name : sorted) { - List<File> lists = Lists.newArrayList(map.get(name)); - Location location = chainLocations(lists); - - List<String> fileNames = Lists.newArrayList(); - boolean seenXml = false; - boolean seenNonXml = false; - for (File f : lists) { - boolean isXml = endsWith(f.getPath(), DOT_XML); - if (isXml && !seenXml) { - fileNames.add(context.getProject().getDisplayPath(f)); - seenXml = true; - } else if (!isXml && !seenNonXml) { - fileNames.add(context.getProject().getDisplayPath(f)); - seenNonXml = true; - } - } - - context.report(ICON_XML_AND_PNG, location, - String.format( - "The following images appear both as density independent `.xml` files and as bitmap files: %1$s", - LintUtils.formatList(fileNames, - context.getDriver().isAbbreviating() ? 10 : -1))); - } - } - } - } - - if (context.isEnabled(ICON_DENSITIES)) { - // Look for folders missing some of the specific assets - Set<String> allNames = new HashSet<String>(); - for (Entry<File,Set<String>> entry : folderToNames.entrySet()) { - if (!isNoDpiFolder(entry.getKey())) { - Set<String> names = entry.getValue(); - allNames.addAll(names); - } - } - - for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) { - File file = entry.getKey(); - if (isNoDpiFolder(file)) { - continue; - } - Set<String> names = entry.getValue(); - if (names.size() != allNames.size()) { - List<String> delta = new ArrayList<String>(nameDifferences(allNames, names)); - if (delta.isEmpty()) { - continue; - } - Collections.sort(delta); - String foundIn = ""; - if (delta.size() == 1) { - // Produce list of where the icon is actually defined - List<String> defined = new ArrayList<String>(); - String name = delta.get(0); - for (Map.Entry<File, Set<String>> e : folderToNames.entrySet()) { - if (e.getValue().contains(name)) { - defined.add(e.getKey().getName()); - } - } - if (!defined.isEmpty()) { - foundIn = String.format(" (found in %1$s)", - LintUtils.formatList(defined, - context.getDriver().isAbbreviating() ? 5 : -1)); - } - } - - // Irrelevant folder? - String folder = file.getName(); - if (!getRequiredDensityFolders(context).contains(folder)) { - continue; - } - - context.report(ICON_DENSITIES, Location.create(file), - String.format( - "Missing the following drawables in `%1$s`: %2$s%3$s", - folder, - LintUtils.formatList(delta, - context.getDriver().isAbbreviating() ? 5 : -1), - foundIn)); - } - } - } - } - - private List<String> getRequiredDensityFolders(@NonNull Context context) { - if (mCachedRequiredDensities == null - || context.getProject() != mCachedDensitiesForProject) { - mCachedDensitiesForProject = context.getProject(); - mCachedRequiredDensities = Lists.newArrayListWithExpectedSize(10); - - List<String> applicableDensities = context.getProject().getApplicableDensities(); - if (applicableDensities != null) { - mCachedRequiredDensities.addAll(applicableDensities); - } else { - if (INCLUDE_LDPI) { - mCachedRequiredDensities.add(DRAWABLE_LDPI); - } - mCachedRequiredDensities.add(DRAWABLE_MDPI); - mCachedRequiredDensities.add(DRAWABLE_HDPI); - mCachedRequiredDensities.add(DRAWABLE_XHDPI); - mCachedRequiredDensities.add(DRAWABLE_XXHDPI); - mCachedRequiredDensities.add(DRAWABLE_XXXHDPI); - } - } - - return mCachedRequiredDensities; - } - - /** - * Adds in the resConfig values specified by the given flavor container, assuming - * it's in one of the relevant variantFlavors, into the given set - */ - private static void addResConfigsFromFlavor(@NonNull Set<String> relevantDensities, - @Nullable List<String> variantFlavors, - @NonNull ProductFlavorContainer container) { - ProductFlavor flavor = container.getProductFlavor(); - if (variantFlavors == null || variantFlavors.contains(flavor.getName())) { - if (!flavor.getResourceConfigurations().isEmpty()) { - for (String densityName : flavor.getResourceConfigurations()) { - Density density = Density.getEnum(densityName); - if (density != null && density.isRecommended() - && density != Density.NODPI && density != Density.ANYDPI) { - relevantDensities.add(densityName); - } - } - } - } - } - - /** - * Compute the difference in names between a and b. This is not just - * Sets.difference(a, b) because we want to make the comparisons <b>without - * file extensions</b> and return the result <b>with</b>.. - */ - private static Set<String> nameDifferences(Set<String> a, Set<String> b) { - Set<String> names1 = new HashSet<String>(a.size()); - for (String s : a) { - names1.add(LintUtils.getBaseName(s)); - } - Set<String> names2 = new HashSet<String>(b.size()); - for (String s : b) { - names2.add(LintUtils.getBaseName(s)); - } - - names1.removeAll(names2); - - if (!names1.isEmpty()) { - // Map filenames back to original filenames with extensions - Set<String> result = new HashSet<String>(names1.size()); - for (String s : a) { - if (names1.contains(LintUtils.getBaseName(s))) { - result.add(s); - } - } - for (String s : b) { - if (names1.contains(LintUtils.getBaseName(s))) { - result.add(s); - } - } - - return result; - } - - return Collections.emptySet(); - } - - /** - * Compute the intersection in names between a and b. This is not just - * Sets.intersection(a, b) because we want to make the comparisons <b>without - * file extensions</b> and return the result <b>with</b>. - */ - private static Set<String> nameIntersection(Set<String> a, Set<String> b) { - Set<String> names1 = new HashSet<String>(a.size()); - for (String s : a) { - names1.add(LintUtils.getBaseName(s)); - } - Set<String> names2 = new HashSet<String>(b.size()); - for (String s : b) { - names2.add(LintUtils.getBaseName(s)); - } - - names1.retainAll(names2); - - if (!names1.isEmpty()) { - // Map filenames back to original filenames with extensions - Set<String> result = new HashSet<String>(names1.size()); - for (String s : a) { - if (names1.contains(LintUtils.getBaseName(s))) { - result.add(s); - } - } - for (String s : b) { - if (names1.contains(LintUtils.getBaseName(s))) { - result.add(s); - } - } - - return result; - } - - return Collections.emptySet(); - } - - private static boolean isNoDpiFolder(File file) { - return file.getName().contains("-nodpi"); - } - - private Map<File, BufferedImage> mImageCache; - - @Nullable - private BufferedImage getImage(@Nullable File file) throws IOException { - if (file == null) { - return null; - } - if (mImageCache == null) { - mImageCache = Maps.newHashMap(); - } else { - BufferedImage image = mImageCache.get(file); - if (image != null) { - return image; - } - } - - BufferedImage image = ImageIO.read(file); - mImageCache.put(file, image); - - return image; - } - - private void checkDrawableDir(Context context, File folder, File[] files, - Map<File, Dimension> pixelSizes, Map<File, Long> fileSizes) { - if (folder.getName().equals(DRAWABLE_FOLDER) - && context.isEnabled(ICON_LOCATION) && - // If supporting older versions than Android 1.6, it's not an error - // to include bitmaps in drawable/ - context.getProject().getMinSdk() >= 4) { - for (File file : files) { - String name = file.getName(); - //noinspection StatementWithEmptyBody - if (name.endsWith(DOT_XML)) { - // pass - most common case, avoids checking other extensions - } else if (endsWith(name, DOT_PNG) - || endsWith(name, DOT_JPG) - || endsWith(name, DOT_JPEG) - || endsWith(name, DOT_GIF)) { - context.report(ICON_LOCATION, - Location.create(file), - String.format("Found bitmap drawable `res/drawable/%1$s` in " + - "densityless folder", - file.getName())); - } - } - } - - if (context.isEnabled(GIF_USAGE)) { - for (File file : files) { - String name = file.getName(); - if (endsWith(name, DOT_GIF)) { - context.report(GIF_USAGE, Location.create(file), - "Using the `.gif` format for bitmaps is discouraged"); - } - } - } - - if (context.isEnabled(ICON_EXTENSION)) { - for (File file : files) { - String path = file.getPath(); - if (isDrawableFile(path) && !endsWith(path, DOT_XML)) { - checkExtension(context, file); - } - } - } - - if (context.isEnabled(ICON_COLORS)) { - for (File file : files) { - String name = file.getName(); - - if (isDrawableFile(name) - && !endsWith(name, DOT_XML) - && !endsWith(name, DOT_9PNG)) { - String baseName = getBaseName(name); - boolean isActionBarIcon = isActionBarIcon(context, baseName, file); - if (isActionBarIcon || isNotificationIcon(baseName)) { - Dimension size = checkColor(context, file, isActionBarIcon); - - // Store dimension for size check if we went to the trouble of reading image - if (size != null && pixelSizes != null) { - pixelSizes.put(file, size); - } - } - } - } - } - - if (context.isEnabled(ICON_LAUNCHER_SHAPE)) { - // Look up launcher icon name - for (File file : files) { - String name = file.getName(); - if (isLauncherIcon(getBaseName(name)) - && !endsWith(name, DOT_XML) - && !endsWith(name, DOT_9PNG)) { - checkLauncherShape(context, file); - } - } - } - - // Check icon sizes - if (context.isEnabled(ICON_EXPECTED_SIZE)) { - checkExpectedSizes(context, folder, files); - } - - if (pixelSizes != null || fileSizes != null) { - for (File file : files) { - // TODO: Combine this check with the check for expected sizes such that - // I don't check file sizes twice! - String fileName = file.getName(); - - if (endsWith(fileName, DOT_PNG) || endsWith(fileName, DOT_JPG) - || endsWith(fileName, DOT_JPEG)) { - // Only scan .png files (except 9-patch png's) and jpg files for - // dip sizes. Duplicate checks can also be performed on ninepatch files. - if (pixelSizes != null && !endsWith(fileName, DOT_9PNG) - && !pixelSizes.containsKey(file)) { // already read by checkColor? - Dimension size = getSize(file); - pixelSizes.put(file, size); - } - if (fileSizes != null) { - fileSizes.put(file, file.length()); - } - } - } - } - - mImageCache = null; - } - - /** - * Check that launcher icons do not fill every pixel in the image - */ - private void checkLauncherShape(Context context, File file) { - try { - BufferedImage image = getImage(file); - if (image != null) { - // TODO: see if the shape is rectangular but inset from outer rectangle; if so - // that's probably not right either! - for (int y = 0, height = image.getHeight(); y < height; y++) { - for (int x = 0, width = image.getWidth(); x < width; x++) { - int rgb = image.getRGB(x, y); - if ((rgb & 0xFF000000) == 0) { - return; - } - } - } - - String message = "Launcher icons should not fill every pixel of their square " + - "region; see the design guide for details"; - context.report(ICON_LAUNCHER_SHAPE, Location.create(file), - message); - } - } catch (IOException e) { - // Pass: ignore files we can't read - } - } - - /** - * Check whether the icons in the file are okay. Also return the image size - * if known (for use by other checks) - */ - private Dimension checkColor(Context context, File file, boolean isActionBarIcon) { - int folderVersion = context.getDriver().getResourceFolderVersion(file); - if (isActionBarIcon) { - if (folderVersion != -1 && folderVersion < 11 - || !isAndroid30(context, folderVersion)) { - return null; - } - } else { - if (folderVersion != -1 && folderVersion < 9 - || !isAndroid23(context, folderVersion) - && !isAndroid30(context, folderVersion)) { - return null; - } - } - - // TODO: This only checks icons that are known to be using the Holo style. - // However, if the user has minSdk < 11 as well as targetSdk > 11, we should - // also check that they actually include a -v11 or -v14 folder with proper - // icons, since the below won't flag the older icons. - try { - BufferedImage image = getImage(file); - if (image != null) { - if (isActionBarIcon) { - checkPixels: - for (int y = 0, height = image.getHeight(); y < height; y++) { - for (int x = 0, width = image.getWidth(); x < width; x++) { - int rgb = image.getRGB(x, y); - if ((rgb & 0xFF000000) != 0) { // else: transparent - int r = (rgb & 0xFF0000) >>> 16; - int g = (rgb & 0x00FF00) >>> 8; - int b = (rgb & 0x0000FF); - if (r != g || r != b) { - String message = "Action Bar icons should use a single gray " - + "color (`#333333` for light themes (with 60%/30% " - + "opacity for enabled/disabled), and `#FFFFFF` with " - + "opacity 80%/30% for dark themes"; - context.report(ICON_COLORS, Location.create(file), - message); - break checkPixels; - } - } - } - } - } else { - if (folderVersion >= 11 || isAndroid30(context, folderVersion)) { - // Notification icons. Should be white as of API 14 - checkPixels: - for (int y = 0, height = image.getHeight(); y < height; y++) { - for (int x = 0, width = image.getWidth(); x < width; x++) { - int rgb = image.getRGB(x, y); - // If the pixel is not completely transparent, insist that - // its RGB channel must be white (with any alpha value) - if ((rgb & 0xFF000000) != 0 && (rgb & 0xFFFFFF) != 0xFFFFFF) { - int r = (rgb & 0xFF0000) >>> 16; - int g = (rgb & 0x00FF00) >>> 8; - int b = (rgb & 0x0000FF); - if (r == g && r == b) { - // If the pixel is not white, it might be because of - // anti-aliasing. In that case, at least one neighbor - // should be of a different color - if (x < width - 1 && rgb != image.getRGB(x + 1, y)) { - continue; - } - if (x > 0 && rgb != image.getRGB(x - 1, y)) { - continue; - } - if (y < height - 1 && rgb != image.getRGB(x, y + 1)) { - continue; - } - if (y > 0 && rgb != image.getRGB(x, y - 1)) { - continue; - } - } - - String message = "Notification icons must be entirely white"; - context.report(ICON_COLORS, Location.create(file), - message); - break checkPixels; - } - } - } - } else { - // As of API 9, should be gray. - checkPixels: - for (int y = 0, height = image.getHeight(); y < height; y++) { - for (int x = 0, width = image.getWidth(); x < width; x++) { - int rgb = image.getRGB(x, y); - if ((rgb & 0xFF000000) != 0) { // else: transparent - int r = (rgb & 0xFF0000) >>> 16; - int g = (rgb & 0x00FF00) >>> 8; - int b = (rgb & 0x0000FF); - if (r != g || r != b) { - String message = "Notification icons should not use " - + "colors"; - context.report(ICON_COLORS, Location.create(file), - message); - break checkPixels; - } - } - } - } - } - } - - return new Dimension(image.getWidth(), image.getHeight()); - } - } catch (IOException e) { - // Pass: ignore files we can't read - } - - return null; - } - - private static void checkExtension(Context context, File file) { - try { - ImageInputStream input = ImageIO.createImageInputStream(file); - if (input != null) { - try { - Iterator<ImageReader> readers = ImageIO.getImageReaders(input); - while (readers.hasNext()) { - ImageReader reader = readers.next(); - try { - reader.setInput(input); - - // Check file extension - String formatName = reader.getFormatName(); - if (formatName != null && !formatName.isEmpty()) { - String path = file.getPath(); - int index = path.lastIndexOf('.'); - String extension = path.substring(index+1).toLowerCase(Locale.US); - - if (!formatName.equalsIgnoreCase(extension)) { - if (endsWith(path, DOT_JPG) - && formatName.equals("JPEG")) { //$NON-NLS-1$ - return; - } - String message = String.format( - "Misleading file extension; named `.%1$s` but the " + - "file format is `%2$s`", extension, formatName); - Location location = Location.create(file); - context.report(ICON_EXTENSION, location, message); - } - break; - } - } finally { - reader.dispose(); - } - } - } finally { - input.close(); - } - } - } catch (IOException e) { - // Pass -- we can't handle all image types, warn about those we can - } - } - - // Like LintUtils.getBaseName, but for files like .svn it returns "" rather than ".svn" - private static String getBaseName(String name) { - String baseName = name; - int index = baseName.indexOf('.'); - if (index != -1) { - baseName = baseName.substring(0, index); - } - - return baseName; - } - - private static void checkMixedNinePatches(Context context, - Map<File, Set<String>> folderToNames) { - Set<String> conflictSet = null; - - for (Entry<File, Set<String>> entry : folderToNames.entrySet()) { - Set<String> baseNames = new HashSet<String>(); - Set<String> names = entry.getValue(); - for (String name : names) { - assert isDrawableFile(name) : name; - String base = getBaseName(name); - if (baseNames.contains(base)) { - String ninepatch = base + DOT_9PNG; - String png = base + DOT_PNG; - if (names.contains(ninepatch) && names.contains(png)) { - if (conflictSet == null) { - conflictSet = Sets.newHashSet(); - } - conflictSet.add(base); - } - } else { - baseNames.add(base); - } - } - } - - if (conflictSet == null || conflictSet.isEmpty()) { - return; - } - - Map<String, List<File>> conflicts = null; - for (Entry<File, Set<String>> entry : folderToNames.entrySet()) { - File dir = entry.getKey(); - Set<String> names = entry.getValue(); - for (String name : names) { - assert isDrawableFile(name) : name; - String base = getBaseName(name); - if (conflictSet.contains(base)) { - if (conflicts == null) { - conflicts = Maps.newHashMap(); - } - List<File> files = conflicts.get(base); - if (files == null) { - files = Lists.newArrayList(); - conflicts.put(base, files); - } - files.add(new File(dir, name)); - } - } - } - - assert conflicts != null && !conflicts.isEmpty() : conflictSet; - List<String> names = new ArrayList<String>(conflicts.keySet()); - Collections.sort(names); - for (String name : names) { - List<File> files = conflicts.get(name); - assert files != null : name; - Location location = chainLocations(files); - - String message = String.format( - "The files `%1$s.png` and `%1$s.9.png` clash; both " - + "will map to `@drawable/%1$s`", name); - context.report(ICON_MIX_9PNG, location, message); - } - } - - private static Location chainLocations(List<File> files) { - // Chain locations together - Collections.sort(files); - Location location = null; - for (File file : files) { - Location linkedLocation = location; - location = Location.create(file); - location.setSecondary(linkedLocation); - } - return location; - } - - private void checkExpectedSizes(Context context, File folder, File[] files) { - if (files == null || files.length == 0) { - return; - } - - String folderName = folder.getName(); - int folderVersion = context.getDriver().getResourceFolderVersion(files[0]); - - for (File file : files) { - String name = file.getName(); - - // TODO: Look up exact app icon from the manifest rather than simply relying on - // the naming conventions described here: - // http://developer.android.com/guide/practices/ui_guidelines/icon_design.html#design-tips - // See if we can figure out other types of icons from usage too. - - String baseName = getBaseName(name); - - if (isLauncherIcon(baseName)) { - // Launcher icons - checkSize(context, folderName, file, 48, 48, true /*exact*/); - } else if (isActionBarIcon(baseName)) { - checkSize(context, folderName, file, 32, 32, true /*exact*/); - } else if (name.startsWith("ic_dialog_")) { //$NON-NLS-1$ - // Dialog - checkSize(context, folderName, file, 32, 32, true /*exact*/); - } else if (name.startsWith("ic_tab_")) { //$NON-NLS-1$ - // Tab icons - checkSize(context, folderName, file, 32, 32, true /*exact*/); - } else if (isNotificationIcon(baseName)) { - // Notification icons - if (isAndroid30(context, folderVersion)) { - checkSize(context, folderName, file, 24, 24, true /*exact*/); - } else if (isAndroid23(context, folderVersion)) { - checkSize(context, folderName, file, 16, 25, false /*exact*/); - } else { - // Android 2.2 or earlier - // TODO: Should this be done for each folder size? - checkSize(context, folderName, file, 25, 25, true /*exact*/); - } - } else if (name.startsWith("ic_menu_")) { //$NON-NLS-1$ - if (isAndroid30(context, folderVersion)) { - // Menu icons (<=2.3 only: Replaced by action bar icons (ic_action_ in 3.0). - // However the table halfway down the page on - // http://developer.android.com/guide/practices/ui_guidelines/icon_design.html - // and the README in the icon template download says that convention is ic_menu - checkSize(context, folderName, file, 32, 32, true); - } else if (isAndroid23(context, folderVersion)) { - // The icon should be 32x32 inside the transparent image; should - // we check that this is mostly the case (a few pixels are allowed to - // overlap for anti-aliasing etc) - checkSize(context, folderName, file, 48, 48, true /*exact*/); - } else { - // Android 2.2 or earlier - // TODO: Should this be done for each folder size? - checkSize(context, folderName, file, 48, 48, true /*exact*/); - } - } - // TODO: ListView icons? - } - } - - /** - * Is this drawable folder for an Android 3.0 drawable? This will be the - * case if it specifies -v11+, or if the minimum SDK version declared in the - * manifest is at least 11. - */ - private static boolean isAndroid30(Context context, int folderVersion) { - return folderVersion >= 11 || context.getMainProject().getMinSdk() >= 11; - } - - /** - * Is this drawable folder for an Android 2.3 drawable? This will be the - * case if it specifies -v9 or -v10, or if the minimum SDK version declared in the - * manifest is 9 or 10 (and it does not specify some higher version like -v11 - */ - private static boolean isAndroid23(Context context, int folderVersion) { - if (isAndroid30(context, folderVersion)) { - return false; - } - - if (folderVersion == 9 || folderVersion == 10) { - return true; - } - - int minSdk = context.getMainProject().getMinSdk(); - - return minSdk == 9 || minSdk == 10; - } - - private static float getMdpiScalingFactor(String folderName) { - // Can't do startsWith(DRAWABLE_MDPI) because the folder could - // be something like "drawable-sw600dp-mdpi". - if (folderName.contains("-mdpi")) { //$NON-NLS-1$ - return 1.0f; - } else if (folderName.contains("-hdpi")) { //$NON-NLS-1$ - return 1.5f; - } else if (folderName.contains("-xhdpi")) { //$NON-NLS-1$ - return 2.0f; - } else if (folderName.contains("-xxhdpi")) { //$NON-NLS-1$ - return 3.0f; - } else if (folderName.contains("-xxxhdpi")) { //$NON-NLS-1$ - return 4.0f; - } else if (folderName.contains("-ldpi")) { //$NON-NLS-1$ - return 0.75f; - } else { - return 0f; - } - } - - private static void checkSize(Context context, String folderName, File file, - int mdpiWidth, int mdpiHeight, boolean exactMatch) { - String fileName = file.getName(); - // Only scan .png files (except 9-patch png's) and jpg files - if (!((endsWith(fileName, DOT_PNG) && !endsWith(fileName, DOT_9PNG)) || - endsWith(fileName, DOT_JPG) || endsWith(fileName, DOT_JPEG))) { - return; - } - - int width; - int height; - // Use 3:4:6:8 scaling ratio to look up the other expected sizes - if (folderName.startsWith(DRAWABLE_MDPI)) { - width = mdpiWidth; - height = mdpiHeight; - } else if (folderName.startsWith(DRAWABLE_HDPI)) { - // Perform math using floating point; if we just do - // width = mdpiWidth * 3 / 2; - // then for mdpiWidth = 25 (as in notification icons on pre-GB) we end up - // with width = 37, instead of 38 (with floating point rounding we get 37.5 = 38) - width = Math.round(mdpiWidth * 3.f / 2); - height = Math.round(mdpiHeight * 3f / 2); - } else if (folderName.startsWith(DRAWABLE_XHDPI)) { - width = mdpiWidth * 2; - height = mdpiHeight * 2; - } else if (folderName.startsWith(DRAWABLE_XXHDPI)) { - width = mdpiWidth * 3; - height = mdpiWidth * 3; - } else if (folderName.startsWith(DRAWABLE_LDPI)) { - width = Math.round(mdpiWidth * 3f / 4); - height = Math.round(mdpiHeight * 3f / 4); - } else { - return; - } - - Dimension size = getSize(file); - if (size != null) { - if (exactMatch && (size.width != width || size.height != height)) { - context.report( - ICON_EXPECTED_SIZE, - Location.create(file), - String.format( - "Incorrect icon size for `%1$s`: expected %2$dx%3$d, but was %4$dx%5$d", - folderName + File.separator + file.getName(), - width, height, size.width, size.height)); - } else if (!exactMatch && (size.width > width || size.height > height)) { - context.report( - ICON_EXPECTED_SIZE, - Location.create(file), - String.format( - "Incorrect icon size for `%1$s`: icon size should be at most %2$dx%3$d, but was %4$dx%5$d", - folderName + File.separator + file.getName(), - width, height, size.width, size.height)); - } - } - } - - private static Dimension getSize(File file) { - try { - ImageInputStream input = ImageIO.createImageInputStream(file); - if (input != null) { - try { - Iterator<ImageReader> readers = ImageIO.getImageReaders(input); - if (readers.hasNext()) { - ImageReader reader = readers.next(); - try { - reader.setInput(input); - return new Dimension(reader.getWidth(0), reader.getHeight(0)); - } finally { - reader.dispose(); - } - } - } finally { - input.close(); - } - } - - // Fallback: read the image using the normal means - BufferedImage image = ImageIO.read(file); - if (image != null) { - return new Dimension(image.getWidth(), image.getHeight()); - } else { - return null; - } - } catch (IOException e) { - // Pass -- we can't handle all image types, warn about those we can - return null; - } - } - - - private Set<String> mActionBarIcons; - private Set<String> mNotificationIcons; - private Set<String> mLauncherIcons; - private Multimap<String, String> mMenuToIcons; - - private boolean isLauncherIcon(String name) { - assert name.indexOf('.') == -1 : name; // Should supply base name - - // Naming convention - //noinspection SimplifiableIfStatement - if (name.startsWith("ic_launcher")) { //$NON-NLS-1$ - return true; - } - return mLauncherIcons != null && mLauncherIcons.contains(name); - } - - private boolean isNotificationIcon(String name) { - assert name.indexOf('.') == -1; // Should supply base name - - // Naming convention - //noinspection SimplifiableIfStatement - if (name.startsWith("ic_stat_")) { //$NON-NLS-1$ - return true; - } - - return mNotificationIcons != null && mNotificationIcons.contains(name); - } - - private boolean isActionBarIcon(String name) { - assert name.indexOf('.') == -1; // Should supply base name - - // Naming convention - //noinspection SimplifiableIfStatement - if (name.startsWith("ic_action_")) { //$NON-NLS-1$ - return true; - } - - // Naming convention - - return mActionBarIcons != null && mActionBarIcons.contains(name); - } - - private boolean isActionBarIcon(Context context, String name, File file) { - if (isActionBarIcon(name)) { - return true; - } - - // As of Android 3.0 ic_menu_ are action icons - //noinspection SimplifiableIfStatement,RedundantIfStatement - if (file != null && name.startsWith("ic_menu_") //$NON-NLS-1$ - && isAndroid30(context, context.getDriver().getResourceFolderVersion(file))) { - // Naming convention - return true; - } - - return false; - } - - // XML detector: Skim manifest and menu files - - @Override - public boolean appliesTo(@NonNull Context context, @NonNull File file) { - return file.getName().equals(ANDROID_MANIFEST_XML); - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == ResourceFolderType.MENU; - } - - @Override - public Collection<String> getApplicableElements() { - return Arrays.asList( - // Manifest - TAG_APPLICATION, - TAG_ACTIVITY, - TAG_SERVICE, - TAG_PROVIDER, - TAG_RECEIVER, - - // Menu - TAG_ITEM - ); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - String icon = element.getAttributeNS(ANDROID_URI, ATTR_ICON); - if (icon != null && icon.startsWith(DRAWABLE_PREFIX)) { - icon = icon.substring(DRAWABLE_PREFIX.length()); - - String tagName = element.getTagName(); - if (tagName.equals(TAG_ITEM)) { - if (mMenuToIcons == null) { - mMenuToIcons = ArrayListMultimap.create(); - } - String menu = getBaseName(context.file.getName()); - mMenuToIcons.put(menu, icon); - } else { - // Manifest tags: launcher icons - if (mLauncherIcons == null) { - mLauncherIcons = Sets.newHashSet(); - } - mLauncherIcons.add(icon); - } - } - } - - // ---- Implements UastScanner ---- - - private static final String NOTIFICATION_CLASS = "android.app.Notification"; - private static final String NOTIFICATION_BUILDER_CLASS = "android.app.Notification.Builder"; - private static final String NOTIFICATION_COMPAT_BUILDER_CLASS = - "android.support.v4.app.NotificationCompat.Builder"; - private static final String SET_SMALL_ICON = "setSmallIcon"; - private static final String ON_CREATE_OPTIONS_MENU = "onCreateOptionsMenu"; - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(2); - types.add(UCallExpression.class); - types.add(UMethod.class); - return types; - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new NotificationFinder(context); - } - - private final class NotificationFinder extends AbstractUastVisitor { - private final JavaContext mContext; - - private NotificationFinder(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitMethod(UMethod method) { - if (ON_CREATE_OPTIONS_MENU.equals(method.getName())) { - // Gather any R.menu references found in this method - method.accept(new MenuFinder()); - } - return super.visitMethod(method); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (UastExpressionUtils.isConstructorCall(node)) { - visitConstructorCall(node); - } - return super.visitCallExpression(node); - } - - private void visitConstructorCall(UCallExpression node) { - UReferenceExpression classReference = node.getClassReference(); - if (classReference == null) { - return; - } - PsiElement resolved = classReference.resolve(); - if (!(resolved instanceof PsiClass)) { - return; - } - String typeName = ((PsiClass) resolved).getQualifiedName(); - if (NOTIFICATION_CLASS.equals(typeName)) { - List<UExpression> args = node.getValueArguments(); - if (args.size() == 3) { - if (args.get(0) instanceof UReferenceExpression && handleSelect(args.get(0))) { - return; - } - - ResourceUrl url = ResourceEvaluator.getResource(mContext, args.get(0)); - if (url != null - && (url.type == ResourceType.DRAWABLE - || url.type == ResourceType.COLOR - || url.type == ResourceType.MIPMAP)) { - if (mNotificationIcons == null) { - mNotificationIcons = Sets.newHashSet(); - } - mNotificationIcons.add(url.name); - } - } - } else if (NOTIFICATION_BUILDER_CLASS.equals(typeName) - || NOTIFICATION_COMPAT_BUILDER_CLASS.equals(typeName)) { - UMethod method = UastUtils.getParentOfType(node, UMethod.class, true); - if (method != null) { - SetIconFinder finder = new SetIconFinder(); - method.accept(finder); - } - } - } - } - - private boolean handleSelect(UElement select) { - ResourceUrl url = ResourceEvaluator.getResourceConstant(select); - if (url != null && url.type == ResourceType.DRAWABLE && !url.framework) { - if (mNotificationIcons == null) { - mNotificationIcons = Sets.newHashSet(); - } - mNotificationIcons.add(url.name); - - return true; - } - - return false; - } - - private final class SetIconFinder extends AbstractUastVisitor { - - @Override - public boolean visitCallExpression(UCallExpression expression) { - if (UastExpressionUtils.isMethodCall(expression)) { - if (SET_SMALL_ICON.equals(expression.getMethodName())) { - List<UExpression> arguments = expression.getValueArguments(); - if (arguments.size() == 1 && arguments.get(0) instanceof UReferenceExpression) { - handleSelect(arguments.get(0)); - } - } - } - return super.visitCallExpression(expression); - } - - @Override - public boolean visitClass(UClass node) { - if (node instanceof UAnonymousClass) { - return true; - } - return super.visitClass(node); - } - } - - private final class MenuFinder extends AbstractUastVisitor { - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - ResourceUrl url = ResourceEvaluator.getResourceConstant(node); - if (url != null && url.type == ResourceType.MENU && !url.framework) { - // Reclassify icons in the given menu as action bar icons - if (mMenuToIcons != null) { - Collection<String> icons = mMenuToIcons.get(url.name); - if (icons != null) { - if (mActionBarIcons == null) { - mActionBarIcons = Sets.newHashSet(); - } - mActionBarIcons.addAll(icons); - } - } - } - - return super.visitSimpleNameReferenceExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaPerformanceDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaPerformanceDetector.java deleted file mode 100644 index b9581cb..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaPerformanceDetector.java +++ /dev/null
@@ -1,527 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.*; -import com.google.common.collect.Sets; -import com.google.common.collect.Sets.SetView; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiType; -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; - -import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT; -import static com.android.tools.klint.client.api.JavaParser.*; -import static com.android.tools.klint.detector.api.LintUtils.skipParentheses; - -/** - * Looks for performance issues in Java files, such as memory allocations during - * drawing operations and using HashMap instead of SparseArray. - */ -public class JavaPerformanceDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - JavaPerformanceDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Allocating objects during a paint method */ - public static final Issue PAINT_ALLOC = Issue.create( - "DrawAllocation", //$NON-NLS-1$ - "Memory allocations within drawing code", - - "You should avoid allocating objects during a drawing or layout operation. These " + - "are called frequently, so a smooth UI can be interrupted by garbage collection " + - "pauses caused by the object allocations.\n" + - "\n" + - "The way this is generally handled is to allocate the needed objects up front " + - "and to reuse them for each drawing operation.\n" + - "\n" + - "Some methods allocate memory on your behalf (such as `Bitmap.create`), and these " + - "should be handled in the same way.", - - Category.PERFORMANCE, - 9, - Severity.WARNING, - IMPLEMENTATION); - - /** Using HashMaps where SparseArray would be better */ - public static final Issue USE_SPARSE_ARRAY = Issue.create( - "UseSparseArrays", //$NON-NLS-1$ - "HashMap can be replaced with SparseArray", - - "For maps where the keys are of type integer, it's typically more efficient to " + - "use the Android `SparseArray` API. This check identifies scenarios where you might " + - "want to consider using `SparseArray` instead of `HashMap` for better performance.\n" + - "\n" + - "This is *particularly* useful when the value types are primitives like ints, " + - "where you can use `SparseIntArray` and avoid auto-boxing the values from `int` to " + - "`Integer`.\n" + - "\n" + - "If you need to construct a `HashMap` because you need to call an API outside of " + - "your control which requires a `Map`, you can suppress this warning using for " + - "example the `@SuppressLint` annotation.", - - Category.PERFORMANCE, - 4, - Severity.WARNING, - IMPLEMENTATION); - - /** Using {@code new Integer()} instead of the more efficient {@code Integer.valueOf} */ - public static final Issue USE_VALUE_OF = Issue.create( - "UseValueOf", //$NON-NLS-1$ - "Should use `valueOf` instead of `new`", - - "You should not call the constructor for wrapper classes directly, such as" + - "`new Integer(42)`. Instead, call the `valueOf` factory method, such as " + - "`Integer.valueOf(42)`. This will typically use less memory because common integers " + - "such as 0 and 1 will share a single instance.", - - Category.PERFORMANCE, - 4, - Severity.WARNING, - IMPLEMENTATION); - - static final String ON_MEASURE = "onMeasure"; - static final String ON_DRAW = "onDraw"; - static final String ON_LAYOUT = "onLayout"; - private static final String LAYOUT = "layout"; - private static final String HASH_MAP = "java.util.HashMap"; - private static final String SPARSE_ARRAY = "android.util.SparseArray"; - public static final String CLASS_CANVAS = "android.graphics.Canvas"; - - /** Constructs a new {@link JavaPerformanceDetector} check */ - public JavaPerformanceDetector() { - } - - // ---- Implements UastScanner ---- - - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(3); - types.add(UCallExpression.class); - types.add(UMethod.class); - return types; - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new PerformanceVisitor(context); - } - - private static class PerformanceVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - private final boolean mCheckMaps; - private final boolean mCheckAllocations; - private final boolean mCheckValueOf; - /** Whether allocations should be "flagged" in the current method */ - private boolean mFlagAllocations; - - public PerformanceVisitor(JavaContext context) { - mContext = context; - - mCheckAllocations = context.isEnabled(PAINT_ALLOC); - mCheckMaps = context.isEnabled(USE_SPARSE_ARRAY); - mCheckValueOf = context.isEnabled(USE_VALUE_OF); - } - - @Override - public boolean visitMethod(UMethod node) { - mFlagAllocations = isBlockedAllocationMethod(node); - return super.visitMethod(node); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (UastExpressionUtils.isConstructorCall(node)) { - visitConstructorCallExpression(node); - } else if (UastExpressionUtils.isMethodCall(node)) { - visitMethodCallExpression(node); - } - return super.visitCallExpression(node); - } - - private void visitConstructorCallExpression(UCallExpression node) { - String typeName = null; - UReferenceExpression classReference = node.getClassReference(); - if (mCheckMaps || mCheckValueOf) { - if (classReference != null) { - typeName = UastUtils.getQualifiedName(classReference); - } - } - - if (mCheckMaps) { - // TODO: Should we handle factory method constructions of HashMaps as well, - // e.g. via Guava? This is a bit trickier since we need to infer the type - // arguments from the calling context. - if (HASH_MAP.equals(typeName)) { - checkHashMap(node); - } else if (SPARSE_ARRAY.equals(typeName)) { - checkSparseArray(node); - } - } - - if (mCheckValueOf) { - if (typeName != null - && (typeName.equals(TYPE_INTEGER_WRAPPER) - || typeName.equals(TYPE_BOOLEAN_WRAPPER) - || typeName.equals(TYPE_FLOAT_WRAPPER) - || typeName.equals(TYPE_CHARACTER_WRAPPER) - || typeName.equals(TYPE_LONG_WRAPPER) - || typeName.equals(TYPE_DOUBLE_WRAPPER) - || typeName.equals(TYPE_BYTE_WRAPPER)) - //&& node.astTypeReference().astParts().size() == 1 - && node.getValueArgumentCount() == 1) { - String argument = node.getValueArguments().get(0).asSourceString(); - mContext.report(USE_VALUE_OF, node, mContext.getUastLocation(node), getUseValueOfErrorMessage( - typeName, argument)); - } - } - - if (mFlagAllocations - && !(skipParentheses(node.getUastParent()) instanceof UThrowExpression) - && mCheckAllocations) { - // Make sure we're still inside the method declaration that marked - // mInDraw as true, in case we've left it and we're in a static - // block or something: - PsiMethod method = UastUtils.getParentOfType(node, UMethod.class); - if (method != null && isBlockedAllocationMethod(method) - && !isLazilyInitialized(node)) { - reportAllocation(node); - } - } - } - - private void reportAllocation(UElement node) { - mContext.report(PAINT_ALLOC, node, mContext.getUastLocation(node), - "Avoid object allocations during draw/layout operations (preallocate and " + - "reuse instead)"); - } - - private void visitMethodCallExpression(UCallExpression node) { - if (!mFlagAllocations) { - return; - } - UExpression receiver = node.getReceiver(); - if (receiver == null) { - return; - } - - String functionName = node.getMethodName(); - if (functionName == null) { - return; - } - - // Look for forbidden methods - if (functionName.equals("createBitmap") //$NON-NLS-1$ - || functionName.equals("createScaledBitmap")) { //$NON-NLS-1$ - PsiMethod method = node.resolve(); - if (method != null && JavaEvaluator.isMemberInClass(method, - "android.graphics.Bitmap") && !isLazilyInitialized(node)) { - reportAllocation(node); - } - } else if (functionName.startsWith("decode")) { //$NON-NLS-1$ - // decodeFile, decodeByteArray, ... - PsiMethod method = node.resolve(); - if (method != null && JavaEvaluator.isMemberInClass(method, - "android.graphics.BitmapFactory") && !isLazilyInitialized(node)) { - reportAllocation(node); - } - } else if (functionName.equals("getClipBounds")) { //$NON-NLS-1$ - if (node.getValueArguments().isEmpty()) { - mContext.report(PAINT_ALLOC, node, mContext.getUastLocation(node), - "Avoid object allocations during draw operations: Use " + - "`Canvas.getClipBounds(Rect)` instead of `Canvas.getClipBounds()` " + - "which allocates a temporary `Rect`"); - } - } - } - - /** - * Check whether the given invocation is done as a lazy initialization, - * e.g. {@code if (foo == null) foo = new Foo();}. - * <p> - * This tries to also handle the scenario where the check is on some - * <b>other</b> variable - e.g. - * <pre> - * if (foo == null) { - * foo == init1(); - * bar = new Bar(); - * } - * </pre> - * or - * <pre> - * if (!initialized) { - * initialized = true; - * bar = new Bar(); - * } - * </pre> - */ - private static boolean isLazilyInitialized(UElement node) { - UElement curr = node.getUastParent(); - while (curr != null) { - if (curr instanceof UMethod) { - return false; - } else if (curr instanceof UIfExpression) { - UIfExpression ifNode = (UIfExpression) curr; - // See if the if block represents a lazy initialization: - // compute all variable names seen in the condition - // (e.g. for "if (foo == null || bar != foo)" the result is "foo,bar"), - // and then compute all variables assigned to in the if body, - // and if there is an overlap, we'll consider the whole if block - // guarded (so lazily initialized and an allocation we won't complain - // about.) - List<String> assignments = new ArrayList<String>(); - AssignmentTracker visitor = new AssignmentTracker(assignments); - if (ifNode.getThenExpression() != null) { - ifNode.getThenExpression().accept(visitor); - } - if (!assignments.isEmpty()) { - List<String> references = new ArrayList<String>(); - addReferencedVariables(references, ifNode.getCondition()); - if (!references.isEmpty()) { - SetView<String> intersection = Sets.intersection( - new HashSet<String>(assignments), - new HashSet<String>(references)); - return !intersection.isEmpty(); - } - } - return false; - - } - curr = curr.getUastParent(); - } - - return false; - } - - /** Adds any variables referenced in the given expression into the given list */ - private static void addReferencedVariables( - @NonNull Collection<String> variables, - @Nullable UExpression expression) { - if (expression instanceof UBinaryExpression) { - UBinaryExpression binary = (UBinaryExpression) expression; - addReferencedVariables(variables, binary.getLeftOperand()); - addReferencedVariables(variables, binary.getRightOperand()); - } else if (expression instanceof UPrefixExpression) { - UPrefixExpression unary = (UPrefixExpression) expression; - addReferencedVariables(variables, unary.getOperand()); - } else if (expression instanceof UParenthesizedExpression) { - UParenthesizedExpression exp = (UParenthesizedExpression) expression; - addReferencedVariables(variables, exp.getExpression()); - } else if (expression instanceof USimpleNameReferenceExpression) { - USimpleNameReferenceExpression reference = (USimpleNameReferenceExpression) expression; - variables.add(reference.getIdentifier()); - } else if (expression instanceof UQualifiedReferenceExpression) { - UQualifiedReferenceExpression ref = (UQualifiedReferenceExpression) expression; - UExpression receiver = ref.getReceiver(); - UExpression selector = ref.getSelector(); - if (receiver instanceof UThisExpression || receiver instanceof USuperExpression) { - String identifier = (selector instanceof USimpleNameReferenceExpression) - ? ((USimpleNameReferenceExpression) selector).getIdentifier() - : null; - if (identifier != null) { - variables.add(identifier); - } - } - } - } - - /** - * Returns whether the given method declaration represents a method - * where allocating objects is not allowed for performance reasons - */ - private boolean isBlockedAllocationMethod( - @NonNull PsiMethod node) { - JavaEvaluator evaluator = mContext.getEvaluator(); - return isOnDrawMethod(evaluator, node) - || isOnMeasureMethod(evaluator, node) - || isOnLayoutMethod(evaluator, node) - || isLayoutMethod(evaluator, node); - } - - /** - * Returns true if this method looks like it's overriding android.view.View's - * {@code protected void onDraw(Canvas canvas)} - */ - private static boolean isOnDrawMethod( - @NonNull JavaEvaluator evaluator, - @NonNull PsiMethod node) { - return ON_DRAW.equals(node.getName()) && evaluator.parametersMatch(node, CLASS_CANVAS); - } - - /** - * Returns true if this method looks like it's overriding - * android.view.View's - * {@code protected void onLayout(boolean changed, int left, int top, - * int right, int bottom)} - */ - private static boolean isOnLayoutMethod( - @NonNull JavaEvaluator evaluator, - @NonNull PsiMethod node) { - return ON_LAYOUT.equals(node.getName()) && evaluator.parametersMatch(node, - TYPE_BOOLEAN, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT); - } - - /** - * Returns true if this method looks like it's overriding android.view.View's - * {@code protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)} - */ - private static boolean isOnMeasureMethod( - @NonNull JavaEvaluator evaluator, - @NonNull PsiMethod node) { - return ON_MEASURE.equals(node.getName()) && evaluator.parametersMatch(node, - TYPE_INT, TYPE_INT); - } - - /** - * Returns true if this method looks like it's overriding android.view.View's - * {@code public void layout(int l, int t, int r, int b)} - */ - private static boolean isLayoutMethod( - @NonNull JavaEvaluator evaluator, - @NonNull PsiMethod node) { - return LAYOUT.equals(node.getName()) && evaluator.parametersMatch(node, - TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT); - } - - /** - * Checks whether the given constructor call and type reference refers - * to a HashMap constructor call that is eligible for replacement by a - * SparseArray call instead - */ - private void checkHashMap(@NonNull UCallExpression node) { - List<PsiType> types = node.getTypeArguments(); - if (types.size() == 2) { - PsiType first = types.get(0); - String typeName = first.getCanonicalText(); - int minSdk = mContext.getMainProject().getMinSdk(); - if (TYPE_INTEGER_WRAPPER.equals(typeName) || TYPE_BYTE_WRAPPER.equals(typeName)) { - String valueType = types.get(1).getCanonicalText(); - if (valueType.equals(TYPE_INTEGER_WRAPPER)) { - mContext.report(USE_SPARSE_ARRAY, node, mContext.getUastLocation(node), - "Use new `SparseIntArray(...)` instead for better performance"); - } else if (valueType.equals(TYPE_LONG_WRAPPER) && minSdk >= 18) { - mContext.report(USE_SPARSE_ARRAY, node, mContext.getUastLocation(node), - "Use `new SparseLongArray(...)` instead for better performance"); - } else if (valueType.equals(TYPE_BOOLEAN_WRAPPER)) { - mContext.report(USE_SPARSE_ARRAY, node, mContext.getUastLocation(node), - "Use `new SparseBooleanArray(...)` instead for better performance"); - } else { - mContext.report(USE_SPARSE_ARRAY, node, mContext.getUastLocation(node), - String.format( - "Use `new SparseArray<%1$s>(...)` instead for better performance", - valueType.substring(valueType.lastIndexOf('.') + 1))); - } - } else if (TYPE_LONG_WRAPPER.equals(typeName) && (minSdk >= 16 || - Boolean.TRUE == mContext.getMainProject().dependsOn( - SUPPORT_LIB_ARTIFACT))) { - boolean useBuiltin = minSdk >= 16; - String message = useBuiltin ? - "Use `new LongSparseArray(...)` instead for better performance" : - "Use `new android.support.v4.util.LongSparseArray(...)` instead for better performance"; - mContext.report(USE_SPARSE_ARRAY, node, mContext.getUastLocation(node), - message); - } - } - } - - private void checkSparseArray(@NonNull UCallExpression node) { - List<PsiType> types = node.getTypeArguments(); - if (types.size() == 1) { - String valueType = types.get(0).getCanonicalText(); - if (valueType.equals(TYPE_INTEGER_WRAPPER)) { - mContext.report(USE_SPARSE_ARRAY, node, mContext.getUastLocation(node), - "Use `new SparseIntArray(...)` instead for better performance"); - } else if (valueType.equals(TYPE_BOOLEAN_WRAPPER)) { - mContext.report(USE_SPARSE_ARRAY, node, mContext.getUastLocation(node), - "Use `new SparseBooleanArray(...)` instead for better performance"); - } - } - } - } - - private static String getUseValueOfErrorMessage(String typeName, String argument) { - // Keep in sync with {@link #getReplacedType} below - return String.format("Use `%1$s.valueOf(%2$s)` instead", - typeName.substring(typeName.lastIndexOf('.') + 1), argument); - } - - /** - * For an error message for an {@link #USE_VALUE_OF} issue reported by this detector, - * returns the type being replaced. Intended to use for IDE quickfix implementations. - */ - @SuppressWarnings("unused") // Used by the IDE - @Nullable - public static String getReplacedType(@NonNull String message, @NonNull TextFormat format) { - message = format.toText(message); - int index = message.indexOf('.'); - if (index != -1 && message.startsWith("Use ")) { - return message.substring(4, index); - } - return null; - } - - /** Visitor which records variable names assigned into */ - private static class AssignmentTracker extends AbstractUastVisitor { - private final Collection<String> mVariables; - - public AssignmentTracker(Collection<String> variables) { - mVariables = variables; - } - - @Override - public boolean visitBinaryExpression(UBinaryExpression node) { - if (UastExpressionUtils.isAssignment(node)) { - UExpression left = node.getLeftOperand(); - if (left instanceof UQualifiedReferenceExpression) { - UQualifiedReferenceExpression ref = (UQualifiedReferenceExpression) left; - if (ref.getReceiver() instanceof UThisExpression || - ref.getReceiver() instanceof USuperExpression) { - PsiElement resolved = ref.resolve(); - if (resolved instanceof PsiField) { - mVariables.add(((PsiField) resolved).getName()); - } - } else { - PsiElement resolved = ref.resolve(); - if (resolved instanceof PsiField) { - mVariables.add(((PsiField) resolved).getName()); - } - } - } else if (left instanceof USimpleNameReferenceExpression) { - mVariables.add(((USimpleNameReferenceExpression) left).getIdentifier()); - } - } - - return super.visitBinaryExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaScriptInterfaceDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaScriptInterfaceDetector.java deleted file mode 100644 index b4f9369..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaScriptInterfaceDetector.java +++ /dev/null
@@ -1,140 +0,0 @@ -/* - * 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TypeEvaluator; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifierList; -import com.intellij.psi.PsiType; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for addJavascriptInterface calls on interfaces have been properly annotated - * with {@code @JavaScriptInterface} - */ -public class JavaScriptInterfaceDetector extends Detector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "JavascriptInterface", //$NON-NLS-1$ - "Missing @JavascriptInterface on methods", - - "As of API 17, you must annotate methods in objects registered with the " + - "`addJavascriptInterface` method with a `@JavascriptInterface` annotation.", - - Category.SECURITY, - 8, - Severity.ERROR, - new Implementation( - JavaScriptInterfaceDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo( - "http://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)"); //$NON-NLS-1$ - - private static final String ADD_JAVASCRIPT_INTERFACE = "addJavascriptInterface"; //$NON-NLS-1$ - private static final String JAVASCRIPT_INTERFACE_CLS = "android.webkit.JavascriptInterface"; //$NON-NLS-1$ - private static final String WEB_VIEW_CLS = "android.webkit.WebView"; //$NON-NLS-1$ - - /** Constructs a new {@link JavaScriptInterfaceDetector} check */ - public JavaScriptInterfaceDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList(ADD_JAVASCRIPT_INTERFACE); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - if (context.getMainProject().getTargetSdk() < 17) { - return; - } - - List<UExpression> arguments = call.getValueArguments(); - if (arguments.size() != 2) { - return; - } - - JavaEvaluator evaluator = context.getEvaluator(); - if (!JavaEvaluator.isMemberInClass(method, WEB_VIEW_CLS)) { - return; - } - - UExpression first = arguments.get(0); - PsiType evaluated = TypeEvaluator.evaluate(context, first); - if (evaluated instanceof PsiClassType) { - PsiClassType classType = (PsiClassType) evaluated; - PsiClass cls = classType.resolve(); - if (cls == null) { - return; - } - if (isJavaScriptAnnotated(cls)) { - return; - } - - Location location = context.getUastNameLocation(call); - String message = String.format( - "None of the methods in the added interface (%1$s) have been annotated " + - "with `@android.webkit.JavascriptInterface`; they will not " + - "be visible in API 17", cls.getName()); - context.report(ISSUE, call, location, message); - } - } - - private static boolean isJavaScriptAnnotated(PsiClass clz) { - while (clz != null) { - PsiModifierList modifierList = clz.getModifierList(); - if (modifierList != null - && modifierList.findAnnotation(JAVASCRIPT_INTERFACE_CLS) != null) { - return true; - } - - for (PsiMethod method : clz.getMethods()) { - if (method.getModifierList().findAnnotation(JAVASCRIPT_INTERFACE_CLS) != null) { - return true; - } - } - - clz = clz.getSuperClass(); - } - - return false; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutConsistencyDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutConsistencyDetector.java deleted file mode 100644 index ddc1a69..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutConsistencyDetector.java +++ /dev/null
@@ -1,442 +0,0 @@ -/* - * 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_PREFIX; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.tools.klint.detector.api.LintUtils.stripIdPrefix; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LayoutDetector; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.android.utils.Pair; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.psi.JavaElementVisitor; -import com.intellij.psi.PsiElement; - -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Checks for consistency in layouts across different resource folders - */ -public class LayoutConsistencyDetector extends LayoutDetector implements Detector.UastScanner { - - /** Map from layout resource names to a list of files defining that resource, - * and within each file the value is a map from string ids to the widget type - * used by that id in this file */ - private final Map<String, List<Pair<File, Map<String, String>>>> mMap = - Maps.newHashMapWithExpectedSize(64); - - /** Ids referenced from .java files. Only ids referenced from code are considered - * vital to be consistent among the layout variations (others could just have ids - * assigned to them in the layout either automatically by the layout editor or there - * in order to support RelativeLayout constraints etc, but not be problematic - * in findViewById calls.) - */ - private final Set<String> mRelevantIds = Sets.newLinkedHashSetWithExpectedSize(64); - - /** Map from layout to id name to a list of locations */ - private Map<String, Map<String, List<Location>>> mLocations; - - /** Map from layout to id name to the error message to display for each */ - private Map<String, Map<String, String>> mErrorMessages; - - /** Inconsistent widget types */ - public static final Issue INCONSISTENT_IDS = Issue.create( - "InconsistentLayout", //$NON-NLS-1$ - "Inconsistent Layouts", - - "This check ensures that a layout resource which is defined in multiple " - + "resource folders, specifies the same set of widgets.\n" - + "\n" - + "This finds cases where you have accidentally forgotten to add " - + "a widget to all variations of the layout, which could result " - + "in a runtime crash for some resource configurations when a " - + "`findViewById()` fails.\n" - + "\n" - + "There *are* cases where this is intentional. For example, you " - + "may have a dedicated large tablet layout which adds some extra " - + "widgets that are not present in the phone version of the layout. " - + "As long as the code accessing the layout resource is careful to " - + "handle this properly, it is valid. In that case, you can suppress " - + "this lint check for the given extra or missing views, or the whole " - + "layout", - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - LayoutConsistencyDetector.class, - Scope.JAVA_AND_RESOURCE_FILES)); - - /** Constructs a consistency check */ - public LayoutConsistencyDetector() { - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == ResourceFolderType.LAYOUT; - } - - @Override - public void visitDocument(@NonNull XmlContext context, @NonNull Document document) { - Element root = document.getDocumentElement(); - if (root != null) { - if (context.getPhase() == 1) { - // Map from ids to types - Map<String,String> fileMap = Maps.newHashMapWithExpectedSize(10); - addIds(root, fileMap); - - getFileMapList(context).add(Pair.of(context.file, fileMap)); - } else { - String name = LintUtils.getLayoutName(context.file); - Map<String, List<Location>> map = mLocations.get(name); - if (map != null) { - lookupLocations(context, root, map); - } - } - } - } - - @NonNull - private List<Pair<File, Map<String, String>>> getFileMapList( - @NonNull XmlContext context) { - String name = LintUtils.getLayoutName(context.file); - List<Pair<File, Map<String, String>>> list = mMap.get(name); - if (list == null) { - list = Lists.newArrayListWithCapacity(4); - mMap.put(name, list); - } - return list; - } - - @Nullable - private static String getId(@NonNull Element element) { - String id = element.getAttributeNS(ANDROID_URI, ATTR_ID); - if (id != null && !id.isEmpty() && !id.startsWith(ANDROID_PREFIX)) { - return stripIdPrefix(id); - } - return null; - } - - private static void addIds(Element element, Map<String,String> map) { - String id = getId(element); - if (id != null) { - String s = stripIdPrefix(id); - map.put(s, element.getTagName()); - } - - NodeList childNodes = element.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - addIds((Element) child, map); - } - } - } - - private static void lookupLocations( - @NonNull XmlContext context, - @NonNull Element element, - @NonNull Map<String, List<Location>> map) { - String id = getId(element); - if (id != null) { - if (map.containsKey(id)) { - if (context.getDriver().isSuppressed(context, INCONSISTENT_IDS, element)) { - map.remove(id); - return; - } - - List<Location> locations = map.get(id); - if (locations == null) { - locations = Lists.newArrayList(); - map.put(id, locations); - } - Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_ID); - assert attr != null; - Location location = context.getLocation(attr); - String folder = context.file.getParentFile().getName(); - location.setMessage(String.format("Occurrence in %1$s", folder)); - locations.add(location); - } - } - - NodeList childNodes = element.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - lookupLocations(context, (Element) child, map); - } - } - } - - @Override - public void afterCheckProject(@NonNull Context context) { - LintDriver driver = context.getDriver(); - if (driver.getPhase() == 1) { - // First phase: gather all the ids and look for consistency issues. - // If any are found, request location computation in phase 2 by - // writing the ids needed for each layout in the {@link #mLocations} map. - for (Map.Entry<String,List<Pair<File,Map<String,String>>>> entry : mMap.entrySet()) { - String layout = entry.getKey(); - List<Pair<File, Map<String, String>>> files = entry.getValue(); - if (files.size() < 2) { - // No consistency problems for files that don't have resource variations - continue; - } - - checkConsistentIds(layout, files); - } - - if (mLocations != null) { - driver.requestRepeat(this, Scope.ALL_RESOURCES_SCOPE); - } - } else { - // Collect results and print - if (!mLocations.isEmpty()) { - reportErrors(context); - } - } - } - - @NonNull - private Set<String> stripIrrelevantIds(@NonNull Set<String> ids) { - if (!mRelevantIds.isEmpty()) { - Set<String> stripped = new HashSet<String>(ids); - stripped.retainAll(mRelevantIds); - return stripped; - } - - return Collections.emptySet(); - } - - private void checkConsistentIds( - @NonNull String layout, - @NonNull List<Pair<File, Map<String, String>>> files) { - int layoutCount = files.size(); - assert layoutCount >= 2; - - Map<File, Set<String>> idMap = getIdMap(files, layoutCount); - Set<String> inconsistent = getInconsistentIds(idMap); - if (inconsistent.isEmpty()) { - return; - } - - if (mLocations == null) { - mLocations = Maps.newHashMap(); - } - if (mErrorMessages == null) { - mErrorMessages = Maps.newHashMap(); - } - - // Map from each id, to a list of layout folders it is present in - int idCount = inconsistent.size(); - Map<String, List<String>> presence = Maps.newHashMapWithExpectedSize(idCount); - Set<String> allLayouts = Sets.newHashSetWithExpectedSize(layoutCount); - for (Map.Entry<File, Set<String>> entry : idMap.entrySet()) { - File file = entry.getKey(); - String folder = file.getParentFile().getName(); - allLayouts.add(folder); - Set<String> ids = entry.getValue(); - for (String id : ids) { - List<String> list = presence.get(id); - if (list == null) { - list = Lists.newArrayListWithExpectedSize(layoutCount); - presence.put(id, list); - } - list.add(folder); - } - } - - // Compute lookup maps which will be used in phase 2 to initialize actual - // locations for the id references - - Map<String, List<Location>> map = Maps.newHashMapWithExpectedSize(idCount); - mLocations.put(layout, map); - Map<String, String> messages = Maps.newHashMapWithExpectedSize(idCount); - mErrorMessages.put(layout, messages); - for (String id : inconsistent) { - map.put(id, null); // The locations will be filled in during the second phase - - // Determine presence description for this id - String message; - List<String> layouts = presence.get(id); - Collections.sort(layouts); - - Set<String> missingSet = new HashSet<String>(allLayouts); - missingSet.removeAll(layouts); - List<String> missing = new ArrayList<String>(missingSet); - Collections.sort(missing); - - if (layouts.size() < layoutCount / 2) { - message = String.format( - "The id \"%1$s\" in layout \"%2$s\" is only present in the following " - + "layout configurations: %3$s (missing from %4$s)", - id, layout, - LintUtils.formatList(layouts, Integer.MAX_VALUE), - LintUtils.formatList(missing, Integer.MAX_VALUE)); - } else { - message = String.format( - "The id \"%1$s\" in layout \"%2$s\" is missing from the following layout " - + "configurations: %3$s (present in %4$s)", - id, layout, LintUtils.formatList(missing, Integer.MAX_VALUE), - LintUtils.formatList(layouts, Integer.MAX_VALUE)); - } - messages.put(id, message); - } - } - - private static Set<String> getInconsistentIds(Map<File, Set<String>> idMap) { - Set<String> union = getAllIds(idMap); - Set<String> inconsistent = new HashSet<String>(); - for (Map.Entry<File, Set<String>> entry : idMap.entrySet()) { - Set<String> ids = entry.getValue(); - if (ids.size() < union.size()) { - Set<String> missing = new HashSet<String>(union); - missing.removeAll(ids); - inconsistent.addAll(missing); - } - } - return inconsistent; - } - - private static Set<String> getAllIds(Map<File, Set<String>> idMap) { - Iterator<Set<String>> iterator = idMap.values().iterator(); - assert iterator.hasNext(); - Set<String> union = new HashSet<String>(iterator.next()); - while (iterator.hasNext()) { - union.addAll(iterator.next()); - } - return union; - } - - private Map<File, Set<String>> getIdMap(List<Pair<File, Map<String, String>>> files, - int layoutCount) { - Map<File, Set<String>> idMap = new HashMap<File, Set<String>>(layoutCount); - for (Pair<File, Map<String, String>> pair : files) { - File file = pair.getFirst(); - Map<String, String> typeMap = pair.getSecond(); - Set<String> ids = typeMap.keySet(); - idMap.put(file, stripIrrelevantIds(ids)); - } - return idMap; - } - - private void reportErrors(Context context) { - List<String> layouts = new ArrayList<String>(mLocations.keySet()); - Collections.sort(layouts); - - for (String layout : layouts) { - Map<String, List<Location>> locationMap = mLocations.get(layout); - Map<String, String> messageMap = mErrorMessages.get(layout); - assert locationMap != null; - assert messageMap != null; - - List<String> ids = new ArrayList<String>(locationMap.keySet()); - Collections.sort(ids); - for (String id : ids) { - String message = messageMap.get(id); - List<Location> locations = locationMap.get(id); - if (locations != null) { - Location location = chainLocations(locations); - - context.report(INCONSISTENT_IDS, location, message); - } - } - } - } - - @NonNull - private static Location chainLocations(@NonNull List<Location> locations) { - assert !locations.isEmpty(); - - // Sort locations by the file parent folders - if (locations.size() > 1) { - Collections.sort(locations, new Comparator<Location>() { - @Override - public int compare(Location location1, Location location2) { - File file1 = location1.getFile(); - File file2 = location2.getFile(); - String folder1 = file1.getParentFile().getName(); - String folder2 = file2.getParentFile().getName(); - return folder1.compareTo(folder2); - } - }); - // Chain locations together - Iterator<Location> iterator = locations.iterator(); - assert iterator.hasNext(); - Location prev = iterator.next(); - while (iterator.hasNext()) { - Location next = iterator.next(); - prev.setSecondary(next); - prev = next; - } - } - - return locations.get(0); - } - - // ---- Implements UastScanner ---- - - @Override - public boolean appliesToResourceRefs() { - return true; - } - - @Override - public void visitResourceReference(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UElement node, @NonNull ResourceType type, @NonNull String name, - boolean isFramework) { - if (!isFramework && type == ResourceType.ID) { - mRelevantIds.add(name); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutInflationDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutInflationDetector.java deleted file mode 100644 index c66853f..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutInflationDetector.java +++ /dev/null
@@ -1,256 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; -import static com.android.tools.klint.checks.ViewHolderDetector.INFLATE; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceFile; -import com.android.ide.common.res2.ResourceItem; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.AndroidReference; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.client.api.UastLintUtils; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LayoutDetector; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.android.utils.Pair; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastLiteralUtils; -import org.jetbrains.uast.visitor.UastVisitor; -import org.kxml2.io.KXmlParser; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * Looks for layout inflation calls passing null as the view root - */ -public class LayoutInflationDetector extends LayoutDetector implements Detector.UastScanner { - - @SuppressWarnings("unchecked") - private static final Implementation IMPLEMENTATION = new Implementation( - LayoutInflationDetector.class, - Scope.JAVA_AND_RESOURCE_FILES, - Scope.JAVA_FILE_SCOPE); - - /** Passing in a null parent to a layout inflater */ - public static final Issue ISSUE = Issue.create( - "InflateParams", //$NON-NLS-1$ - "Layout Inflation without a Parent", - - "When inflating a layout, avoid passing in null as the parent view, since " + - "otherwise any layout parameters on the root of the inflated layout will be ignored.", - - Category.CORRECTNESS, - 5, - Severity.WARNING, - IMPLEMENTATION) - .addMoreInfo("http://www.doubleencore.com/2013/05/layout-inflation-as-intended"); - - private static final String ERROR_MESSAGE = - "Avoid passing `null` as the view root (needed to resolve " - + "layout parameters on the inflated layout's root element)"; - - /** Constructs a new {@link LayoutInflationDetector} check */ - public LayoutInflationDetector() { - } - - @Override - public void afterCheckProject(@NonNull Context context) { - if (mPendingErrors != null) { - for (Pair<String,Location> pair : mPendingErrors) { - String inflatedLayout = pair.getFirst(); - if (mLayoutsWithRootLayoutParams == null || - !mLayoutsWithRootLayoutParams.contains(inflatedLayout)) { - // No root layout parameters on the inflated layout: no need to complain - continue; - } - Location location = pair.getSecond(); - context.report(ISSUE, location, ERROR_MESSAGE); - } - } - } - - // ---- Implements XmlScanner ---- - - private Set<String> mLayoutsWithRootLayoutParams; - private List<Pair<String,Location>> mPendingErrors; - - @Override - public void visitDocument(@NonNull XmlContext context, @NonNull Document document) { - Element root = document.getDocumentElement(); - if (root != null) { - NamedNodeMap attributes = root.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Attr attribute = (Attr) attributes.item(i); - if (attribute.getLocalName() != null - && attribute.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - if (mLayoutsWithRootLayoutParams == null) { - mLayoutsWithRootLayoutParams = Sets.newHashSetWithExpectedSize(20); - } - mLayoutsWithRootLayoutParams.add(LintUtils.getBaseName(context.file.getName())); - break; - } - } - } - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList(INFLATE); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - assert method.getName().equals(INFLATE); - if (call.getReceiver() == null) { - return; - } - List<UExpression> arguments = call.getValueArguments(); - if (arguments.size() < 2) { - return; - } - - UExpression second = arguments.get(1); - if (!UastLiteralUtils.isNullLiteral(second)) { - return; - } - - UExpression first = arguments.get(0); - AndroidReference androidReference = UastLintUtils.toAndroidReferenceViaResolve(first); - if (androidReference == null) { - return; - } - - String layoutName = androidReference.getName(); - if (context.getScope().contains(Scope.RESOURCE_FILE)) { - // We're doing a full analysis run: we can gather this information - // incrementally - if (!context.getDriver().isSuppressed(context, ISSUE, call)) { - if (mPendingErrors == null) { - mPendingErrors = Lists.newArrayList(); - } - Location location = context.getUastLocation(second); - mPendingErrors.add(Pair.of(layoutName, location)); - } - } else if (hasLayoutParams(context, layoutName)) { - context.report(ISSUE, call, context.getUastLocation(second), ERROR_MESSAGE); - } - } - - private static boolean hasLayoutParams(@NonNull JavaContext context, String name) { - LintClient client = context.getClient(); - if (!client.supportsProjectResources()) { - return true; // not certain - } - - Project project = context.getProject(); - AbstractResourceRepository resources = client.getProjectResources(project, true); - if (resources == null) { - return true; // not certain - } - - List<ResourceItem> items = resources.getResourceItem(ResourceType.LAYOUT, name); - if (items == null || items.isEmpty()) { - return false; - } - - for (ResourceItem item : items) { - ResourceFile source = item.getSource(); - if (source == null) { - return true; // not certain - } - File file = source.getFile(); - if (file.exists()) { - try { - String s = context.getClient().readFile(file); - if (hasLayoutParams(new StringReader(s))) { - return true; - } - } catch (Exception e) { - context.log(e, "Could not read/parse inflated layout"); - return true; // not certain - } - } - } - - return false; - } - - @VisibleForTesting - static boolean hasLayoutParams(@NonNull Reader reader) - throws XmlPullParserException, IOException { - KXmlParser parser = new KXmlParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(reader); - - while (true) { - int event = parser.next(); - if (event == XmlPullParser.START_TAG) { - for (int i = 0; i < parser.getAttributeCount(); i++) { - if (parser.getAttributeName(i).startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - String prefix = parser.getAttributePrefix(i); - if (prefix != null && !prefix.isEmpty() && - ANDROID_URI.equals(parser.getNamespace(prefix))) { - return true; - } - } - } - - return false; - } else if (event == XmlPullParser.END_DOCUMENT) { - return false; - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LeakDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LeakDetector.java deleted file mode 100644 index d900484..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LeakDetector.java +++ /dev/null
@@ -1,183 +0,0 @@ -/* - * 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.klint.checks; - -import static com.android.SdkConstants.CLASS_CONTEXT; -import static com.android.SdkConstants.CLASS_FRAGMENT; -import static com.android.SdkConstants.CLASS_VIEW; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiModifierList; -import com.intellij.psi.PsiType; - -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UField; -import org.jetbrains.uast.UVariable; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for leaks via static fields - */ -public class LeakDetector extends Detector implements Detector.UastScanner { - /** Leaking data via static fields */ - public static final Issue ISSUE = Issue.create( - "StaticFieldLeak", //$NON-NLS-1$ - "Static Field Leaks", - - "A static field will leak contexts.", - - Category.PERFORMANCE, - 6, - Severity.WARNING, - new Implementation( - LeakDetector.class, - Scope.JAVA_FILE_SCOPE)); - - /** Constructs a new {@link LeakDetector} check */ - public LeakDetector() { - } - - // ---- Implements JavaScanner ---- - - @Override - public List<Class<? extends PsiElement>> getApplicablePsiTypes() { - return Collections.<Class<? extends PsiElement>>singletonList(PsiField.class); - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new FieldChecker(context); - } - - private static class FieldChecker extends AbstractUastVisitor { - private final JavaContext mContext; - - private FieldChecker(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitClass(@NotNull UClass node) { - return super.visitClass(node); - } - - @Override - public boolean visitVariable(UVariable node) { - if (node instanceof UField) { - checkField((UField) node); - } - return super.visitVariable(node); - } - - private void checkField(UField field) { - PsiModifierList modifierList = field.getModifierList(); - if (modifierList == null || !modifierList.hasModifierProperty(PsiModifier.STATIC)) { - return; - } - - PsiType type = field.getType(); - if (!(type instanceof PsiClassType)) { - return; - } - - String fqn = type.getCanonicalText(); - if (fqn.startsWith("java.")) { - return; - } - PsiClass cls = ((PsiClassType) type).resolve(); - if (cls == null) { - return; - } - if (fqn.startsWith("android.")) { - if (isLeakCandidate(cls)) { - String message = "Do not place Android context classes in static fields; " - + "this is a memory leak (and also breaks Instant Run)"; - report(field, message); - } - } else { - // User application object -- look to see if that one itself has - // static fields? - // We only check *one* level of indirection here - int count = 0; - for (PsiField referenced : cls.getAllFields()) { - // Only check a few; avoid getting bogged down on large classes - if (count++ == 20) { - break; - } - - PsiType innerType = referenced.getType(); - if (!(innerType instanceof PsiClassType)) { - continue; - } - - fqn = innerType.getCanonicalText(); - if (fqn.startsWith("java.")) { - continue; - } - PsiClass innerCls = ((PsiClassType) innerType).resolve(); - if (innerCls == null) { - continue; - } - if (fqn.startsWith("android.")) { - if (isLeakCandidate(innerCls)) { - String message = - "Do not place Android context classes in static fields " - + "(static reference to `" - + cls.getName() + "` which has field " - + "`" + referenced.getName() + "` pointing to `" - + innerCls.getName() + "`); " - + "this is a memory leak (and also breaks Instant Run)"; - report(field, message); - break; - } - } - } - } - } - - private void report(@NonNull UElement element, @NonNull String message) { - mContext.report(ISSUE, element, mContext.getUastLocation(element), message); - } - } - - private static boolean isLeakCandidate(@NonNull PsiClass cls) { - return InheritanceUtil.isInheritor(cls, false, CLASS_CONTEXT) - || InheritanceUtil.isInheritor(cls, false, CLASS_VIEW) - || InheritanceUtil.isInheritor(cls, false, CLASS_FRAGMENT); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LocaleDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LocaleDetector.java deleted file mode 100644 index 98dd50b..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LocaleDetector.java +++ /dev/null
@@ -1,166 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.FORMAT_METHOD; -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Checks for errors related to locale handling - */ -public class LocaleDetector extends Detector implements Detector.UastScanner { - private static final Implementation IMPLEMENTATION = new Implementation( - LocaleDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Calling risky convenience methods */ - public static final Issue STRING_LOCALE = Issue.create( - "DefaultLocale", //$NON-NLS-1$ - "Implied default locale in case conversion", - - "Calling `String#toLowerCase()` or `#toUpperCase()` *without specifying an " + - "explicit locale* is a common source of bugs. The reason for that is that those " + - "methods will use the current locale on the user's device, and even though the " + - "code appears to work correctly when you are developing the app, it will fail " + - "in some locales. For example, in the Turkish locale, the uppercase replacement " + - "for `i` is *not* `I`.\n" + - "\n" + - "If you want the methods to just perform ASCII replacement, for example to convert " + - "an enum name, call `String#toUpperCase(Locale.US)` instead. If you really want to " + - "use the current locale, call `String#toUpperCase(Locale.getDefault())` instead.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - IMPLEMENTATION) - .addMoreInfo( - "http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$ - - /** Constructs a new {@link LocaleDetector} */ - public LocaleDetector() { - } - - // ---- Implements JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - if (LintClient.isStudio()) { - // In the IDE, don't flag toUpperCase/toLowerCase; these - // are already flagged by built-in IDE inspections, so we don't - // want duplicate warnings. - return Collections.singletonList(FORMAT_METHOD); - } else { - return Arrays.asList( - // Only when not running in the IDE - "toLowerCase", //$NON-NLS-1$ - "toUpperCase", //$NON-NLS-1$ - FORMAT_METHOD - ); - } - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - if (JavaEvaluator.isMemberInClass(method, TYPE_STRING)) { - String name = method.getName(); - if (name.equals(FORMAT_METHOD)) { - checkFormat(context, method, call); - } else if (method.getParameterList().getParametersCount() == 0) { - Location location = context.getUastNameLocation(call); - String message = String.format( - "Implicitly using the default locale is a common source of bugs: " + - "Use `%1$s(Locale)` instead", name); - context.report(STRING_LOCALE, call, location, message); - } - } - } - - /** Returns true if the given node is a parameter to a Logging call */ - private static boolean isLoggingParameter(@NonNull UCallExpression node) { - UCallExpression parentCall = - UastUtils.getParentOfType(node, UCallExpression.class, true); - if (parentCall != null) { - String name = parentCall.getMethodName(); - if (name != null && name.length() == 1) { // "d", "i", "e" etc in Log - PsiMethod method = parentCall.resolve(); - return JavaEvaluator.isMemberInClass(method, LogDetector.LOG_CLS); - } - } - - return false; - } - - private static void checkFormat( - @NonNull JavaContext context, - @NonNull PsiMethod method, - @NonNull UCallExpression call) { - // Only check the non-locale version of String.format - if (method.getParameterList().getParametersCount() == 0 - || !context.getEvaluator().parameterHasType(method, 0, TYPE_STRING)) { - return; - } - List<UExpression> expressions = call.getValueArguments(); - if (expressions.isEmpty()) { - return; - } - - // Find the formatting string - UExpression first = expressions.get(0); - Object value = ConstantEvaluator.evaluate(context, first); - if (!(value instanceof String)) { - return; - } - - String format = (String) value; - if (StringFormatDetector.isLocaleSpecific(format)) { - if (isLoggingParameter(call)) { - return; - } - Location location = context.getUastLocation(call); - String message = - "Implicitly using the default locale is a common source of bugs: " + - "Use `String.format(Locale, ...)` instead"; - context.report(STRING_LOCALE, call, location, message); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LogDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LogDetector.java deleted file mode 100644 index 2bebea10..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LogDetector.java +++ /dev/null
@@ -1,354 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.UastLintUtils; -import com.android.tools.klint.detector.api.*; -import com.intellij.psi.*; -import org.jetbrains.uast.*; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.*; - -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; - -/** - * Detector for finding inefficiencies and errors in logging calls. - */ -public class LogDetector extends Detector implements Detector.UastScanner { - private static final Implementation IMPLEMENTATION = new Implementation( - LogDetector.class, Scope.JAVA_FILE_SCOPE); - - - /** Log call missing surrounding if */ - public static final Issue CONDITIONAL = Issue.create( - "LogConditional", //$NON-NLS-1$ - "Unconditional Logging Calls", - "The BuildConfig class (available in Tools 17) provides a constant, \"DEBUG\", " + - "which indicates whether the code is being built in release mode or in debug " + - "mode. In release mode, you typically want to strip out all the logging calls. " + - "Since the compiler will automatically remove all code which is inside a " + - "\"if (false)\" check, surrounding your logging calls with a check for " + - "BuildConfig.DEBUG is a good idea.\n" + - "\n" + - "If you *really* intend for the logging to be present in release mode, you can " + - "suppress this warning with a @SuppressLint annotation for the intentional " + - "logging calls.", - - Category.PERFORMANCE, - 5, - Severity.WARNING, - IMPLEMENTATION).setEnabledByDefault(false); - - /** Mismatched tags between isLogging and log calls within it */ - public static final Issue WRONG_TAG = Issue.create( - "LogTagMismatch", //$NON-NLS-1$ - "Mismatched Log Tags", - "When guarding a `Log.v(tag, ...)` call with `Log.isLoggable(tag)`, the " + - "tag passed to both calls should be the same. Similarly, the level passed " + - "in to `Log.isLoggable` should typically match the type of `Log` call, e.g. " + - "if checking level `Log.DEBUG`, the corresponding `Log` call should be `Log.d`, " + - "not `Log.i`.", - - Category.CORRECTNESS, - 5, - Severity.ERROR, - IMPLEMENTATION); - - /** Log tag is too long */ - public static final Issue LONG_TAG = Issue.create( - "LongLogTag", //$NON-NLS-1$ - "Too Long Log Tags", - "Log tags are only allowed to be at most 23 tag characters long.", - - Category.CORRECTNESS, - 5, - Severity.ERROR, - IMPLEMENTATION); - - @SuppressWarnings("SpellCheckingInspection") - private static final String IS_LOGGABLE = "isLoggable"; //$NON-NLS-1$ - public static final String LOG_CLS = "android.util.Log"; //$NON-NLS-1$ - private static final String PRINTLN = "println"; //$NON-NLS-1$ - - private static final Map<String, String> TAG_PAIRS; - - static { - Map<String, String> pairs = new HashMap<String, String>(); - pairs.put("d", "DEBUG"); - pairs.put("e", "ERROR"); - pairs.put("i", "INFO"); - pairs.put("v", "VERBOSE"); - pairs.put("w", "WARN"); - TAG_PAIRS = Collections.unmodifiableMap(pairs); - } - - // ---- Implements Detector.UastScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Arrays.asList( - "d", //$NON-NLS-1$ - "e", //$NON-NLS-1$ - "i", //$NON-NLS-1$ - "v", //$NON-NLS-1$ - "w", //$NON-NLS-1$ - PRINTLN, - IS_LOGGABLE); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - JavaEvaluator evaluator = context.getEvaluator(); - if (!JavaEvaluator.isMemberInClass(method, LOG_CLS)) { - return; - } - - String name = method.getName(); - boolean withinConditional = IS_LOGGABLE.equals(name) || - checkWithinConditional(context, node.getUastParent(), node); - - // See if it's surrounded by an if statement (and it's one of the non-error, spammy - // log methods (info, verbose, etc)) - if (("i".equals(name) || "d".equals(name) || "v".equals(name) || PRINTLN.equals(name)) - && !withinConditional - && performsWork(node) - && context.isEnabled(CONDITIONAL)) { - String message = String.format("The log call Log.%1$s(...) should be " + - "conditional: surround with `if (Log.isLoggable(...))` or " + - "`if (BuildConfig.DEBUG) { ... }`", - name); - context.report(CONDITIONAL, node, context.getUastLocation(node), message); - } - - // Check tag length - if (context.isEnabled(LONG_TAG)) { - int tagArgumentIndex = PRINTLN.equals(name) ? 1 : 0; - PsiParameterList parameterList = method.getParameterList(); - List<UExpression> argumentList = node.getValueArguments(); - if (evaluator.parameterHasType(method, tagArgumentIndex, TYPE_STRING) - && parameterList.getParametersCount() == argumentList.size()) { - UExpression argument = argumentList.get(tagArgumentIndex); - String tag = ConstantEvaluator.evaluateString(context, argument, true); - if (tag != null && tag.length() > 23) { - String message = String.format( - "The logging tag can be at most 23 characters, was %1$d (%2$s)", - tag.length(), tag); - context.report(LONG_TAG, node, context.getUastLocation(node), message); - } - } - } - - } - - /** Returns true if the given logging call performs "work" to compute the message */ - private static boolean performsWork(@NonNull UCallExpression node) { - String referenceName = node.getMethodName(); - if (referenceName == null) { - return false; - } - int messageArgumentIndex = PRINTLN.equals(referenceName) ? 2 : 1; - List<UExpression> arguments = node.getValueArguments(); - if (arguments.size() > messageArgumentIndex) { - UExpression argument = arguments.get(messageArgumentIndex); - if (argument == null) { - return false; - } - if (argument instanceof ULiteralExpression) { - return false; - } - if (argument instanceof UBinaryExpression) { - String string = UastUtils.evaluateString(argument); - //noinspection VariableNotUsedInsideIf - if (string != null) { // does it resolve to a constant? - return false; - } - } else if (argument instanceof USimpleNameReferenceExpression) { - // Just a simple local variable/field reference - return false; - } else if (argument instanceof UQualifiedReferenceExpression) { - String string = UastUtils.evaluateString(argument); - //noinspection VariableNotUsedInsideIf - if (string != null) { - return false; - } - PsiElement resolved = ((UQualifiedReferenceExpression) argument).resolve(); - if (resolved instanceof PsiVariable) { - // Just a reference to a property/field, parameter or variable - return false; - } - } - - // Method invocations etc - return true; - } - - return false; - } - - private static boolean checkWithinConditional( - @NonNull JavaContext context, - @Nullable UElement curr, - @NonNull UCallExpression logCall) { - while (curr != null) { - if (curr instanceof UIfExpression) { - - UExpression condition = ((UIfExpression) curr).getCondition(); - if (condition instanceof UQualifiedReferenceExpression) { - condition = getLastInQualifiedChain((UQualifiedReferenceExpression) condition); - } - - if (condition instanceof UCallExpression) { - UCallExpression call = (UCallExpression) condition; - if (IS_LOGGABLE.equals(call.getMethodName())) { - checkTagConsistent(context, logCall, call); - } - } - - return true; - } else if (curr instanceof UCallExpression - || curr instanceof UMethod - || curr instanceof UClassInitializer - || curr instanceof UField - || curr instanceof UClass) { // static block - break; - } - curr = curr.getUastParent(); - } - return false; - } - - /** Checks that the tag passed to Log.s and Log.isLoggable match */ - private static void checkTagConsistent(JavaContext context, UCallExpression logCall, - UCallExpression isLoggableCall) { - List<UExpression> isLoggableArguments = isLoggableCall.getValueArguments(); - List<UExpression> logArguments = logCall.getValueArguments(); - if (isLoggableArguments.isEmpty() || logArguments.isEmpty()) { - return; - } - UExpression isLoggableTag = isLoggableArguments.get(0); - UExpression logTag = logArguments.get(0); - - String logCallName = logCall.getMethodName(); - if (logCallName == null) { - return; - } - boolean isPrintln = PRINTLN.equals(logCallName); - if (isPrintln && logArguments.size() > 1) { - logTag = logArguments.get(1); - } - - if (logTag != null) { - if (!areLiteralsEqual(isLoggableTag, logTag) && - !UastLintUtils.areIdentifiersEqual(isLoggableTag, logTag)) { - PsiNamedElement resolved1 = UastUtils.tryResolveNamed(isLoggableTag); - PsiNamedElement resolved2 = UastUtils.tryResolveNamed(logTag); - if ((resolved1 == null || resolved2 == null || !resolved1.equals(resolved2)) - && context.isEnabled(WRONG_TAG)) { - Location location = context.getUastLocation(logTag); - Location alternate = context.getUastLocation(isLoggableTag); - alternate.setMessage("Conflicting tag"); - location.setSecondary(alternate); - String isLoggableDescription = resolved1 != null - ? resolved1.getName() - : isLoggableTag.asRenderString(); - String logCallDescription = resolved2 != null - ? resolved2.getName() - : logTag.asRenderString(); - String message = String.format( - "Mismatched tags: the `%1$s()` and `isLoggable()` calls typically " + - "should pass the same tag: `%2$s` versus `%3$s`", - logCallName, - isLoggableDescription, - logCallDescription); - context.report(WRONG_TAG, isLoggableCall, location, message); - } - } - } - - // Check log level versus the actual log call type (e.g. flag - // if (Log.isLoggable(TAG, Log.DEBUG) Log.info(TAG, "something") - - if (logCallName.length() != 1 || isLoggableArguments.size() < 2) { // e.g. println - return; - } - UExpression isLoggableLevel = isLoggableArguments.get(1); - if (isLoggableLevel == null) { - return; - } - - PsiNamedElement resolved = UastUtils.tryResolveNamed(isLoggableLevel); - if (resolved == null) { - return; - } - - if (resolved instanceof PsiVariable) { - PsiClass containingClass = UastUtils.getContainingClass(resolved); - if (containingClass == null - || !"android.util.Log".equals(containingClass.getQualifiedName()) - || resolved.getName() == null - || resolved.getName().equals(TAG_PAIRS.get(logCallName))) { - return; - } - - String expectedCall = resolved.getName().substring(0, 1) - .toLowerCase(Locale.getDefault()); - - String message = String.format( - "Mismatched logging levels: when checking `isLoggable` level `%1$s`, the " + - "corresponding log call should be `Log.%2$s`, not `Log.%3$s`", - resolved.getName(), expectedCall, logCallName); - Location location = context.getUastLocation(logCall.getMethodIdentifier()); - Location alternate = context.getUastLocation(isLoggableLevel); - alternate.setMessage("Conflicting tag"); - location.setSecondary(alternate); - context.report(WRONG_TAG, isLoggableCall, location, message); - } - } - - @NonNull - private static UExpression getLastInQualifiedChain(@NonNull UQualifiedReferenceExpression node) { - UExpression last = node.getSelector(); - while (last instanceof UQualifiedReferenceExpression) { - last = ((UQualifiedReferenceExpression) last).getSelector(); - } - return last; - } - - private static boolean areLiteralsEqual(UExpression first, UExpression second) { - if (!(first instanceof ULiteralExpression)) { - return false; - } - - if (!(second instanceof ULiteralExpression)) { - return false; - } - - Object firstValue = ((ULiteralExpression) first).getValue(); - Object secondValue = ((ULiteralExpression) second).getValue(); - - if (firstValue == null) { - return secondValue == null; - } - - return firstValue.equals(secondValue); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MathDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MathDetector.java deleted file mode 100644 index bc6a2ca..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MathDetector.java +++ /dev/null
@@ -1,94 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.List; - -/** - * Looks for usages of {@link Math} methods which can be replaced with - * {@code android.util.FloatMath} methods to avoid casting. - */ -public class MathDetector extends Detector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "FloatMath", //$NON-NLS-1$ - "Using `FloatMath` instead of `Math`", - - "In older versions of Android, using `android.util.FloatMath` was recommended " + - "for performance reasons when operating on floats. However, on modern hardware " + - "doubles are just as fast as float (though they take more memory), and in " + - "recent versions of Android, `FloatMath` is actually slower than using `java.lang.Math` " + - "due to the way the JIT optimizes `java.lang.Math`. Therefore, you should use " + - "`Math` instead of `FloatMath` if you are only targeting Froyo and above.", - - Category.PERFORMANCE, - 3, - Severity.WARNING, - new Implementation( - MathDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo( - "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$ - - /** Constructs a new {@link MathDetector} check */ - public MathDetector() { - } - - // ---- Implements JavaScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Arrays.asList( - "sin", //$NON-NLS-1$ - "cos", //$NON-NLS-1$ - "ceil", //$NON-NLS-1$ - "sqrt", //$NON-NLS-1$ - "floor" //$NON-NLS-1$ - ); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - if (context.getEvaluator().isMemberInClass(method, "android.util.FloatMath") - && context.getProject().getMinSdk() >= 8) { - String message = String.format( - "Use `java.lang.Math#%1$s` instead of `android.util.FloatMath#%1$s()` " + - "since it is faster as of API 8", method.getName()); - - Location location = context.getUastLocation(call.getMethodIdentifier()); - context.report(ISSUE, call, location, message); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MergeRootFrameLayoutDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MergeRootFrameLayoutDetector.java deleted file mode 100644 index 7bc51b3..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MergeRootFrameLayoutDetector.java +++ /dev/null
@@ -1,176 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.AndroidReference; -import com.android.tools.klint.client.api.UastLintUtils; -import com.android.tools.klint.detector.api.*; -import com.android.tools.klint.detector.api.Location.Handle; -import com.android.utils.Pair; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.util.*; - -import static com.android.SdkConstants.*; - -/** - * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag. - */ -public class MergeRootFrameLayoutDetector extends LayoutDetector implements Detector.UastScanner { - /** - * Set of layouts that we want to enable the warning for. We only warn for - * {@code <FrameLayout>}'s that are the root of a layout included from - * another layout, or directly referenced via a {@code setContentView} call. - */ - private Set<String> mWhitelistedLayouts; - - /** - * Set of pending [layout, location] pairs where the given layout is a - * FrameLayout that perhaps should be replaced by a {@code <merge>} tag (if - * the layout is included or set as the content view. This must be processed - * after the whole project has been scanned since the set of includes etc - * can be encountered after the included layout. - */ - private List<Pair<String, Location.Handle>> mPending; - - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "MergeRootFrame", //$NON-NLS-1$ - "FrameLayout can be replaced with `<merge>` tag", - - "If a `<FrameLayout>` is the root of a layout and does not provide background " + - "or padding etc, it can often be replaced with a `<merge>` tag which is slightly " + - "more efficient. Note that this depends on context, so make sure you understand " + - "how the `<merge>` tag works before proceeding.", - Category.PERFORMANCE, - 4, - Severity.WARNING, - new Implementation( - MergeRootFrameLayoutDetector.class, - EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE))) - .addMoreInfo( - "http://android-developers.blogspot.com/2009/03/android-layout-tricks-3-optimize-by.html"); //$NON-NLS-1$ - - /** Constructs a new {@link MergeRootFrameLayoutDetector} */ - public MergeRootFrameLayoutDetector() { - } - - @Override - public void afterCheckProject(@NonNull Context context) { - if (mPending != null && mWhitelistedLayouts != null) { - // Process all the root FrameLayouts that are eligible, and generate - // suggestions for <merge> replacements for any layouts that are included - // from other layouts - for (Pair<String, Handle> pair : mPending) { - String layout = pair.getFirst(); - if (mWhitelistedLayouts.contains(layout)) { - Handle handle = pair.getSecond(); - - Object clientData = handle.getClientData(); - if (clientData instanceof Node) { - if (context.getDriver().isSuppressed(null, ISSUE, (Node) clientData)) { - return; - } - } - - Location location = handle.resolve(); - context.report(ISSUE, location, - "This `<FrameLayout>` can be replaced with a `<merge>` tag"); - } - } - } - } - - // Implements XmlScanner - - @Override - public Collection<String> getApplicableElements() { - return Arrays.asList(VIEW_INCLUDE, FRAME_LAYOUT); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - String tag = element.getTagName(); - if (tag.equals(VIEW_INCLUDE)) { - String layout = element.getAttribute(ATTR_LAYOUT); // NOTE: Not in android: namespace - if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { // Ignore @android:layout/ layouts - layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length()); - whiteListLayout(layout); - } - } else { - assert tag.equals(FRAME_LAYOUT); - if (LintUtils.isRootElement(element) && - ((isWidthFillParent(element) && isHeightFillParent(element)) || - !element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY)) - && !element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND) - && !element.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND) - && !hasPadding(element)) { - String layout = LintUtils.getLayoutName(context.file); - Handle handle = context.createLocationHandle(element); - handle.setClientData(element); - - if (!context.getProject().getReportIssues()) { - // If this is a library project not being analyzed, ignore it - return; - } - - if (mPending == null) { - mPending = new ArrayList<Pair<String,Handle>>(); - } - mPending.add(Pair.of(layout, handle)); - } - } - } - - private void whiteListLayout(String layout) { - if (mWhitelistedLayouts == null) { - mWhitelistedLayouts = new HashSet<String>(); - } - mWhitelistedLayouts.add(layout); - } - - // Implements JavaScanner - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("setContentView"); //$NON-NLS-1$ - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - List<UExpression> expressions = call.getValueArguments(); - - if (expressions.size() == 1) { - AndroidReference androidReference = - UastLintUtils.toAndroidReferenceViaResolve(expressions.get(0)); - - if (androidReference != null && androidReference.getType() == ResourceType.LAYOUT) { - whiteListLayout(androidReference.getName()); - } - } - - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/NonInternationalizedSmsDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/NonInternationalizedSmsDetector.java deleted file mode 100644 index 4781d1e..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/NonInternationalizedSmsDetector.java +++ /dev/null
@@ -1,100 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.ArrayList; -import java.util.List; - -/** Detector looking for text messages sent to an unlocalized phone number. */ -public class NonInternationalizedSmsDetector extends Detector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "UnlocalizedSms", //$NON-NLS-1$ - "SMS phone number missing country code", - - "SMS destination numbers must start with a country code or the application code " + - "must ensure that the SMS is only sent when the user is in the same country as " + - "the receiver.", - - Category.CORRECTNESS, - 5, - Severity.WARNING, - new Implementation( - NonInternationalizedSmsDetector.class, - Scope.JAVA_FILE_SCOPE)); - - - /** Constructs a new {@link NonInternationalizedSmsDetector} check */ - public NonInternationalizedSmsDetector() { - } - - // ---- Implements JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - List<String> methodNames = new ArrayList<String>(2); - methodNames.add("sendTextMessage"); //$NON-NLS-1$ - methodNames.add("sendMultipartTextMessage"); //$NON-NLS-1$ - return methodNames; - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - if (call.getReceiver() == null) { - // "sendTextMessage"/"sendMultipartTextMessage" in the code with no operand - return; - } - - List<UExpression> args = call.getValueArguments(); - if (args.size() != 5) { - return; - } - UExpression destinationAddress = args.get(0); - if (!(destinationAddress instanceof ULiteralExpression)) { - return; - } - Object literal = ((ULiteralExpression) destinationAddress).getValue(); - if (!(literal instanceof String)) { - return; - } - String number = (String) literal; - if (number.startsWith("+")) { //$NON-NLS-1$ - return; - } - context.report(ISSUE, call, context.getUastLocation(destinationAddress), - "To make sure the SMS can be sent by all users, please start the SMS number " + - "with a + and a country code or restrict the code invocation to people in the " + - "country you are targeting."); - } -} \ No newline at end of file
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverdrawDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverdrawDetector.java deleted file mode 100644 index 7f5cc93..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverdrawDetector.java +++ /dev/null
@@ -1,554 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_BACKGROUND; -import static com.android.SdkConstants.ATTR_CONTEXT; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_PARENT; -import static com.android.SdkConstants.ATTR_THEME; -import static com.android.SdkConstants.ATTR_TILE_MODE; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.DRAWABLE_PREFIX; -import static com.android.SdkConstants.NULL_RESOURCE; -import static com.android.SdkConstants.R_CLASS; -import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.TAG_ACTIVITY; -import static com.android.SdkConstants.TAG_APPLICATION; -import static com.android.SdkConstants.TAG_BITMAP; -import static com.android.SdkConstants.TAG_STYLE; -import static com.android.SdkConstants.TOOLS_URI; -import static com.android.SdkConstants.TRANSPARENT_COLOR; -import static com.android.SdkConstants.VALUE_DISABLED; -import static com.android.tools.klint.detector.api.LintUtils.endsWith; -import static com.android.utils.SdkUtils.getResourceFieldName; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.AndroidReference; -import com.android.tools.klint.client.api.UastLintUtils; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LayoutDetector; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.android.utils.Pair; -import com.intellij.psi.JavaRecursiveElementVisitor; -import com.intellij.psi.PsiAnonymousClass; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiReferenceExpression; - -import org.jetbrains.uast.UAnonymousClass; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UQualifiedReferenceExpression; -import org.jetbrains.uast.USimpleNameReferenceExpression; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Check which looks for overdraw problems where view areas are painted and then - * painted over, meaning that the bottom paint operation is a waste of time. - */ -public class OverdrawDetector extends LayoutDetector implements Detector.UastScanner { - private static final String SET_THEME = "setTheme"; //$NON-NLS-1$ - - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "Overdraw", //$NON-NLS-1$ - "Overdraw: Painting regions more than once", - - "If you set a background drawable on a root view, then you should use a " + - "custom theme where the theme background is null. Otherwise, the theme background " + - "will be painted first, only to have your custom background completely cover it; " + - "this is called \"overdraw\".\n" + - "\n" + - "NOTE: This detector relies on figuring out which layouts are associated with " + - "which activities based on scanning the Java code, and it's currently doing that " + - "using an inexact pattern matching algorithm. Therefore, it can incorrectly " + - "conclude which activity the layout is associated with and then wrongly complain " + - "that a background-theme is hidden.\n" + - "\n" + - "If you want your custom background on multiple pages, then you should consider " + - "making a custom theme with your custom background and just using that theme " + - "instead of a root element background.\n" + - "\n" + - "Of course it's possible that your custom drawable is translucent and you want " + - "it to be mixed with the background. However, you will get better performance " + - "if you pre-mix the background with your drawable and use that resulting image or " + - "color as a custom theme background instead.\n", - - Category.PERFORMANCE, - 3, - Severity.WARNING, - new Implementation( - OverdrawDetector.class, - EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES))); - - /** Mapping from FQN activity names to theme names registered in the manifest */ - private Map<String, String> mActivityToTheme; - - /** The default theme declared in the manifest, or null */ - private String mManifestTheme; - - /** Mapping from layout name (not including {@code @layout/} prefix) to activity FQN */ - private Map<String, List<String>> mLayoutToActivity; - - /** List of theme names registered in the project which have blank backgrounds */ - private List<String> mBlankThemes; - - /** List of drawable resources that are not flagged for overdraw (XML drawables - * except for {@code <bitmap>} drawables without tiling) */ - private List<String> mValidDrawables; - - /** - * List of pairs of (location, background drawable) corresponding to root elements - * in layouts that define a given background drawable. These should be checked to - * see if they are painting on top of a non-transparent theme. - */ - private List<Pair<Location, String>> mRootAttributes; - - /** Constructs a new {@link OverdrawDetector} */ - public OverdrawDetector() { - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - // Look in layouts for drawable resources - return super.appliesTo(folderType) - // and in resource files for theme definitions - || folderType == ResourceFolderType.VALUES - // and in drawable files for bitmap tiling modes - || folderType == ResourceFolderType.DRAWABLE; - } - - /** Is the given theme a "blank" theme (one not painting its background) */ - private boolean isBlankTheme(String name) { - if (name.startsWith("@android:style/Theme_")) { //$NON-NLS-1$ - if (name.contains("NoFrame") //$NON-NLS-1$ - || name.contains("Theme_Wallpaper") //$NON-NLS-1$ - || name.contains("Theme_Holo_Wallpaper") //$NON-NLS-1$ - || name.contains("Theme_Translucent") //$NON-NLS-1$ - || name.contains("Theme_Dialog_NoFrame") //$NON-NLS-1$ - || name.contains("Theme_Holo_Dialog_Alert") //$NON-NLS-1$ - || name.contains("Theme_Holo_Light_Dialog_Alert") //$NON-NLS-1$ - || name.contains("Theme_Dialog_Alert") //$NON-NLS-1$ - || name.contains("Theme_Panel") //$NON-NLS-1$ - || name.contains("Theme_Light_Panel") //$NON-NLS-1$ - || name.contains("Theme_Holo_Panel") //$NON-NLS-1$ - || name.contains("Theme_Holo_Light_Panel")) { //$NON-NLS-1$ - return true; - } - } - - return mBlankThemes != null && mBlankThemes.contains(name); - - } - - @Override - public void afterCheckProject(@NonNull Context context) { - if (mRootAttributes != null) { - for (Pair<Location, String> pair : mRootAttributes) { - Location location = pair.getFirst(); - - Object clientData = location.getClientData(); - if (clientData instanceof Node) { - if (context.getDriver().isSuppressed(null, ISSUE, (Node) clientData)) { - return; - } - } - - String layoutName = location.getFile().getName(); - if (endsWith(layoutName, DOT_XML)) { - layoutName = layoutName.substring(0, layoutName.length() - DOT_XML.length()); - } - - String theme = getTheme(context, layoutName); - if (theme == null || !isBlankTheme(theme)) { - String drawable = pair.getSecond(); - String message = String.format( - "Possible overdraw: Root element paints background `%1$s` with " + - "a theme that also paints a background (inferred theme is `%2$s`)", - drawable, theme); - // TODO: Compute applicable scope node - context.report(ISSUE, location, message); - } - } - } - } - - /** Return the theme to be used for the given layout */ - private String getTheme(Context context, String layoutName) { - if (mActivityToTheme != null && mLayoutToActivity != null) { - List<String> activities = mLayoutToActivity.get(layoutName); - if (activities != null) { - for (String activity : activities) { - String theme = mActivityToTheme.get(activity); - if (theme != null) { - return theme; - } - } - } - } - - if (mManifestTheme != null) { - return mManifestTheme; - } - - Project project = context.getMainProject(); - int apiLevel = project.getTargetSdk(); - if (apiLevel == -1) { - apiLevel = project.getMinSdk(); - } - - if (apiLevel >= 11) { - return "@android:style/Theme.Holo"; //$NON-NLS-1$ - } else { - return "@android:style/Theme"; //$NON-NLS-1$ - } - } - - // ---- Implements XmlScanner ---- - - @Override - public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { - // Ignore tools:background and any other custom attribute that isn't actually the - // android View background attribute - if (!ANDROID_URI.equals(attribute.getNamespaceURI())) { - return; - } - - // Only consider the root element's background - Element documentElement = attribute.getOwnerDocument().getDocumentElement(); - if (documentElement == attribute.getOwnerElement()) { - // If the drawable is a non-repeated pattern then the overdraw might be - // intentional since the image isn't covering the whole screen - String background = attribute.getValue(); - if (mValidDrawables != null && mValidDrawables.contains(background)) { - return; - } - - if (background.equals(TRANSPARENT_COLOR) || background.equals(NULL_RESOURCE)) { - return; - } - - if (background.startsWith("@android:drawable/")) { //$NON-NLS-1$ - // We haven't had a chance to study the builtin drawables the way we - // check the project local ones in scanBitmap() and beforeCheckFile(), - // but many of these are not bitmaps, so ignore these - return; - } - - String name = context.file.getName(); - if (name.contains("list_") || name.contains("_item")) { //$NON-NLS-1$ //$NON-NLS-2$ - // Canonical list_item layout name: don't warn about these, it's - // pretty common to want to paint custom list item backgrounds - return; - } - - if (!context.getProject().getReportIssues()) { - // If this is a library project not being analyzed, ignore it - return; - } - - Location location = context.getLocation(attribute); - location.setClientData(attribute); - if (mRootAttributes == null) { - mRootAttributes = new ArrayList<Pair<Location,String>>(); - } - mRootAttributes.add(Pair.of(location, attribute.getValue())); - - String activity = documentElement.getAttributeNS(TOOLS_URI, ATTR_CONTEXT); - if (activity != null && !activity.isEmpty()) { - if (activity.startsWith(".")) { //$NON-NLS-1$ - activity = context.getProject().getPackage() + activity; - } - registerLayoutActivity(LintUtils.getLayoutName(context.file), activity); - } - } - } - - @Override - public Collection<String> getApplicableAttributes() { - return Collections.singletonList( - // Layouts: Look for background attributes on root elements for possible overdraw - ATTR_BACKGROUND - ); - } - - @Override - public Collection<String> getApplicableElements() { - return Arrays.asList( - // Manifest: Look at theme registrations - TAG_ACTIVITY, - TAG_APPLICATION, - - // Resource files: Look at theme definitions - TAG_STYLE, - - // Bitmaps - TAG_BITMAP - ); - } - - @Override - public void beforeCheckFile(@NonNull Context context) { - if (endsWith(context.file.getName(), DOT_XML)) { - // Drawable XML files should not be considered for overdraw, except for <bitmap>'s. - // The bitmap elements are handled in the scanBitmap() method; it will clear - // out anything added by this method. - File parent = context.file.getParentFile(); - ResourceFolderType type = ResourceFolderType.getFolderType(parent.getName()); - if (type == ResourceFolderType.DRAWABLE) { - if (mValidDrawables == null) { - mValidDrawables = new ArrayList<String>(); - } - String resource = getDrawableResource(context.file); - mValidDrawables.add(resource); - } - } - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - String tag = element.getTagName(); - if (tag.equals(TAG_STYLE)) { - scanTheme(element); - } else if (tag.equals(TAG_ACTIVITY)) { - scanActivity(context, element); - } else if (tag.equals(TAG_APPLICATION)) { - if (element.hasAttributeNS(ANDROID_URI, ATTR_THEME)) { - mManifestTheme = element.getAttributeNS(ANDROID_URI, ATTR_THEME); - } - } else if (tag.equals(TAG_BITMAP)) { - scanBitmap(context, element); - } - } - - private static String getDrawableResource(File drawableFile) { - String resource = drawableFile.getName(); - if (endsWith(resource, DOT_XML)) { - resource = resource.substring(0, resource.length() - DOT_XML.length()); - } - return DRAWABLE_PREFIX + resource; - } - - private void scanBitmap(Context context, Element element) { - String tileMode = element.getAttributeNS(ANDROID_URI, ATTR_TILE_MODE); - if (!(tileMode.equals(VALUE_DISABLED) || tileMode.isEmpty())) { - if (mValidDrawables != null) { - String resource = getDrawableResource(context.file); - mValidDrawables.remove(resource); - } - } - } - - private void scanActivity(Context context, Element element) { - String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - if (name.indexOf('$') != -1) { - name = name.replace('$', '.'); - } - if (name.startsWith(".")) { //$NON-NLS-1$ - String pkg = context.getProject().getPackage(); - if (pkg != null && !pkg.isEmpty()) { - name = pkg + name; - } - } - - String theme = element.getAttributeNS(ANDROID_URI, ATTR_THEME); - if (theme != null && !theme.isEmpty()) { - if (mActivityToTheme == null) { - mActivityToTheme = new HashMap<String, String>(); - } - mActivityToTheme.put(name, getResourceFieldName(theme)); - } - } - - private void scanTheme(Element element) { - // Look for theme definitions, and record themes that provide a null background. - String styleName = element.getAttribute(ATTR_NAME); - String parent = element.getAttribute(ATTR_PARENT); - if (parent == null) { - // Eclipse DOM workaround - parent = ""; - } - - if (parent.isEmpty()) { - int index = styleName.lastIndexOf('.'); - if (index != -1) { - parent = styleName.substring(0, index); - } - } - parent = parent.replace('.', '_'); - - String resource = STYLE_RESOURCE_PREFIX + getResourceFieldName(styleName); - - NodeList items = element.getChildNodes(); - for (int i = 0, n = items.getLength(); i < n; i++) { - if (items.item(i).getNodeType() == Node.ELEMENT_NODE) { - Element item = (Element) items.item(i); - String name = item.getAttribute(ATTR_NAME); - if (name.equals("android:windowBackground")) { //$NON-NLS-1$ - NodeList textNodes = item.getChildNodes(); - for (int j = 0, m = textNodes.getLength(); j < m; j++) { - Node textNode = textNodes.item(j); - if (textNode.getNodeType() == Node.TEXT_NODE) { - String text = textNode.getNodeValue(); - String trim = text.trim(); - if (!trim.isEmpty()) { - if (trim.equals(NULL_RESOURCE) - || trim.equals(TRANSPARENT_COLOR) - || mValidDrawables != null - && mValidDrawables.contains(trim)) { - if (mBlankThemes == null) { - mBlankThemes = new ArrayList<String>(); - } - mBlankThemes.add(resource); - } - } - } - } - - return; - } - } - } - - if (isBlankTheme(parent)) { - if (mBlankThemes == null) { - mBlankThemes = new ArrayList<String>(); - } - mBlankThemes.add(resource); - } - } - - private void registerLayoutActivity(String layout, String classFqn) { - if (mLayoutToActivity == null) { - mLayoutToActivity = new HashMap<String, List<String>>(); - } - List<String> list = mLayoutToActivity.get(layout); - if (list == null) { - list = new ArrayList<String>(); - mLayoutToActivity.put(layout, list); - } - list.add(classFqn); - } - - // ---- Implements UastScanner ---- - - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList("android.app.Activity"); - } - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - return Collections.<Class<? extends UElement>>singletonList(UClass.class); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - if (!context.getProject().getReportIssues()) { - return; - } - String name = declaration.getQualifiedName(); - if (name != null) { - declaration.accept(new OverdrawVisitor(name, declaration)); - } - } - - private class OverdrawVisitor extends AbstractUastVisitor { - private final String mName; - private final PsiClass mCls; - - public OverdrawVisitor(String name, PsiClass cls) { - mName = name; - mCls = cls; - } - - @Override - public boolean visitClass(UClass node) { - // Don't go into inner classes - if (mCls.equals(node.getPsi())) { - return true; - } - - return super.visitClass(node); - } - - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - AndroidReference androidReference = UastLintUtils.toAndroidReferenceViaResolve(node); - if (androidReference != null && androidReference.getType() == ResourceType.LAYOUT) { - registerLayoutActivity(androidReference.getName(), mName); - } - - return super.visitSimpleNameReferenceExpression(node); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (SET_THEME.equals(node.getMethodName()) && node.getValueArgumentCount() == 1) { - // Look at argument - UExpression arg = node.getValueArguments().get(0); - AndroidReference androidReference = UastLintUtils.toAndroidReferenceViaResolve(arg); - if (androidReference != null && androidReference.getType() == ResourceType.STYLE) { - String style = androidReference.getName(); - if (mActivityToTheme == null) { - mActivityToTheme = new HashMap<String, String>(); - } - mActivityToTheme.put(mName, STYLE_RESOURCE_PREFIX + style); - } - } - - return super.visitCallExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverrideConcreteDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverrideConcreteDetector.java deleted file mode 100644 index a1c9074..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverrideConcreteDetector.java +++ /dev/null
@@ -1,159 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.util.PsiTreeUtil; - -import org.jetbrains.uast.UClass; - -import java.util.Collections; -import java.util.List; - -/** - * Checks that subclasses of certain APIs are overriding all methods that were abstract - * in one or more earlier API levels that are still targeted by the minSdkVersion - * of this project. - */ -public class OverrideConcreteDetector extends Detector implements Detector.UastScanner { - /** Are previously-abstract methods all overridden? */ - public static final Issue ISSUE = Issue.create( - "OverrideAbstract", //$NON-NLS-1$ - "Not overriding abstract methods on older platforms", - - "To improve the usability of some APIs, some methods that used to be `abstract` have " + - "been made concrete by adding default implementations. This means that when compiling " + - "with new versions of the SDK, your code does not have to override these methods.\n" + - "\n" + - "However, if your code is also targeting older versions of the platform where these " + - "methods were still `abstract`, the code will crash. You must override all methods " + - "that used to be abstract in any versions targeted by your application's " + - "`minSdkVersion`.", - - Category.CORRECTNESS, - 6, - Severity.FATAL, - new Implementation( - OverrideConcreteDetector.class, - Scope.JAVA_FILE_SCOPE) - ); - - // This check is currently hardcoded for the specific case of the - // NotificationListenerService change in API 21. We should consider - // attempting to infer this information automatically from changes in - // the API current.txt file and making this detector more database driven, - // like the API detector. - - private static final String NOTIFICATION_LISTENER_SERVICE_FQN - = "android.service.notification.NotificationListenerService"; - public static final String STATUS_BAR_NOTIFICATION_FQN - = "android.service.notification.StatusBarNotification"; - private static final String ON_NOTIFICATION_POSTED = "onNotificationPosted"; - private static final String ON_NOTIFICATION_REMOVED = "onNotificationRemoved"; - private static final int CONCRETE_IN = 21; - - /** Constructs a new {@link OverrideConcreteDetector} */ - public OverrideConcreteDetector() { - } - - // ---- Implements JavaScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList(NOTIFICATION_LISTENER_SERVICE_FQN); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.isAbstract(declaration)) { - return; - } - - int minSdk = Math.max(context.getProject().getMinSdk(), getTargetApi(declaration)); - if (minSdk >= CONCRETE_IN) { - return; - } - - String[] methodNames = {ON_NOTIFICATION_POSTED, ON_NOTIFICATION_REMOVED}; - for (String methodName : methodNames) { - boolean found = false; - for (PsiMethod method : declaration.findMethodsByName(methodName, true)) { - // Make sure it's not the base method, but that it's been defined - // in a subclass, concretely - PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - continue; - } - if (NOTIFICATION_LISTENER_SERVICE_FQN.equals(containingClass.getQualifiedName())) { - continue; - } - // Make sure subclass isn't just defining another abstract definition - // of the method - if (evaluator.isAbstract(method)) { - continue; - } - // Make sure it has the exact right signature - if (method.getParameterList().getParametersCount() != 1) { - continue; // Wrong signature - } - if (!evaluator.parameterHasType(method, 0, STATUS_BAR_NOTIFICATION_FQN)) { - continue; - } - - found = true; - break; - } - - if (!found) { - String message = String.format( - "Must override `%1$s.%2$s(%3$s)`: Method was abstract until %4$d, and your `minSdkVersion` is %5$d", - NOTIFICATION_LISTENER_SERVICE_FQN, methodName, - STATUS_BAR_NOTIFICATION_FQN, CONCRETE_IN, minSdk); - context.reportUast(ISSUE, declaration, context.getUastNameLocation(declaration), message); - break; - } - - } - } - - private static int getTargetApi(@NonNull PsiClass node) { - while (node != null) { - int targetApi = ApiDetector.getTargetApi(node.getModifierList()); - if (targetApi != -1) { - return targetApi; - } - - node = PsiTreeUtil.getParentOfType(node, PsiClass.class, true); - } - - return -1; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java deleted file mode 100644 index e5b66db..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java +++ /dev/null
@@ -1,111 +0,0 @@ -/* - * 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.klint.checks; - -import static com.android.SdkConstants.CLASS_PARCELABLE; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiModifier; - -import com.intellij.psi.util.InheritanceUtil; -import kotlinx.android.parcel.Parcelize; -import org.jetbrains.uast.UAnonymousClass; -import org.jetbrains.uast.UClass; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for Parcelable classes that are missing a CREATOR field - */ -public class ParcelDetector extends Detector implements Detector.UastScanner { - - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "ParcelCreator", //$NON-NLS-1$ - "Missing Parcelable `CREATOR` field", - - "According to the `Parcelable` interface documentation, " + - "\"Classes implementing the Parcelable interface must also have a " + - "static field called `CREATOR`, which is an object implementing the " + - "`Parcelable.Creator` interface.\"", - - Category.CORRECTNESS, - 3, - Severity.ERROR, - new Implementation( - ParcelDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html"); - - /** Constructs a new {@link ParcelDetector} check */ - public ParcelDetector() { - } - - // ---- Implements JavaScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList(CLASS_PARCELABLE); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - if (declaration instanceof UAnonymousClass) { - // Anonymous classes aren't parcelable - return; - } - - // Only applies to concrete classes - if (declaration.isInterface()) { - return; - } - if (declaration.hasModifierProperty(PsiModifier.ABSTRACT)) { - return; - } - - // Parceling spans is handled in TextUtils#CHAR_SEQUENCE_CREATOR - if (InheritanceUtil.isInheritor(declaration, false, "android.text.ParcelableSpan")) { - return; - } - - // Do not report errors on our Android Extensions-improved Parcelables - if (declaration.findAnnotation(Parcelize.class.getName()) != null) { - return; - } - - PsiField field = declaration.findFieldByName("CREATOR", false); - if (field == null) { - Location location = context.getUastNameLocation(declaration); - context.reportUast(ISSUE, declaration, location, - "This class implements `Parcelable` but does not " - + "provide a `CREATOR` field"); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java deleted file mode 100644 index c95c3bf..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java +++ /dev/null
@@ -1,224 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.SdkConstants.CLASS_INTENT; -import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION; -import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_READ; -import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_WRITE; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.UastLintUtils; -import com.android.tools.klint.detector.api.JavaContext; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiVariable; - -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; - -import java.util.List; - -/** - * Utility for locating permissions required by an intent or content resolver - */ -public class PermissionFinder { - /** - * Operation that has a permission requirement -- such as a method call, - * a content resolver read or write operation, an intent, etc. - */ - public enum Operation { - CALL, ACTION, READ, WRITE; - - /** Prefix to use when describing a name with a permission requirement */ - public String prefix() { - switch (this) { - case ACTION: - return "by intent"; - case READ: - return "to read"; - case WRITE: - return "to write"; - case CALL: - default: - return "by"; - } - } - } - - /** A permission requirement given a name and operation */ - public static class Result { - @NonNull public final PermissionRequirement requirement; - @NonNull public final String name; - @NonNull public final Operation operation; - - public Result( - @NonNull Operation operation, - @NonNull PermissionRequirement requirement, - @NonNull String name) { - this.operation = operation; - this.requirement = requirement; - this.name = name; - } - } - - /** - * Searches for a permission requirement for the given parameter in the given call - * - * @param operation the operation to look up - * @param context the context to use for lookup - * @param parameter the parameter which contains the value which implies the permission - * @return the result with the permission requirement, or null if nothing is found - */ - @Nullable - public static Result findRequiredPermissions( - @NonNull Operation operation, - @NonNull JavaContext context, - @NonNull UElement parameter) { - - // To find the permission required by an intent, we proceed in 3 steps: - // (1) Locate the parameter in the start call that corresponds to - // the Intent - // - // (2) Find the place where the intent is initialized, and figure - // out the action name being passed to it. - // - // (3) Find the place where the action is defined, and look for permission - // annotations on that action declaration! - - return new PermissionFinder(context, operation).search(parameter); - } - - private PermissionFinder(@NonNull JavaContext context, @NonNull Operation operation) { - mContext = context; - mOperation = operation; - } - - @NonNull private final JavaContext mContext; - @NonNull private final Operation mOperation; - - @Nullable - public Result search(@NonNull UElement node) { - if (UastLiteralUtils.isNullLiteral(node)) { - return null; - } else if (node instanceof UIfExpression) { - UIfExpression expression = (UIfExpression) node; - if (expression.getThenExpression() != null) { - Result result = search(expression.getThenExpression()); - if (result != null) { - return result; - } - } - if (expression.getElseExpression() != null) { - Result result = search(expression.getElseExpression()); - if (result != null) { - return result; - } - } - } else if (UastExpressionUtils.isTypeCast(node)) { - UBinaryExpressionWithType cast = (UBinaryExpressionWithType) node; - UExpression operand = cast.getOperand(); - return search(operand); - } else if (node instanceof UParenthesizedExpression) { - UParenthesizedExpression parens = (UParenthesizedExpression) node; - UExpression expression = parens.getExpression(); - if (expression != null) { - return search(expression); - } - } else if (UastExpressionUtils.isConstructorCall(node) && mOperation == Operation.ACTION) { - // Identifies "new Intent(argument)" calls and, if found, continues - // resolving the argument instead looking for the action definition - UCallExpression call = (UCallExpression) node; - UReferenceExpression classReference = call.getClassReference(); - String type = classReference != null ? UastUtils.getQualifiedName(classReference) : null; - if (CLASS_INTENT.equals(type)) { - List<UExpression> expressions = call.getValueArguments(); - if (!expressions.isEmpty()) { - UExpression action = expressions.get(0); - if (action != null) { - return search(action); - } - } - } - return null; - } else if (node instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) node).resolve(); - if (resolved instanceof PsiField) { - UField field = (UField) mContext.getUastContext().convertElementWithParent(resolved, UField.class); - if (field == null) { - return null; - } - if (mOperation == Operation.ACTION) { - UAnnotation annotation = field.findAnnotation(PERMISSION_ANNOTATION); - if (annotation != null) { - return getPermissionRequirement(field, annotation); - } - } else if (mOperation == Operation.READ || mOperation == Operation.WRITE) { - String fqn = mOperation == Operation.READ - ? PERMISSION_ANNOTATION_READ : PERMISSION_ANNOTATION_WRITE; - UAnnotation annotation = field.findAnnotation(fqn); - if (annotation != null) { - List<UNamedExpression> attributes = annotation.getAttributeValues(); - UNamedExpression o = attributes.size() == 1 ? attributes.get(0) : null; - if (o != null && o.getExpression() instanceof UAnnotation) { - annotation = (UAnnotation) o.getExpression(); - if (PERMISSION_ANNOTATION.equals(annotation.getQualifiedName())) { - return getPermissionRequirement(field, annotation); - } - } else { - // The complex annotations used for read/write cannot be - // expressed in the external annotations format, so they're inlined. - // (See Extractor.AnnotationData#write). - // - // Instead we've inlined the fields of the annotation on the - // outer one: - return getPermissionRequirement(field, annotation); - } - } - } else { - assert false : mOperation; - } - } - - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - UExpression lastAssignment = - UastLintUtils.findLastAssignment(variable, node, mContext); - - if (lastAssignment != null) { - return search(lastAssignment); - } - } - } - - return null; - } - - @NonNull - private Result getPermissionRequirement( - @NonNull PsiField field, - @NonNull UAnnotation annotation) { - PermissionRequirement requirement = PermissionRequirement.create(annotation); - PsiClass containingClass = field.getContainingClass(); - String name = containingClass != null - ? containingClass.getName() + "." + field.getName() - : field.getName(); - assert name != null; - return new Result(mOperation, requirement, name); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionHolder.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionHolder.java deleted file mode 100644 index 32ff92d..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionHolder.java +++ /dev/null
@@ -1,149 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.sdklib.AndroidVersion; - -import java.util.Collections; -import java.util.Set; - -/** - * A {@linkplain PermissionHolder} knows which permissions are held/granted and can look up - * individual permissions and respond to queries about whether they are held or not. - */ -public interface PermissionHolder { - - /** Returns true if the permission holder has been granted the given permission */ - boolean hasPermission(@NonNull String permission); - - /** Returns true if the given permission is known to be revocable for targetSdkVersion ≥ M */ - boolean isRevocable(@NonNull String permission); - - @NonNull - AndroidVersion getMinSdkVersion(); - - @NonNull - AndroidVersion getTargetSdkVersion(); - - /** - * A convenience implementation of {@link PermissionHolder} backed by a set - */ - class SetPermissionLookup implements PermissionHolder { - private final Set<String> mGrantedPermissions; - private final Set<String> mRevocablePermissions; - private final AndroidVersion mMinSdkVersion; - private final AndroidVersion mTargetSdkVersion; - - public SetPermissionLookup( - @NonNull Set<String> grantedPermissions, - @NonNull Set<String> revocablePermissions, - @NonNull AndroidVersion minSdkVersion, - @NonNull AndroidVersion targetSdkVersion) { - mGrantedPermissions = grantedPermissions; - mRevocablePermissions = revocablePermissions; - mMinSdkVersion = minSdkVersion; - mTargetSdkVersion = targetSdkVersion; - } - - @VisibleForTesting - public SetPermissionLookup(@NonNull Set<String> grantedPermissions, - @NonNull Set<String> revocablePermissions) { - this(grantedPermissions, revocablePermissions, AndroidVersion.DEFAULT, - AndroidVersion.DEFAULT); - } - - @VisibleForTesting - public SetPermissionLookup(@NonNull Set<String> grantedPermissions) { - this(grantedPermissions, Collections.<String>emptySet()); - } - - @Override - public boolean hasPermission(@NonNull String permission) { - return mGrantedPermissions.contains(permission); - } - - @Override - public boolean isRevocable(@NonNull String permission) { - return mRevocablePermissions.contains(permission); - } - - @NonNull - @Override - public AndroidVersion getMinSdkVersion() { - return mMinSdkVersion; - } - - @NonNull - @Override - public AndroidVersion getTargetSdkVersion() { - return mTargetSdkVersion; - } - - /** - * Creates a {@linkplain PermissionHolder} which combines the permissions - * held by the given holder, with the permissions implied by the given - * {@link PermissionRequirement} - */ - @NonNull - public static PermissionHolder join(@NonNull PermissionHolder lookup, - @NonNull PermissionRequirement requirement) { - SetPermissionLookup empty = new SetPermissionLookup(Collections.<String>emptySet(), - Collections.<String>emptySet(), lookup.getMinSdkVersion(), - lookup.getTargetSdkVersion()); - return join(lookup, requirement.getMissingPermissions(empty)); - } - - /** - * Creates a {@linkplain PermissionHolder} which combines the permissions - * held by the given holder, along with a set of additional permission names - */ - @NonNull - public static PermissionHolder join(@NonNull final PermissionHolder lookup, - @Nullable final Set<String> permissions) { - if (permissions != null && !permissions.isEmpty()) { - return new PermissionHolder() { - @Override - public boolean hasPermission(@NonNull String permission) { - return lookup.hasPermission(permission) - || permissions.contains(permission); - } - - @Override - public boolean isRevocable(@NonNull String permission) { - return lookup.isRevocable(permission); - } - - @NonNull - @Override - public AndroidVersion getMinSdkVersion() { - return lookup.getMinSdkVersion(); - } - - @NonNull - @Override - public AndroidVersion getTargetSdkVersion() { - return lookup.getTargetSdkVersion(); - } - }; - } - return lookup; - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java deleted file mode 100644 index ef5ba74..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java +++ /dev/null
@@ -1,655 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.sdklib.AndroidVersion; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.intellij.psi.JavaTokenType; -import com.intellij.psi.tree.IElementType; -import org.jetbrains.uast.UAnnotation; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.util.UastExpressionUtils; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static com.android.SdkConstants.ATTR_VALUE; -import static com.android.tools.klint.checks.SupportAnnotationDetector.*; - -/** - * A permission requirement is a boolean expression of permission names that a - * caller must satisfy for a given Android API. - */ -public abstract class PermissionRequirement { - public static final String ATTR_PROTECTION_LEVEL = "protectionLevel"; //$NON-NLS-1$ - public static final String VALUE_DANGEROUS = "dangerous"; //$NON-NLS-1$ - - protected final UAnnotation annotation; - private int firstApi; - private int lastApi; - - @SuppressWarnings("ConstantConditions") - public static final PermissionRequirement NONE = new PermissionRequirement(null) { - @Override - public boolean isSatisfied(@NonNull PermissionHolder available) { - return true; - } - - @Override - public boolean appliesTo(@NonNull PermissionHolder available) { - return false; - } - - @Override - public boolean isConditional() { - return false; - } - - @Override - public boolean isRevocable(@NonNull PermissionHolder revocable) { - return false; - } - - @Override - public String toString() { - return "None"; - } - - @Override - protected void addMissingPermissions(@NonNull PermissionHolder available, - @NonNull Set<String> result) { - } - - @Override - protected void addRevocablePermissions(@NonNull Set<String> result, - @NonNull PermissionHolder revocable) { - } - - @Nullable - @Override - public IElementType getOperator() { - return null; - } - - @NonNull - @Override - public Iterable<PermissionRequirement> getChildren() { - return Collections.emptyList(); - } - }; - - private PermissionRequirement(@NonNull UAnnotation annotation) { - this.annotation = annotation; - } - - @NonNull - public static PermissionRequirement create(@NonNull UAnnotation annotation) { - String value = getAnnotationStringValue(annotation, ATTR_VALUE); - if (value != null && !value.isEmpty()) { - return new Single(annotation, value); - } - - String[] anyOf = getAnnotationStringValues(annotation, ATTR_ANY_OF); - if (anyOf != null) { - if (anyOf.length > 1) { - return new Many(annotation, JavaTokenType.OROR, anyOf); - } else if (anyOf.length == 1) { - return new Single(annotation, anyOf[0]); - } - } - - String[] allOf = getAnnotationStringValues(annotation, ATTR_ALL_OF); - if (allOf != null) { - if (allOf.length > 1) { - return new Many(annotation, JavaTokenType.ANDAND, allOf); - } else if (allOf.length == 1) { - return new Single(annotation, allOf[0]); - } - } - - return NONE; - } - - @Nullable - public static Boolean getAnnotationBooleanValue(@Nullable UAnnotation annotation, - @NonNull String name) { - if (annotation != null) { - UExpression attributeValue = annotation.findDeclaredAttributeValue(name); - if (attributeValue == null && ATTR_VALUE.equals(name)) { - attributeValue = annotation.findDeclaredAttributeValue(null); - } - // Use constant evaluator since we want to resolve field references as well - if (attributeValue != null) { - Object o = ConstantEvaluator.evaluate(null, attributeValue); - if (o instanceof Boolean) { - return (Boolean) o; - } - } - } - - return null; - } - - @Nullable - public static Long getAnnotationLongValue(@Nullable UAnnotation annotation, - @NonNull String name) { - if (annotation != null) { - UExpression attributeValue = annotation.findDeclaredAttributeValue(name); - if (attributeValue == null && ATTR_VALUE.equals(name)) { - attributeValue = annotation.findDeclaredAttributeValue(null); - } - // Use constant evaluator since we want to resolve field references as well - if (attributeValue != null) { - Object o = ConstantEvaluator.evaluate(null, attributeValue); - if (o instanceof Number) { - return ((Number)o).longValue(); - } - } - } - - return null; - } - - @Nullable - public static Double getAnnotationDoubleValue(@Nullable UAnnotation annotation, - @NonNull String name) { - if (annotation != null) { - UExpression attributeValue = annotation.findDeclaredAttributeValue(name); - if (attributeValue == null && ATTR_VALUE.equals(name)) { - attributeValue = annotation.findDeclaredAttributeValue(null); - } - // Use constant evaluator since we want to resolve field references as well - if (attributeValue != null) { - Object o = ConstantEvaluator.evaluate(null, attributeValue); - if (o instanceof Number) { - return ((Number)o).doubleValue(); - } - } - } - - return null; - } - - @Nullable - public static String getAnnotationStringValue(@Nullable UAnnotation annotation, - @NonNull String name) { - if (annotation != null) { - UExpression attributeValue = annotation.findDeclaredAttributeValue(name); - if (attributeValue == null && ATTR_VALUE.equals(name)) { - attributeValue = annotation.findDeclaredAttributeValue(null); - } - // Use constant evaluator since we want to resolve field references as well - if (attributeValue != null) { - Object o = ConstantEvaluator.evaluate(null, attributeValue); - if (o instanceof String) { - return (String) o; - } - } - } - - return null; - } - - @Nullable - public static String[] getAnnotationStringValues(@Nullable UAnnotation annotation, - @NonNull String name) { - if (annotation != null) { - UExpression attributeValue = annotation.findDeclaredAttributeValue(name); - if (attributeValue == null && ATTR_VALUE.equals(name)) { - attributeValue = annotation.findDeclaredAttributeValue(null); - } - if (attributeValue == null) { - return null; - } - if (UastExpressionUtils.isArrayInitializer(attributeValue)) { - List<UExpression> initializers = - ((UCallExpression) attributeValue).getValueArguments(); - List<String> result = Lists.newArrayListWithCapacity(initializers.size()); - ConstantEvaluator constantEvaluator = new ConstantEvaluator(null); - for (UExpression element : initializers) { - Object o = constantEvaluator.evaluate(element); - if (o instanceof String) { - result.add((String)o); - } - } - if (result.isEmpty()) { - return null; - } else { - return result.toArray(new String[0]); - } - } else { - // Use constant evaluator since we want to resolve field references as well - Object o = ConstantEvaluator.evaluate(null, attributeValue); - if (o instanceof String) { - return new String[]{(String) o}; - } else if (o instanceof String[]) { - return (String[])o; - } else if (o instanceof Object[]) { - Object[] array = (Object[]) o; - List<String> strings = Lists.newArrayListWithCapacity(array.length); - for (Object element : array) { - if (element instanceof String) { - strings.add((String) element); - } - } - return strings.toArray(new String[0]); - } - } - } - - return null; - } - - /** - * Returns false if this permission does not apply given the specified minimum and - * target sdk versions - * - * @param minSdkVersion the minimum SDK version - * @param targetSdkVersion the target SDK version - * @return true if this permission requirement applies for the given versions - */ - /** - * Returns false if this permission does not apply given the specified minimum and target - * sdk versions - * - * @param available the permission holder which also knows the min and target versions - * @return true if this permission requirement applies for the given versions - */ - protected boolean appliesTo(@NonNull PermissionHolder available) { - if (firstApi == 0) { // initialized? - firstApi = -1; // initialized, not specified - - // Not initialized - String range = getAnnotationStringValue(annotation, "apis"); - if (range != null) { - // Currently only support the syntax "a..b" where a and b are inclusive end points - // and where "a" and "b" are optional - int index = range.indexOf(".."); - if (index != -1) { - try { - if (index > 0) { - firstApi = Integer.parseInt(range.substring(0, index)); - } else { - firstApi = 1; - } - if (index + 2 < range.length()) { - lastApi = Integer.parseInt(range.substring(index + 2)); - } else { - lastApi = Integer.MAX_VALUE; - } - } catch (NumberFormatException ignore) { - } - } - } - } - - if (firstApi != -1) { - AndroidVersion minSdkVersion = available.getMinSdkVersion(); - if (minSdkVersion.getFeatureLevel() > lastApi) { - return false; - } - - AndroidVersion targetSdkVersion = available.getTargetSdkVersion(); - if (targetSdkVersion.getFeatureLevel() < firstApi) { - return false; - } - } - return true; - } - - /** - * Returns whether this requirement is conditional, meaning that there are - * some circumstances in which the requirement is not necessary. For - * example, consider - * {@code android.app.backup.BackupManager.dataChanged(java.lang.String)} . - * Here the {@code android.permission.BACKUP} is required but only if the - * argument is not your own package. - * <p> - * This is used to handle permissions differently between the "missing" and - * "unused" checks. When checking for missing permissions, we err on the - * side of caution: if you are missing a permission, but the permission is - * conditional, you may not need it so we may not want to complain. However, - * when looking for unused permissions, we don't want to flag the - * conditional permissions as unused since they may be required. - * - * @return true if this requirement is conditional - */ - public boolean isConditional() { - Boolean o = getAnnotationBooleanValue(annotation, ATTR_CONDITIONAL); - if (o != null) { - return o; - } - return false; - } - - /** - * Returns whether this requirement is for a single permission (rather than - * a boolean expression such as one permission or another.) - * - * @return true if this requirement is just a simple permission name - */ - public boolean isSingle() { - return true; - } - - /** - * Whether the permission requirement is satisfied given the set of granted permissions - * - * @param available the available permissions - * @return true if all permissions specified by this requirement are available - */ - public abstract boolean isSatisfied(@NonNull PermissionHolder available); - - /** Describes the missing permissions (e.g. "P1, P2 and P3") */ - public String describeMissingPermissions(@NonNull PermissionHolder available) { - return ""; - } - - /** Returns the missing permissions (e.g. {"P1", "P2", "P3"} */ - public Set<String> getMissingPermissions(@NonNull PermissionHolder available) { - Set<String> result = Sets.newHashSet(); - addMissingPermissions(available, result); - return result; - } - - protected abstract void addMissingPermissions(@NonNull PermissionHolder available, - @NonNull Set<String> result); - - /** Returns the permissions in the requirement that are revocable */ - public Set<String> getRevocablePermissions(@NonNull PermissionHolder revocable) { - Set<String> result = Sets.newHashSet(); - addRevocablePermissions(result, revocable); - return result; - } - - protected abstract void addRevocablePermissions(@NonNull Set<String> result, - @NonNull PermissionHolder revocable); - - /** - * Returns whether this permission is revocable - * - * @param revocable the set of revocable permissions - * @return true if a user can revoke the permission - */ - public abstract boolean isRevocable(@NonNull PermissionHolder revocable); - - /** - * For permission requirements that combine children, the operator to combine them with; null - * for leaf nodes - */ - @Nullable - public abstract IElementType getOperator(); - - /** - * Returns nested requirements, combined via {@link #getOperator()} - */ - @NonNull - public abstract Iterable<PermissionRequirement> getChildren(); - - /** Require a single permission */ - private static class Single extends PermissionRequirement { - public final String name; - - public Single(@NonNull UAnnotation annotation, @NonNull String name) { - super(annotation); - this.name = name; - } - - @Override - public boolean isRevocable(@NonNull PermissionHolder revocable) { - return revocable.isRevocable(name) || isRevocableSystemPermission(name); - } - - @Nullable - @Override - public IElementType getOperator() { - return null; - } - - @NonNull - @Override - public Iterable<PermissionRequirement> getChildren() { - return Collections.emptyList(); - } - - @Override - public boolean isSingle() { - return true; - } - - @Override - public String toString() { - return name; - } - - @Override - public boolean isSatisfied(@NonNull PermissionHolder available) { - return available.hasPermission(name) || !appliesTo(available); - } - - @Override - public String describeMissingPermissions(@NonNull PermissionHolder available) { - return isSatisfied(available) ? "" : name; - } - - @Override - protected void addMissingPermissions(@NonNull PermissionHolder available, - @NonNull Set<String> missing) { - if (!isSatisfied(available)) { - missing.add(name); - } - } - - @Override - protected void addRevocablePermissions(@NonNull Set<String> result, - @NonNull PermissionHolder revocable) { - if (isRevocable(revocable)) { - result.add(name); - } - } - } - - protected static void appendOperator(StringBuilder sb, IElementType operator) { - sb.append(' '); - if (operator == JavaTokenType.ANDAND) { - sb.append("and"); - } else if (operator == JavaTokenType.OROR) { - sb.append("or"); - } else { - assert operator == JavaTokenType.XOR : operator; - sb.append("xor"); - } - sb.append(' '); - } - - /** - * Require a series of permissions, all with the same operator. - */ - private static class Many extends PermissionRequirement { - public final IElementType operator; - public final List<PermissionRequirement> permissions; - - public Many( - @NonNull UAnnotation annotation, - IElementType operator, - String[] names) { - super(annotation); - assert operator == JavaTokenType.OROR - || operator == JavaTokenType.ANDAND : operator; - assert names.length >= 2; - this.operator = operator; - this.permissions = Lists.newArrayListWithExpectedSize(names.length); - for (String name : names) { - permissions.add(new Single(annotation, name)); - } - } - - @Override - public boolean isSingle() { - return false; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append(permissions.get(0)); - - for (int i = 1; i < permissions.size(); i++) { - appendOperator(sb, operator); - sb.append(permissions.get(i)); - } - - return sb.toString(); - } - - @Override - public boolean isSatisfied(@NonNull PermissionHolder available) { - if (operator == JavaTokenType.ANDAND) { - for (PermissionRequirement requirement : permissions) { - if (!requirement.isSatisfied(available) && requirement.appliesTo(available)) { - return false; - } - } - return true; - } else { - assert operator == JavaTokenType.OROR : operator; - for (PermissionRequirement requirement : permissions) { - if (requirement.isSatisfied(available) || !requirement.appliesTo(available)) { - return true; - } - } - return false; - } - } - - @Override - public String describeMissingPermissions(@NonNull PermissionHolder available) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (PermissionRequirement requirement : permissions) { - if (!requirement.isSatisfied(available)) { - if (first) { - first = false; - } else { - appendOperator(sb, operator); - } - sb.append(requirement.describeMissingPermissions(available)); - } - } - return sb.toString(); - } - - @Override - protected void addMissingPermissions(@NonNull PermissionHolder available, - @NonNull Set<String> missing) { - for (PermissionRequirement requirement : permissions) { - if (!requirement.isSatisfied(available)) { - requirement.addMissingPermissions(available, missing); - } - } - } - - @Override - protected void addRevocablePermissions(@NonNull Set<String> result, - @NonNull PermissionHolder revocable) { - for (PermissionRequirement requirement : permissions) { - requirement.addRevocablePermissions(result, revocable); - } - } - - @Override - public boolean isRevocable(@NonNull PermissionHolder revocable) { - // TODO: Pass in the available set of permissions here, and if - // the operator is JavaTokenType.OROR, only return revocable=true - // if an unsatisfied permission is also revocable. In other words, - // if multiple permissions are allowed, and some of them are satisfied and - // not revocable the overall permission requirement is not revocable. - for (PermissionRequirement requirement : permissions) { - if (requirement.isRevocable(revocable)) { - return true; - } - } - return false; - } - - @Nullable - @Override - public IElementType getOperator() { - return operator; - } - - @NonNull - @Override - public Iterable<PermissionRequirement> getChildren() { - return permissions; - } - } - - /** - * Returns true if the given permission name is a revocable permission for - * targetSdkVersion ≥ 23 - * - * @param name permission name - * @return true if this is a revocable permission - */ - public static boolean isRevocableSystemPermission(@NonNull String name) { - return Arrays.binarySearch(REVOCABLE_PERMISSION_NAMES, name) >= 0; - } - - @VisibleForTesting - static final String[] REVOCABLE_PERMISSION_NAMES = new String[] { - "android.permission.ACCESS_COARSE_LOCATION", - "android.permission.ACCESS_FINE_LOCATION", - "android.permission.BODY_SENSORS", - "android.permission.CALL_PHONE", - "android.permission.CAMERA", - "android.permission.PROCESS_OUTGOING_CALLS", - "android.permission.READ_CALENDAR", - "android.permission.READ_CALL_LOG", - "android.permission.READ_CELL_BROADCASTS", - "android.permission.READ_CONTACTS", - "android.permission.READ_EXTERNAL_STORAGE", - "android.permission.READ_PHONE_STATE", - "android.permission.READ_PROFILE", - "android.permission.READ_SMS", - "android.permission.READ_SOCIAL_STREAM", - "android.permission.RECEIVE_MMS", - "android.permission.RECEIVE_SMS", - "android.permission.RECEIVE_WAP_PUSH", - "android.permission.RECORD_AUDIO", - "android.permission.SEND_SMS", - "android.permission.USE_FINGERPRINT", - "android.permission.USE_SIP", - "android.permission.WRITE_CALENDAR", - "android.permission.WRITE_CALL_LOG", - "android.permission.WRITE_CONTACTS", - "android.permission.WRITE_EXTERNAL_STORAGE", - "android.permission.WRITE_SETTINGS", - "android.permission.WRITE_PROFILE", - "android.permission.WRITE_SOCIAL_STREAM", - "com.android.voicemail.permission.ADD_VOICEMAIL", - }; -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PluralsDatabase.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PluralsDatabase.java deleted file mode 100644 index 8bea3e7..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PluralsDatabase.java +++ /dev/null
@@ -1,333 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import static com.android.tools.klint.checks.PluralsDatabase.Quantity.few; -import static com.android.tools.klint.checks.PluralsDatabase.Quantity.many; -import static com.android.tools.klint.checks.PluralsDatabase.Quantity.one; -import static com.android.tools.klint.checks.PluralsDatabase.Quantity.two; -import static com.android.tools.klint.checks.PluralsDatabase.Quantity.zero; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.LintUtils; -import com.google.common.collect.Maps; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -/** - * Database used by the {@link PluralsDetector} to get information - * about plural forms for a given language - */ -public class PluralsDatabase { - private static final EnumSet<Quantity> NONE = EnumSet.noneOf(Quantity.class); - - private static final PluralsDatabase sInstance = new PluralsDatabase(); - private final Map<String, EnumSet<Quantity>> mPlurals = Maps.newHashMap(); - - /** Bit set if this language uses quantity zero */ - @SuppressWarnings("PointlessBitwiseExpression") - static final int FLAG_ZERO = 1 << 0; - /** Bit set if this language uses quantity one */ - static final int FLAG_ONE = 1 << 1; - /** Bit set if this language uses quantity two */ - static final int FLAG_TWO = 1 << 2; - /** Bit set if this language uses quantity few */ - static final int FLAG_FEW = 1 << 3; - /** Bit set if this language uses quantity many */ - static final int FLAG_MANY = 1 << 4; - /** Bit set if this language has multiple values that match quantity zero */ - static final int FLAG_MULTIPLE_ZERO = 1 << 5; - /** Bit set if this language has multiple values that match quantity one */ - static final int FLAG_MULTIPLE_ONE = 1 << 6; - /** Bit set if this language has multiple values that match quantity two */ - static final int FLAG_MULTIPLE_TWO = 1 << 7; - - @NonNull - public static PluralsDatabase get() { - return sInstance; - } - - private static int getFlags(@NonNull String language) { - int index = getLanguageIndex(language); - if (index != -1) { - return FLAGS[index]; - } - return 0; - } - - private static int getLanguageIndex(@NonNull String language) { - int index = Arrays.binarySearch(LANGUAGE_CODES, language); - if (index >= 0) { - assert LANGUAGE_CODES[index].equals(language); - return index; - } else { - return -1; - } - } - - @Nullable - public EnumSet<Quantity> getRelevant(@NonNull String language) { - EnumSet<Quantity> set = mPlurals.get(language); - if (set == null) { - int index = getLanguageIndex(language); - if (index == -1) { - mPlurals.put(language, NONE); - return null; - } - - // Process each item and look for relevance - int flag = FLAGS[index]; - - set = EnumSet.noneOf(Quantity.class); - if ((flag & FLAG_ZERO) != 0) { - set.add(zero); - } - if ((flag & FLAG_ONE) != 0) { - set.add(one); - } - if ((flag & FLAG_TWO) != 0) { - set.add(two); - } - if ((flag & FLAG_FEW) != 0) { - set.add(few); - } - if ((flag & FLAG_MANY) != 0) { - set.add(many); - } - - mPlurals.put(language, set); - } - return set == NONE ? null : set; - } - - @SuppressWarnings("MethodMayBeStatic") - public boolean hasMultipleValuesForQuantity( - @NonNull String language, - @NonNull Quantity quantity) { - if (quantity == one) { - return (getFlags(language) & FLAG_MULTIPLE_ONE) != 0; - } else if (quantity == two) { - return (getFlags(language) & FLAG_MULTIPLE_TWO) != 0; - } else { - return quantity == zero && (getFlags(language) & FLAG_MULTIPLE_ZERO) != 0; - } - } - - @SuppressWarnings("MethodMayBeStatic") - @Nullable - public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) { - if (quantity == one) { - return getExampleForQuantityOne(language); - } else if (quantity == two) { - return getExampleForQuantityTwo(language); - } else if (quantity == zero) { - return getExampleForQuantityZero(language); - } else { - return null; - } - } - - public enum Quantity { - // deliberately lower case to match attribute names - few, many, one, two, zero, other; - - @Nullable - public static Quantity get(@NonNull String name) { - for (Quantity quantity : values()) { - if (name.equals(quantity.name())) { - return quantity; - } - } - - return null; - } - - public static String formatSet(@NonNull EnumSet<Quantity> set) { - List<String> list = new ArrayList<String>(set.size()); - for (Quantity quantity : set) { - list.add('`' + quantity.name() + '`'); - } - return LintUtils.formatList(list, Integer.MAX_VALUE); - } - } - - // GENERATED DATA. - // This data is generated by the #testDatabaseAccurate method in PluralsDatabaseTest - // which will generate the following if it can find an ICU plurals database file - // in the unit test data folder. - - /** Set of language codes relevant to plurals data */ - private static final String[] LANGUAGE_CODES = new String[] { - "af", "ak", "am", "ar", "as", "az", "be", "bg", "bh", "bm", - "bn", "bo", "br", "bs", "ca", "ce", "cs", "cy", "da", "de", - "dv", "dz", "ee", "el", "en", "eo", "es", "et", "eu", "fa", - "ff", "fi", "fo", "fr", "fy", "ga", "gd", "gl", "gu", "gv", - "ha", "he", "hi", "hr", "hu", "hy", "id", "ig", "ii", "in", - "is", "it", "iu", "iw", "ja", "ji", "jv", "ka", "kk", "kl", - "km", "kn", "ko", "ks", "ku", "kw", "ky", "lb", "lg", "ln", - "lo", "lt", "lv", "mg", "mk", "ml", "mn", "mr", "ms", "mt", - "my", "nb", "nd", "ne", "nl", "nn", "no", "nr", "ny", "om", - "or", "os", "pa", "pl", "ps", "pt", "rm", "ro", "ru", "se", - "sg", "si", "sk", "sl", "sn", "so", "sq", "sr", "ss", "st", - "sv", "sw", "ta", "te", "th", "ti", "tk", "tl", "tn", "to", - "tr", "ts", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", - "wo", "xh", "yi", "yo", "zh", "zu" - }; - - /** - * Relevant flags for each language (corresponding to each language listed - * in the same position in {@link #LANGUAGE_CODES}) - */ - private static final int[] FLAGS = new int[] { - 0x0002, 0x0042, 0x0042, 0x001f, 0x0042, 0x0002, 0x005a, 0x0002, - 0x0042, 0x0000, 0x0042, 0x0000, 0x00de, 0x004a, 0x0002, 0x0002, - 0x000a, 0x001f, 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0002, - 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0042, 0x0042, 0x0002, - 0x0002, 0x0042, 0x0002, 0x001e, 0x00ce, 0x0002, 0x0042, 0x00ce, - 0x0002, 0x0016, 0x0042, 0x004a, 0x0002, 0x0042, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0042, 0x0002, 0x0006, 0x0016, 0x0000, 0x0002, - 0x0000, 0x0002, 0x0002, 0x0002, 0x0000, 0x0042, 0x0000, 0x0002, - 0x0002, 0x0006, 0x0002, 0x0002, 0x0002, 0x0042, 0x0000, 0x004a, - 0x0063, 0x0042, 0x0042, 0x0002, 0x0002, 0x0042, 0x0000, 0x001a, - 0x0000, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, - 0x0002, 0x0002, 0x0002, 0x0002, 0x0042, 0x001a, 0x0002, 0x0042, - 0x0002, 0x000a, 0x005a, 0x0006, 0x0000, 0x0042, 0x000a, 0x00ce, - 0x0002, 0x0002, 0x0002, 0x004a, 0x0002, 0x0002, 0x0002, 0x0002, - 0x0002, 0x0002, 0x0000, 0x0042, 0x0002, 0x0042, 0x0002, 0x0000, - 0x0002, 0x0002, 0x0002, 0x005a, 0x0002, 0x0002, 0x0002, 0x0000, - 0x0002, 0x0042, 0x0000, 0x0002, 0x0002, 0x0000, 0x0000, 0x0042 - }; - - @Nullable - private static String getExampleForQuantityZero(@NonNull String language) { - int index = getLanguageIndex(language); - switch (index) { - // set14 - case 72: // lv - return "0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, \u2026"; - case -1: - default: - return null; - } - } - - @Nullable - private static String getExampleForQuantityOne(@NonNull String language) { - int index = getLanguageIndex(language); - switch (index) { - // set1 - case 2: // am - case 4: // as - case 10: // bn - case 29: // fa - case 38: // gu - case 42: // hi - case 61: // kn - case 77: // mr - case 135: // zu - return "0, 1"; - // set11 - case 50: // is - return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026"; - // set12 - case 74: // mk - return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026"; - // set13 - case 117: // tl - return "0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, \u2026"; - // set14 - case 72: // lv - return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026"; - // set2 - case 30: // ff - case 33: // fr - case 45: // hy - return "0, 1"; - // set20 - case 13: // bs - case 43: // hr - case 107: // sr - return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026"; - // set21 - case 36: // gd - return "1, 11"; - // set22 - case 103: // sl - return "1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026"; - // set27 - case 6: // be - return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026"; - // set28 - case 71: // lt - return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026"; - // set30 - case 98: // ru - case 123: // uk - return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026"; - // set31 - case 12: // br - return "1, 21, 31, 41, 51, 61, 81, 101, 1001, \u2026"; - // set33 - case 39: // gv - return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026"; - // set4 - case 101: // si - return "0, 1"; - // set5 - case 1: // ak - case 8: // bh - case 69: // ln - case 73: // mg - case 92: // pa - case 115: // ti - case 129: // wa - return "0, 1"; - // set7 - case 95: // pt - return "0, 1"; - case -1: - default: - return null; - } - } - - @Nullable - private static String getExampleForQuantityTwo(@NonNull String language) { - int index = getLanguageIndex(language); - switch (index) { - // set21 - case 36: // gd - return "2, 12"; - // set22 - case 103: // sl - return "2, 102, 202, 302, 402, 502, 602, 702, 1002, \u2026"; - // set31 - case 12: // br - return "2, 22, 32, 42, 52, 62, 82, 102, 1002, \u2026"; - // set33 - case 39: // gv - return "2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, \u2026"; - case -1: - default: - return null; - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PreferenceActivityDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PreferenceActivityDetector.java deleted file mode 100644 index 70e7d74..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PreferenceActivityDetector.java +++ /dev/null
@@ -1,164 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_PACKAGE; -import static com.android.SdkConstants.TAG_ACTIVITY; -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; - -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.uast.UClass; -import org.w3c.dom.Element; - -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Ensures that PreferenceActivity and its subclasses are never exported. - */ -public class PreferenceActivityDetector extends Detector - implements XmlScanner, Detector.UastScanner { - public static final Issue ISSUE = Issue.create( - "ExportedPreferenceActivity", //$NON-NLS-1$ - "PreferenceActivity should not be exported", - "Fragment injection gives anyone who can send your PreferenceActivity an intent the " - + "ability to load any fragment, with any arguments, in your process.", - Category.SECURITY, - 8, - Severity.WARNING, - new Implementation( - PreferenceActivityDetector.class, - EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE))) - .addMoreInfo("http://securityintelligence.com/" - + "new-vulnerability-android-framework-fragment-injection"); - private static final String PREFERENCE_ACTIVITY = "android.preference.PreferenceActivity"; //$NON-NLS-1$ - private static final String IS_VALID_FRAGMENT = "isValidFragment"; //$NON-NLS-1$ - - private final Map<String, Location.Handle> mExportedActivities = - new HashMap<String, Location.Handle>(); - - // ---- Implements XmlScanner ---- - - @Override - public Collection<String> getApplicableElements() { - return Collections.singletonList(TAG_ACTIVITY); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - if (SecurityDetector.getExported(element)) { - String fqcn = getFqcn(element); - if (fqcn != null) { - if (fqcn.equals(PREFERENCE_ACTIVITY) && - !context.getDriver().isSuppressed(context, ISSUE, element)) { - String message = "`PreferenceActivity` should not be exported"; - context.report(ISSUE, element, context.getLocation(element), message); - } - mExportedActivities.put(fqcn, context.createLocationHandle(element)); - } - } - } - - private static String getFqcn(@NonNull Element activityElement) { - String activityClassName = activityElement.getAttributeNS(ANDROID_URI, ATTR_NAME); - - if (activityClassName == null || activityClassName.isEmpty()) { - return null; - } - - // If the activity class name starts with a '.', it is shorthand for prepending the - // package name specified in the manifest. - if (activityClassName.startsWith(".")) { - String pkg = activityElement.getOwnerDocument().getDocumentElement() - .getAttribute(ATTR_PACKAGE); - if (pkg != null) { - return pkg + activityClassName; - } else { - return null; - } - } - - return activityClassName; - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList(PREFERENCE_ACTIVITY); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - if (!context.getProject().getReportIssues()) { - return; - } - JavaEvaluator evaluator = context.getEvaluator(); - String className = declaration.getQualifiedName(); - if (InheritanceUtil.isInheritor(declaration, false, PREFERENCE_ACTIVITY) - && mExportedActivities.containsKey(className)) { - // Ignore the issue if we target an API greater than 19 and the class in - // question specifically overrides isValidFragment() and thus knowingly white-lists - // valid fragments. - if (context.getMainProject().getTargetSdk() >= 19 - && overridesIsValidFragment(evaluator, declaration)) { - return; - } - - String message = String.format( - "`PreferenceActivity` subclass `%1$s` should not be exported", - className); - Location location = mExportedActivities.get(className).resolve(); - context.reportUast(ISSUE, declaration, location, message); - } - } - - private static boolean overridesIsValidFragment( - @NonNull JavaEvaluator evaluator, - @NonNull PsiClass resolvedClass) { - for (PsiMethod method : resolvedClass.findMethodsByName(IS_VALID_FRAGMENT, false)) { - if (evaluator.parametersMatch(method, TYPE_STRING)) { - return true; - } - } - return false; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PrivateResourceDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PrivateResourceDetector.java deleted file mode 100644 index 23b693c5..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PrivateResourceDetector.java +++ /dev/null
@@ -1,321 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_REF_PREFIX; -import static com.android.SdkConstants.ATTR_TYPE; -import static com.android.SdkConstants.FD_RES_VALUES; -import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE; -import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY; -import static com.android.SdkConstants.RESOURCE_CLZ_ID; -import static com.android.SdkConstants.TAG_ARRAY; -import static com.android.SdkConstants.TAG_INTEGER_ARRAY; -import static com.android.SdkConstants.TAG_ITEM; -import static com.android.SdkConstants.TAG_PLURALS; -import static com.android.SdkConstants.TAG_RESOURCES; -import static com.android.SdkConstants.TAG_STRING_ARRAY; -import static com.android.SdkConstants.TAG_STYLE; -import static com.android.SdkConstants.TOOLS_URI; -import static com.android.SdkConstants.VALUE_TRUE; -import static com.android.tools.klint.detector.api.LintUtils.getBaseName; -import static com.android.utils.SdkUtils.getResourceFieldName; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.builder.model.AndroidLibrary; -import com.android.builder.model.MavenCoordinates; -import com.android.ide.common.repository.ResourceVisibilityLookup; -import com.android.ide.common.resources.ResourceUrl; -import com.android.resources.FolderTypeRelationship; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.ResourceXmlDetector; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; - -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -/** - * Check which looks for access of private resources. - */ -public class PrivateResourceDetector extends ResourceXmlDetector implements - Detector.UastScanner { - /** Attribute for overriding a resource */ - private static final String ATTR_OVERRIDE = "override"; - - @SuppressWarnings("unchecked") - private static final Implementation IMPLEMENTATION = new Implementation( - PrivateResourceDetector.class, - Scope.JAVA_AND_RESOURCE_FILES, - Scope.JAVA_FILE_SCOPE, - Scope.RESOURCE_FILE_SCOPE); - - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "PrivateResource", //$NON-NLS-1$ - "Using private resources", - - "Private resources should not be referenced; the may not be present everywhere, and " + - "even where they are they may disappear without notice.\n" + - "\n" + - "To fix this, copy the resource into your own project instead.", - - Category.CORRECTNESS, - 3, - Severity.WARNING, - IMPLEMENTATION); - - /** Constructs a new detector */ - public PrivateResourceDetector() { - } - - // ---- Implements UastScanner ---- - - @Override - public boolean appliesToResourceRefs() { - return true; - } - - @Override - public void visitResourceReference(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UElement node, @NonNull ResourceType resourceType, @NonNull String name, - boolean isFramework) { - if (context.getProject().isGradleProject() && !isFramework) { - Project project = context.getProject(); - if (project.getGradleProjectModel() != null && project.getCurrentVariant() != null) { - if (isPrivate(context, resourceType, name)) { - String message = createUsageErrorMessage(context, resourceType, name); - context.report(ISSUE, node, context.getUastLocation(node), message); - } - } - } - } - - // ---- Implements XmlScanner ---- - - @Override - public Collection<String> getApplicableAttributes() { - return ALL; - } - - /** Check resource references: accessing a private resource from an upstream library? */ - @Override - public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { - String value = attribute.getNodeValue(); - if (context.getProject().isGradleProject()) { - ResourceUrl url = ResourceUrl.parse(value); - if (isPrivate(context, url)) { - String message = createUsageErrorMessage(context, url.type, url.name); - context.report(ISSUE, attribute, context.getValueLocation(attribute), message); - } - } - } - - /** Check resource definitions: overriding a private resource from an upstream library? */ - @Override - public Collection<String> getApplicableElements() { - return Arrays.asList( - TAG_STYLE, - TAG_RESOURCES, - TAG_ARRAY, - TAG_STRING_ARRAY, - TAG_INTEGER_ARRAY, - TAG_PLURALS - ); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - if (TAG_RESOURCES.equals(element.getTagName())) { - for (Element item : LintUtils.getChildren(element)) { - Attr nameAttribute = item.getAttributeNode(ATTR_NAME); - if (nameAttribute != null) { - String name = getResourceFieldName(nameAttribute.getValue()); - String type = item.getTagName(); - if (type.equals(TAG_ITEM)) { - type = item.getAttribute(ATTR_TYPE); - if (type == null || type.isEmpty()) { - type = RESOURCE_CLZ_ID; - } - } else if (type.equals("declare-styleable")) { //$NON-NLS-1$ - type = RESOURCE_CLR_STYLEABLE; - } else if (type.contains("array")) { //$NON-NLS-1$ - // <string-array> etc - type = RESOURCE_CLZ_ARRAY; - } - ResourceType t = ResourceType.getEnum(type); - if (t != null && isPrivate(context, t, name) && - !VALUE_TRUE.equals(item.getAttributeNS(TOOLS_URI, ATTR_OVERRIDE))) { - String message = createOverrideErrorMessage(context, t, name); - Location location = context.getValueLocation(nameAttribute); - context.report(ISSUE, nameAttribute, location, message); - } - } - } - } else { - assert TAG_STYLE.equals(element.getTagName()) - || TAG_ARRAY.equals(element.getTagName()) - || TAG_PLURALS.equals(element.getTagName()) - || TAG_INTEGER_ARRAY.equals(element.getTagName()) - || TAG_STRING_ARRAY.equals(element.getTagName()); - for (Element item : LintUtils.getChildren(element)) { - checkChildRefs(context, item); - } - } - } - - private static boolean isPrivate(Context context, ResourceType type, String name) { - if (type == ResourceType.ID) { - // No need to complain about "overriding" id's. There's no harm - // in doing so. (This avoids warning about cases like for example - // appcompat's (private) @id/title resource, which would otherwise - // flag any attempt to create a resource named title in the user's - // project. - return false; - } - - if (context.getProject().isGradleProject()) { - ResourceVisibilityLookup lookup = context.getProject().getResourceVisibility(); - return lookup.isPrivate(type, name); - } - - return false; - } - - private static boolean isPrivate(@NonNull Context context, @Nullable ResourceUrl url) { - return url != null && !url.framework && isPrivate(context, url.type, url.name); - } - - private static void checkChildRefs(@NonNull XmlContext context, Element item) { - // Look for ?attr/ and @dimen/foo etc references in the item children - NodeList childNodes = item.getChildNodes(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.TEXT_NODE) { - String text = child.getNodeValue(); - - int index = text.indexOf(ATTR_REF_PREFIX); - if (index != -1) { - String name = text.substring(index + ATTR_REF_PREFIX.length()).trim(); - if (isPrivate(context, ResourceType.ATTR, name)) { - String message = createUsageErrorMessage(context, ResourceType.ATTR, name); - context.report(ISSUE, item, context.getLocation(child), message); - } - } else { - for (int j = 0, m = text.length(); j < m; j++) { - char c = text.charAt(j); - if (c == '@') { - ResourceUrl url = ResourceUrl.parse(text.trim()); - if (isPrivate(context, url)) { - String message = createUsageErrorMessage(context, url.type, - url.name); - context.report(ISSUE, item, context.getLocation(child), message); - } - break; - } else if (!Character.isWhitespace(c)) { - break; - } - } - } - } - } - } - - @Override - public void beforeCheckFile(@NonNull Context context) { - File file = context.file; - boolean isXmlFile = LintUtils.isXmlFile(file); - if (!isXmlFile && !LintUtils.isBitmapFile(file)) { - return; - } - String parentName = file.getParentFile().getName(); - int dash = parentName.indexOf('-'); - if (dash != -1 || FD_RES_VALUES.equals(parentName)) { - return; - } - ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName); - if (folderType == null) { - return; - } - List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType); - if (types.isEmpty()) { - return; - } - ResourceType type = types.get(0); - String resourceName = getResourceFieldName(getBaseName(file.getName())); - if (isPrivate(context, type, resourceName)) { - String message = createOverrideErrorMessage(context, type, resourceName); - Location location = Location.create(file); - context.report(ISSUE, location, message); - } - } - - private static String createOverrideErrorMessage(@NonNull Context context, - @NonNull ResourceType type, @NonNull String name) { - String libraryName = getLibraryName(context, type, name); - return String.format("Overriding `@%1$s/%2$s` which is marked as private in %3$s. If " - + "deliberate, use tools:override=\"true\", otherwise pick a " - + "different name.", type, name, libraryName); - } - - private static String createUsageErrorMessage(@NonNull Context context, - @NonNull ResourceType type, @NonNull String name) { - String libraryName = getLibraryName(context, type, name); - return String.format("The resource `@%1$s/%2$s` is marked as private in %3$s", type, - name, libraryName); - } - - /** Pick a suitable name to describe the library defining the private resource */ - @Nullable - private static String getLibraryName(@NonNull Context context, @NonNull ResourceType type, - @NonNull String name) { - ResourceVisibilityLookup lookup = context.getProject().getResourceVisibility(); - AndroidLibrary library = lookup.getPrivateIn(type, name); - if (library != null) { - String libraryName = library.getProject(); - if (libraryName != null) { - return libraryName; - } - MavenCoordinates coordinates = library.getResolvedCoordinates(); - if (coordinates != null) { - return coordinates.getGroupId() + ':' + coordinates.getArtifactId(); - } - } - return "the library"; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ReadParcelableDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ReadParcelableDetector.java deleted file mode 100644 index 8cd8cc6..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ReadParcelableDetector.java +++ /dev/null
@@ -1,119 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.SdkConstants.CLASS_PARCEL; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastLiteralUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.List; - -/** - * Looks for Parcelable classes that are missing a CREATOR field - */ -public class ReadParcelableDetector extends Detector implements Detector.UastScanner { - - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "ParcelClassLoader", //$NON-NLS-1$ - "Default Parcel Class Loader", - - "The documentation for `Parcel#readParcelable(ClassLoader)` (and its variations) " + - "says that you can pass in `null` to pick up the default class loader. However, " + - "that ClassLoader is a system class loader and is not able to find classes in " + - "your own application.\n" + - "\n" + - "If you are writing your own classes into the `Parcel` (not just SDK classes like " + - "`String` and so on), then you should supply a `ClassLoader` for your application " + - "instead; a simple way to obtain one is to just call `getClass().getClassLoader()` " + - "from your own class.", - - Category.CORRECTNESS, - 3, - Severity.WARNING, - new Implementation( - ReadParcelableDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo("http://developer.android.com/reference/android/os/Parcel.html"); - - /** Constructs a new {@link ReadParcelableDetector} check */ - public ReadParcelableDetector() { - } - - // ---- Implements UastScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Arrays.asList( - "readParcelable", - "readParcelableArray", - "readBundle", - "readArray", - "readSparseArray", - "readValue", - "readPersistableBundle" - ); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - return; - } - if (!(CLASS_PARCEL.equals(containingClass.getQualifiedName()))) { - return; - } - - List<UExpression> expressions = node.getValueArguments(); - int argumentCount = expressions.size(); - if (argumentCount == 0) { - String message = String.format("Using the default class loader " - + "will not work if you are restoring your own classes. Consider " - + "using for example `%1$s(getClass().getClassLoader())` instead.", - node.getMethodName()); - Location location = context.getUastLocation(node); - context.report(ISSUE, node, location, message); - } else if (argumentCount == 1) { - UExpression parameter = expressions.get(0); - if (UastLiteralUtils.isNullLiteral(parameter)) { - String message = "Passing null here (to use the default class loader) " - + "will not work if you are restoring your own classes. Consider " - + "using for example `getClass().getClassLoader()` instead."; - Location location = context.getUastLocation(node); - context.report(ISSUE, node, location, message); - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RecyclerViewDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RecyclerViewDetector.java deleted file mode 100644 index 252a0dd..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RecyclerViewDetector.java +++ /dev/null
@@ -1,340 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.*; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.intellij.psi.*; -import org.jetbrains.uast.*; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.android.tools.klint.checks.CutPasteDetector.isReachableFrom; - -/** - * Checks related to RecyclerView usage. - */ -public class RecyclerViewDetector extends Detector implements Detector.UastScanner { - - public static final Implementation IMPLEMENTATION = new Implementation( - RecyclerViewDetector.class, - Scope.JAVA_FILE_SCOPE); - - public static final Issue FIXED_POSITION = Issue.create( - "RecyclerView", //$NON-NLS-1$ - "RecyclerView Problems", - "`RecyclerView` will *not* call `onBindViewHolder` again when the position of " + - "the item changes in the data set unless the item itself is " + - "invalidated or the new position cannot be determined.\n" + - "\n" + - "For this reason, you should *only* use the position parameter " + - "while acquiring the related data item inside this method, and " + - "should *not* keep a copy of it.\n" + - "\n" + - "If you need the position of an item later on (e.g. in a click " + - "listener), use `getAdapterPosition()` which will have the updated " + - "adapter position.", - Category.CORRECTNESS, - 8, - Severity.ERROR, - IMPLEMENTATION); - - public static final Issue DATA_BINDER = Issue.create( - "PendingBindings", //$NON-NLS-1$ - "Missing Pending Bindings", - "When using a `ViewDataBinding` in a `onBindViewHolder` method, you *must* " + - "call `executePendingBindings()` before the method exits; otherwise " + - "the data binding runtime will update the UI in the next animation frame " + - "causing a delayed update and potential jumps if the item resizes.", - Category.CORRECTNESS, - 8, - Severity.ERROR, - IMPLEMENTATION); - - private static final String VIEW_ADAPTER = "android.support.v7.widget.RecyclerView.Adapter"; //$NON-NLS-1$ - private static final String ON_BIND_VIEW_HOLDER = "onBindViewHolder"; //$NON-NLS-1$ - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList(VIEW_ADAPTER); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - JavaEvaluator evaluator = context.getEvaluator(); - for (PsiMethod method : declaration.findMethodsByName(ON_BIND_VIEW_HOLDER, false)) { - int size = evaluator.getParameterCount(method); - if (size == 2 || size == 3) { - checkMethod(context, method, declaration); - } - } - - super.checkClass(context, declaration); - } - - private static void checkMethod(@NonNull JavaContext context, - @NonNull PsiMethod declaration, @NonNull PsiClass cls) { - PsiParameter[] parameters = declaration.getParameterList().getParameters(); - PsiParameter viewHolder = parameters[0]; - PsiParameter parameter = parameters[1]; - - ParameterEscapesVisitor visitor = new ParameterEscapesVisitor(context, cls, parameter); - UMethod method = context.getUastContext().getMethod(declaration); - method.accept(visitor); - if (visitor.variableEscapes()) { - reportError(context, viewHolder, parameter); - } - - // Look for pending data binder calls that aren't executed before the method finishes - List<UCallExpression> dataBinderReferences = visitor.getDataBinders(); - checkDataBinders(context, method, dataBinderReferences); - } - - private static void reportError(@NonNull JavaContext context, PsiParameter viewHolder, - PsiParameter parameter) { - String variablePrefix = viewHolder.getName(); - if (variablePrefix == null) { - variablePrefix = "ViewHolder"; - } - String message = String.format("Do not treat position as fixed; only use immediately " - + "and call `%1$s.getAdapterPosition()` to look it up later", - variablePrefix); - context.report(FIXED_POSITION, parameter, context.getLocation(parameter), - message); - } - - private static void checkDataBinders(@NonNull JavaContext context, - @NonNull UMethod declaration, List<UCallExpression> references) { - if (references != null && !references.isEmpty()) { - List<UCallExpression> targets = Lists.newArrayList(); - List<UCallExpression> sources = Lists.newArrayList(); - for (UCallExpression ref : references) { - if (isExecutePendingBindingsCall(ref)) { - targets.add(ref); - } else { - sources.add(ref); - } - } - - // Only operate on the last call in each block: ignore siblings with the same parent - // That way if you have - // dataBinder.foo(); - // dataBinder.bar(); - // dataBinder.baz(); - // we only flag the *last* of these calls as needing an executePendingBindings - // afterwards. We do this with a parent map such that we correctly pair - // elements when they have nested references within (such as if blocks.) - Map<UElement, UCallExpression> parentToChildren = Maps.newHashMap(); - for (UCallExpression reference : sources) { - // Note: We're using a map, not a multimap, and iterating forwards: - // this means that the *last* element will overwrite previous entries, - // and we end up with the last reference for each parent which is what we - // want - UExpression statement = UastUtils.getParentOfType(reference, UExpression.class, true); - if (statement != null) { - parentToChildren.put(statement.getUastParent(), reference); - } - } - - for (UCallExpression source : parentToChildren.values()) { - UExpression sourceBinderReference = source.getReceiver(); - PsiField sourceDataBinder = getDataBinderReference(sourceBinderReference); - assert sourceDataBinder != null; - - boolean reachesTarget = false; - for (UCallExpression target : targets) { - if (sourceDataBinder.equals(getDataBinderReference(target.getReceiver())) - // TODO: Provide full control flow graph, or at least provide an - // isReachable method which can take multiple targets - && isReachableFrom(declaration, source, target)) { - reachesTarget = true; - break; - } - } - if (!reachesTarget) { - String message = String.format( - "You must call `%1$s.executePendingBindings()` " - + "before the `onBind` method exits, otherwise, the DataBinding " - + "library will update the UI in the next animation frame " - + "causing a delayed update & potential jumps if the item " - + "resizes.", - sourceBinderReference.asSourceString()); - context.report(DATA_BINDER, source, context.getUastLocation(source), message); - } - } - } - } - - private static boolean isExecutePendingBindingsCall(UCallExpression call) { - return "executePendingBindings".equals(call.getMethodName()); - } - - @Nullable - private static PsiField getDataBinderReference(@Nullable UExpression element) { - if (element instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) element).resolve(); - if (resolved instanceof PsiField) { - PsiField field = (PsiField) resolved; - if ("dataBinder".equals(field.getName())) { - return field; - } - } - } - - return null; - } - - /** - * Determines whether a given variable "escapes" either to a field or to a nested - * runnable. (We deliberately ignore variables that escape via method calls.) - */ - private static class ParameterEscapesVisitor extends AbstractUastVisitor { - protected final JavaContext mContext; - private final List<PsiVariable> mVariables; - private final PsiClass mBindClass; - private boolean mEscapes; - private boolean mFoundInnerClass; - - private ParameterEscapesVisitor(JavaContext context, - @NonNull PsiClass bindClass, - @NonNull PsiParameter variable) { - mContext = context; - mVariables = Lists.<PsiVariable>newArrayList(variable); - mBindClass = bindClass; - } - - private boolean variableEscapes() { - return mEscapes; - } - - @Override - public boolean visitVariable(UVariable variable) { - UExpression initializer = variable.getUastInitializer(); - if (initializer instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) initializer).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - if (resolved instanceof PsiLocalVariable) { - mVariables.add(variable); - } else if (resolved instanceof PsiField) { - mEscapes = true; - } - } - } - - return super.visitVariable(variable); - } - - @Override - public boolean visitBinaryExpression(UBinaryExpression node) { - if (node.getOperator() instanceof UastBinaryOperator.AssignOperator) { - UExpression rhs = node.getRightOperand(); - boolean clearLhs = true; - if (rhs instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) rhs).resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - clearLhs = false; - PsiElement resolvedLhs = UastUtils.tryResolve(node.getLeftOperand()); - if (resolvedLhs instanceof PsiLocalVariable) { - PsiLocalVariable variable = (PsiLocalVariable) resolvedLhs; - mVariables.add(variable); - } else if (resolvedLhs instanceof PsiField) { - mEscapes = true; - } - } - } - if (clearLhs) { - // If we reassign one of the variables, clear it out - PsiElement resolved = UastUtils.tryResolve(node.getLeftOperand()); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - //noinspection SuspiciousMethodCalls - mVariables.remove(resolved); - } - } - } - return super.visitBinaryExpression(node); - } - - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - if (mFoundInnerClass) { - // Check to see if this reference is inside the same class as the original - // onBind (e.g. is this a reference from an inner class, or a reference - // to a variable assigned from there) - PsiElement resolved = node.resolve(); - //noinspection SuspiciousMethodCalls - if (resolved != null && mVariables.contains(resolved)) { - PsiClass outer = UastUtils.getParentOfType(node, UClass.class, true); - if (!mBindClass.equals(outer)) { - mEscapes = true; - } - } - } - - return super.visitSimpleNameReferenceExpression(node); - } - - @Override - public boolean visitClass(UClass node) { - if (node instanceof UAnonymousClass || !node.isStatic()) { - mFoundInnerClass = true; - } - - return super.visitClass(node); - } - - // Also look for data binder references - - private List<UCallExpression> mDataBinders = null; - - @Nullable - private List<UCallExpression> getDataBinders() { - return mDataBinders; - } - - @Override - public boolean visitCallExpression(UCallExpression expression) { - if (UastExpressionUtils.isMethodCall(expression)) { - UExpression methodExpression = expression.getReceiver(); - PsiField dataBinder = getDataBinderReference(methodExpression); - //noinspection VariableNotUsedInsideIf - if (dataBinder != null) { - if (mDataBinders == null) { - mDataBinders = Lists.newArrayList(); - } - mDataBinders.add(expression); - } - } - - return super.visitCallExpression(expression); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RegistrationDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RegistrationDetector.java deleted file mode 100644 index e0e9e43..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RegistrationDetector.java +++ /dev/null
@@ -1,319 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.CLASS_ACTIVITY; -import static com.android.SdkConstants.CLASS_APPLICATION; -import static com.android.SdkConstants.CLASS_BROADCASTRECEIVER; -import static com.android.SdkConstants.CLASS_CONTENTPROVIDER; -import static com.android.SdkConstants.CLASS_SERVICE; -import static com.android.SdkConstants.TAG_ACTIVITY; -import static com.android.SdkConstants.TAG_APPLICATION; -import static com.android.SdkConstants.TAG_PROVIDER; -import static com.android.SdkConstants.TAG_RECEIVER; -import static com.android.SdkConstants.TAG_SERVICE; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.builder.model.AndroidProject; -import com.android.builder.model.ProductFlavorContainer; -import com.android.builder.model.SourceProviderContainer; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LayoutDetector; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.android.utils.SdkUtils; -import com.google.common.collect.Maps; -import com.intellij.psi.PsiClass; - -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.uast.UClass; -import org.w3c.dom.Element; - -import java.io.File; -import java.util.Arrays; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -/** - * Checks for missing manifest registrations for activities, services etc - * and also makes sure that they are registered with the correct tag - */ -public class RegistrationDetector extends LayoutDetector implements Detector.UastScanner { - /** Unregistered activities and services */ - public static final Issue ISSUE = Issue.create( - "Registered", //$NON-NLS-1$ - "Class is not registered in the manifest", - - "Activities, services and content providers should be registered in the " + - "`AndroidManifest.xml` file using `<activity>`, `<service>` and `<provider>` tags.\n" + - "\n" + - "If your activity is simply a parent class intended to be subclassed by other " + - "\"real\" activities, make it an abstract class.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - RegistrationDetector.class, - EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE))) - .addMoreInfo( - "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$ - - protected Map<String, String> mManifestRegistrations; - - /** Constructs a new {@link RegistrationDetector} */ - public RegistrationDetector() { - } - - // ---- Implements XmlScanner ---- - - @Override - public Collection<String> getApplicableElements() { - return Arrays.asList(sTags); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - if (!element.hasAttributeNS(ANDROID_URI, ATTR_NAME)) { - // For example, application appears in manifest and doesn't always have a name - return; - } - String fqcn = getFqcn(context, element); - String tag = element.getTagName(); - String frameworkClass = tagToClass(tag); - if (frameworkClass != null) { - String signature = fqcn; - if (mManifestRegistrations == null) { - mManifestRegistrations = Maps.newHashMap(); - } - mManifestRegistrations.put(signature, frameworkClass); - if (signature.indexOf('$') != -1) { - signature = signature.replace('$', '.'); - mManifestRegistrations.put(signature, frameworkClass); - } - } - } - - /** - * Returns the fully qualified class name for a manifest entry element that - * specifies a name attribute - * - * @param context the query context providing the project - * @param element the element - * @return the fully qualified class name - */ - @NonNull - private static String getFqcn(@NonNull XmlContext context, @NonNull Element element) { - String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - if (className.startsWith(".")) { //$NON-NLS-1$ - return context.getProject().getPackage() + className; - } else if (className.indexOf('.') == -1) { - // According to the <activity> manifest element documentation, this is not - // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html ) - // but it appears in manifest files and appears to be supported by the runtime - // so handle this in code as well: - return context.getProject().getPackage() + '.' + className; - } // else: the class name is already a fully qualified class name - - return className; - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Arrays.asList( - // Common super class for Activity, ContentProvider, Service, Application - // (as well as some other classes not registered in the manifest, such as - // Fragment and VoiceInteractionSession) - "android.content.ComponentCallbacks2", - CLASS_BROADCASTRECEIVER); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass cls) { - if (cls.getName() == null) { - // anonymous class; can't be registered - return; - } - - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.isAbstract(cls) || evaluator.isPrivate(cls)) { - // Abstract classes do not need to be registered, and - // private classes are clearly not intended to be registered - return; - } - - String rightTag = getTag(evaluator, cls); - if (rightTag == null) { - // some non-registered Context, such as a BackupAgent - return; - } - String className = cls.getQualifiedName(); - if (className == null) { - return; - } - if (mManifestRegistrations != null) { - String framework = mManifestRegistrations.get(className); - if (framework == null) { - reportMissing(context, cls, className, rightTag); - } else if (!InheritanceUtil.isInheritor(cls, false, framework)) { - reportWrongTag(context, cls, rightTag, className, framework); - } - } else { - reportMissing(context, cls, className, rightTag); - } - } - - private static void reportWrongTag( - @NonNull JavaContext context, - @NonNull PsiClass node, - @NonNull String rightTag, - @NonNull String className, - @NonNull String framework) { - String wrongTag = classToTag(framework); - if (wrongTag == null) { - return; - } - Location location = context.getNameLocation(node); - String message = String.format("`%1$s` is %2$s but is registered " - + "in the manifest as %3$s", className, describeTag(rightTag), - describeTag(wrongTag)); - context.report(ISSUE, node, location, message); - } - - private static String describeTag(@NonNull String tag) { - String article = tag.startsWith("a") ? "an" : "a"; // an for activity and application - return String.format("%1$s `<%2$s>`", article, tag); - } - - private static void reportMissing( - @NonNull JavaContext context, - @NonNull PsiClass node, - @NonNull String className, - @NonNull String tag) { - if (tag.equals(TAG_RECEIVER)) { - // Receivers can be registered in code; don't flag these. - return; - } - - // Don't flag activities registered in test source sets - if (context.getProject().isGradleProject()) { - AndroidProject model = context.getProject().getGradleProjectModel(); - if (model != null) { - String javaSource = context.file.getPath(); - // Test source set? - - for (SourceProviderContainer extra : model.getDefaultConfig().getExtraSourceProviders()) { - String artifactName = extra.getArtifactName(); - if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) { - for (File file : extra.getSourceProvider().getJavaDirectories()) { - if (SdkUtils.startsWithIgnoreCase(javaSource, file.getPath())) { - return; - } - } - } - } - - for (ProductFlavorContainer container : model.getProductFlavors()) { - for (SourceProviderContainer extra : container.getExtraSourceProviders()) { - String artifactName = extra.getArtifactName(); - if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) { - for (File file : extra.getSourceProvider().getJavaDirectories()) { - if (SdkUtils.startsWithIgnoreCase(javaSource, file.getPath())) { - return; - } - } - } - } - } - } - } - - Location location = context.getNameLocation(node); - String message = String.format("The `<%1$s> %2$s` is not registered in the manifest", - tag, className); - context.report(ISSUE, node, location, message); - } - - private static String getTag(@NonNull JavaEvaluator evaluator, @NonNull PsiClass cls) { - String tag = null; - for (String s : sClasses) { - if (InheritanceUtil.isInheritor(cls, false, s)) { - tag = classToTag(s); - break; - } - } - return tag; - } - - /** The manifest tags we care about */ - private static final String[] sTags = new String[] { - TAG_ACTIVITY, - TAG_SERVICE, - TAG_RECEIVER, - TAG_PROVIDER, - TAG_APPLICATION - // Keep synchronized with {@link #sClasses} - }; - - /** The corresponding framework classes that the tags in {@link #sTags} should extend */ - private static final String[] sClasses = new String[] { - CLASS_ACTIVITY, - CLASS_SERVICE, - CLASS_BROADCASTRECEIVER, - CLASS_CONTENTPROVIDER, - CLASS_APPLICATION - // Keep synchronized with {@link #sTags} - }; - - /** Looks up the corresponding framework class a given manifest tag's class should extend */ - private static String tagToClass(String tag) { - for (int i = 0, n = sTags.length; i < n; i++) { - if (sTags[i].equals(tag)) { - return sClasses[i]; - } - } - - return null; - } - - /** Looks up the tag a given framework class should be registered with */ - protected static String classToTag(String className) { - for (int i = 0, n = sClasses.length; i < n; i++) { - if (sClasses[i].equals(className)) { - return sTags[i]; - } - } - - return null; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RequiredAttributeDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RequiredAttributeDetector.java deleted file mode 100644 index 3a4d4c1..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RequiredAttributeDetector.java +++ /dev/null
@@ -1,621 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX; -import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_LAYOUT; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_PARENT; -import static com.android.SdkConstants.ATTR_STYLE; -import static com.android.SdkConstants.AUTO_URI; -import static com.android.SdkConstants.FD_RES_LAYOUT; -import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7; -import static com.android.SdkConstants.GRID_LAYOUT; -import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.REQUEST_FOCUS; -import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.TABLE_LAYOUT; -import static com.android.SdkConstants.TABLE_ROW; -import static com.android.SdkConstants.TAG_DATA; -import static com.android.SdkConstants.TAG_IMPORT; -import static com.android.SdkConstants.TAG_ITEM; -import static com.android.SdkConstants.TAG_LAYOUT; -import static com.android.SdkConstants.TAG_STYLE; -import static com.android.SdkConstants.TAG_VARIABLE; -import static com.android.SdkConstants.VIEW_INCLUDE; -import static com.android.SdkConstants.VIEW_MERGE; -import static com.android.resources.ResourceFolderType.LAYOUT; -import static com.android.resources.ResourceFolderType.VALUES; -import static com.android.tools.klint.detector.api.LintUtils.getLayoutName; -import static com.android.tools.klint.detector.api.LintUtils.isNullLiteral; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.ide.common.resources.ResourceUrl; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LayoutDetector; -import com.android.tools.klint.detector.api.ResourceEvaluator; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.psi.JavaElementVisitor; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiMethodCallExpression; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastLiteralUtils; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.io.File; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Ensures that layout width and height attributes are specified - */ -public class RequiredAttributeDetector extends LayoutDetector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "RequiredSize", //$NON-NLS-1$ - "Missing `layout_width` or `layout_height` attributes", - - "All views must specify an explicit `layout_width` and `layout_height` attribute. " + - "There is a runtime check for this, so if you fail to specify a size, an exception " + - "is thrown at runtime.\n" + - "\n" + - "It's possible to specify these widths via styles as well. GridLayout, as a special " + - "case, does not require you to specify a size.", - Category.CORRECTNESS, - 4, - Severity.ERROR, - new Implementation( - RequiredAttributeDetector.class, - EnumSet.of(Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES))); - - public static final String PERCENT_RELATIVE_LAYOUT - = "android.support.percent.PercentRelativeLayout"; - public static final String ATTR_LAYOUT_WIDTH_PERCENT = "layout_widthPercent"; - public static final String ATTR_LAYOUT_HEIGHT_PERCENT = "layout_heightPercent"; - - /** Map from each style name to parent style */ - @Nullable private Map<String, String> mStyleParents; - - /** Set of style names where the style sets the layout width */ - @Nullable private Set<String> mWidthStyles; - - /** Set of style names where the style sets the layout height */ - @Nullable private Set<String> mHeightStyles; - - /** Set of layout names for layouts that are included by an {@code <include>} tag - * where the width is set on the include */ - @Nullable private Set<String> mIncludedWidths; - - /** Set of layout names for layouts that are included by an {@code <include>} tag - * where the height is set on the include */ - @Nullable private Set<String> mIncludedHeights; - - /** Set of layout names for layouts that are included by an {@code <include>} tag - * where the width is <b>not</b> set on the include */ - @Nullable private Set<String> mNotIncludedWidths; - - /** Set of layout names for layouts that are included by an {@code <include>} tag - * where the height is <b>not</b> set on the include */ - @Nullable private Set<String> mNotIncludedHeights; - - /** Whether the width was set in a theme definition */ - private boolean mSetWidthInTheme; - - /** Whether the height was set in a theme definition */ - private boolean mSetHeightInTheme; - - /** Constructs a new {@link RequiredAttributeDetector} */ - public RequiredAttributeDetector() { - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == LAYOUT || folderType == VALUES; - } - - @Override - public void afterCheckProject(@NonNull Context context) { - // Process checks in two phases: - // Phase 1: Gather styles and includes (styles are encountered after the layouts - // so we can't do it in a single phase, and includes can be affected by includes from - // layouts we haven't seen yet) - // Phase 2: Process layouts, using gathered style and include data, and mark layouts - // not known. - // - if (context.getPhase() == 1) { - checkSizeSetInTheme(); - - context.requestRepeat(this, Scope.RESOURCE_FILE_SCOPE); - } - } - - private boolean isWidthStyle(String style) { - return isSizeStyle(style, mWidthStyles); - } - - private boolean isHeightStyle(String style) { - return isSizeStyle(style, mHeightStyles); - } - - private boolean isSizeStyle(String style, Set<String> sizeStyles) { - if (isFrameworkSizeStyle(style)) { - return true; - } - if (sizeStyles == null) { - return false; - } - return isSizeStyle(stripStylePrefix(style), sizeStyles, 0); - } - - private static boolean isFrameworkSizeStyle(String style) { - // The styles Widget.TextView.ListSeparator (and several theme variations, such as - // Widget.Holo.TextView.ListSeparator, Widget.Holo.Light.TextView.ListSeparator, etc) - // define layout_width and layout_height. - // These are exposed through the listSeparatorTextViewStyle style. - if (style.equals("?android:attr/listSeparatorTextViewStyle") //$NON-NLS-1$ - || style.equals("?android/listSeparatorTextViewStyle")) { //$NON-NLS-1$ - return true; - } - - // It's also set on Widget.QuickContactBadge and Widget.QuickContactBadgeSmall - // These are exposed via a handful of attributes with a common prefix - if (style.startsWith("?android:attr/quickContactBadgeStyle")) { //$NON-NLS-1$ - return true; - } - - // Finally, the styles are set on MediaButton and Widget.Holo.Tab (and - // Widget.Holo.Light.Tab) but these are not exposed via attributes. - - return false; - } - - private boolean isSizeStyle( - @NonNull String style, - @NonNull Set<String> sizeStyles, int depth) { - if (depth == 30) { - // Cycle between local and framework attribute style missed - // by the fact that we're stripping the distinction between framework - // and local styles here - return false; - } - - assert !style.startsWith(STYLE_RESOURCE_PREFIX) - && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX); - - if (sizeStyles.contains(style)) { - return true; - } - - if (mStyleParents != null) { - String parentStyle = mStyleParents.get(style); - if (parentStyle != null) { - parentStyle = stripStylePrefix(parentStyle); - if (isSizeStyle(parentStyle, sizeStyles, depth + 1)) { - return true; - } - } - } - - int index = style.lastIndexOf('.'); - if (index > 0) { - return isSizeStyle(style.substring(0, index), sizeStyles, depth + 1); - } - - return false; - } - - private void checkSizeSetInTheme() { - // Look through the styles and determine whether each style is a theme - if (mStyleParents == null) { - return; - } - - Map<String, Boolean> isTheme = Maps.newHashMap(); - for (String style : mStyleParents.keySet()) { - if (isTheme(stripStylePrefix(style), isTheme, 0)) { - mSetWidthInTheme = true; - mSetHeightInTheme = true; - break; - } - } - } - - private boolean isTheme(String style, Map<String, Boolean> isTheme, int depth) { - if (depth == 30) { - // Cycle between local and framework attribute style missed - // by the fact that we're stripping the distinction between framework - // and local styles here - return false; - } - - assert !style.startsWith(STYLE_RESOURCE_PREFIX) - && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX); - - Boolean known = isTheme.get(style); - if (known != null) { - return known; - } - - if (style.contains("Theme")) { //$NON-NLS-1$ - isTheme.put(style, true); - return true; - } - - if (mStyleParents != null) { - String parentStyle = mStyleParents.get(style); - if (parentStyle != null) { - parentStyle = stripStylePrefix(parentStyle); - if (isTheme(parentStyle, isTheme, depth + 1)) { - isTheme.put(style, true); - return true; - } - } - } - - int index = style.lastIndexOf('.'); - if (index > 0) { - String parentStyle = style.substring(0, index); - boolean result = isTheme(parentStyle, isTheme, depth + 1); - isTheme.put(style, result); - return result; - } - - return false; - } - - @VisibleForTesting - static boolean hasLayoutVariations(File file) { - File parent = file.getParentFile(); - if (parent == null) { - return false; - } - File res = parent.getParentFile(); - if (res == null) { - return false; - } - String name = file.getName(); - File[] folders = res.listFiles(); - if (folders == null) { - return false; - } - for (File folder : folders) { - if (!folder.getName().startsWith(FD_RES_LAYOUT)) { - continue; - } - if (folder.equals(parent)) { - continue; - } - File other = new File(folder, name); - if (other.exists()) { - return true; - } - } - - return false; - } - - private static String stripStylePrefix(@NonNull String style) { - if (style.startsWith(STYLE_RESOURCE_PREFIX)) { - style = style.substring(STYLE_RESOURCE_PREFIX.length()); - } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) { - style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length()); - } - - return style; - } - - private static boolean isRootElement(@NonNull Node node) { - return node == node.getOwnerDocument().getDocumentElement(); - } - - // ---- Implements XmlScanner ---- - - @Override - public Collection<String> getApplicableElements() { - return ALL; - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - ResourceFolderType folderType = context.getResourceFolderType(); - int phase = context.getPhase(); - if (phase == 1 && folderType == VALUES) { - String tag = element.getTagName(); - if (TAG_STYLE.equals(tag)) { - String parent = element.getAttribute(ATTR_PARENT); - if (parent != null && !parent.isEmpty()) { - String name = element.getAttribute(ATTR_NAME); - if (name != null && !name.isEmpty()) { - if (mStyleParents == null) { - mStyleParents = Maps.newHashMap(); - } - mStyleParents.put(name, parent); - } - } - } else if (TAG_ITEM.equals(tag) - && TAG_STYLE.equals(element.getParentNode().getNodeName())) { - String name = element.getAttribute(ATTR_NAME); - if (name.endsWith(ATTR_LAYOUT_WIDTH) && - name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_WIDTH)) { - if (mWidthStyles == null) { - mWidthStyles = Sets.newHashSet(); - } - String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME); - mWidthStyles.add(styleName); - } - if (name.endsWith(ATTR_LAYOUT_HEIGHT) && - name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_HEIGHT)) { - if (mHeightStyles == null) { - mHeightStyles = Sets.newHashSet(); - } - String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME); - mHeightStyles.add(styleName); - } - } - } else if (folderType == LAYOUT) { - if (phase == 1) { - // Gather includes - if (element.getTagName().equals(VIEW_INCLUDE)) { - String layout = element.getAttribute(ATTR_LAYOUT); - if (layout != null && !layout.isEmpty()) { - recordIncludeWidth(layout, - element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH)); - recordIncludeHeight(layout, - element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT)); - } - } - } else { - assert phase == 2; // Check everything using style data and include data - boolean hasWidth = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH); - boolean hasHeight = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT); - - if (mSetWidthInTheme) { - hasWidth = true; - } - - if (mSetHeightInTheme) { - hasHeight = true; - } - - if (hasWidth && hasHeight) { - return; - } - - String tag = element.getTagName(); - if (VIEW_MERGE.equals(tag) - || VIEW_INCLUDE.equals(tag) - || REQUEST_FOCUS.equals(tag)) { - return; - } - - // Data binding: these tags shouldn't specify width/height - if (tag.equals(TAG_LAYOUT) - || tag.equals(TAG_VARIABLE) - || tag.equals(TAG_DATA) - || tag.equals(TAG_IMPORT)) { - return; - } - - String parentTag = element.getParentNode() != null - ? element.getParentNode().getNodeName() : ""; - if (TABLE_LAYOUT.equals(parentTag) - || TABLE_ROW.equals(parentTag) - || GRID_LAYOUT.equals(parentTag) - || FQCN_GRID_LAYOUT_V7.equals(parentTag)) { - return; - } - - // PercentRelativeLayout or PercentFrameLayout? - boolean isPercent = parentTag.startsWith("android.support.percent.Percent"); - if (isPercent) { - hasWidth |= element.hasAttributeNS(AUTO_URI, ATTR_LAYOUT_WIDTH_PERCENT); - hasHeight |= element.hasAttributeNS(AUTO_URI, ATTR_LAYOUT_HEIGHT_PERCENT); - if (hasWidth && hasHeight) { - return; - } - } - - if (!context.getProject().getReportIssues()) { - // If this is a library project not being analyzed, ignore it - return; - } - - boolean certain = true; - boolean isRoot = isRootElement(element); - if (isRoot || isRootElement(element.getParentNode()) - && VIEW_MERGE.equals(parentTag)) { - String name = LAYOUT_RESOURCE_PREFIX + getLayoutName(context.file); - if (!hasWidth && mIncludedWidths != null) { - hasWidth = mIncludedWidths.contains(name); - // If the layout is *also* included in a context where the width - // was not set, we're not certain; it's possible that - if (mNotIncludedWidths != null && mNotIncludedWidths.contains(name)) { - hasWidth = false; - // If we only have a single layout we know that this layout isn't - // always included with layout_width or layout_height set, but - // if there are multiple layouts, it's possible that at runtime - // we only load the size-less layout by the tag which includes - // the size - certain = !hasLayoutVariations(context.file); - } - } - if (!hasHeight && mIncludedHeights != null) { - hasHeight = mIncludedHeights.contains(name); - if (mNotIncludedHeights != null && mNotIncludedHeights.contains(name)) { - hasHeight = false; - certain = !hasLayoutVariations(context.file); - } - } - if (hasWidth && hasHeight) { - return; - } - } - - if (!hasWidth || !hasHeight) { - String style = element.getAttribute(ATTR_STYLE); - if (style != null && !style.isEmpty()) { - if (!hasWidth) { - hasWidth = isWidthStyle(style); - } - if (!hasHeight) { - hasHeight = isHeightStyle(style); - } - } - if (hasWidth && hasHeight) { - return; - } - } - - String message; - if (!(hasWidth || hasHeight)) { - if (certain) { - message = "The required `layout_width` and `layout_height` attributes " + - "are missing"; - } else { - message = "The required `layout_width` and `layout_height` attributes " + - "*may* be missing"; - } - } else { - String attribute = hasWidth ? ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH; - if (certain) { - message = String.format("The required `%1$s` attribute is missing", - attribute); - } else { - message = String.format("The required `%1$s` attribute *may* be missing", - attribute); - } - } - if (isPercent) { - String escapedLayoutWidth = '`' + ATTR_LAYOUT_WIDTH + '`'; - String escapedLayoutHeight = '`' + ATTR_LAYOUT_HEIGHT + '`'; - String escapedLayoutWidthPercent = '`' + ATTR_LAYOUT_WIDTH_PERCENT + '`'; - String escapedLayoutHeightPercent = '`' + ATTR_LAYOUT_HEIGHT_PERCENT + '`'; - message = message.replace(escapedLayoutWidth, escapedLayoutWidth + " or " - + escapedLayoutWidthPercent).replace(escapedLayoutHeight, - escapedLayoutHeight + " or " + escapedLayoutHeightPercent); - } - context.report(ISSUE, element, context.getLocation(element), - message); - } - } - } - - private void recordIncludeWidth(String layout, boolean providesWidth) { - if (providesWidth) { - if (mIncludedWidths == null) { - mIncludedWidths = Sets.newHashSet(); - } - mIncludedWidths.add(layout); - } else { - if (mNotIncludedWidths == null) { - mNotIncludedWidths = Sets.newHashSet(); - } - mNotIncludedWidths.add(layout); - } - } - - private void recordIncludeHeight(String layout, boolean providesHeight) { - if (providesHeight) { - if (mIncludedHeights == null) { - mIncludedHeights = Sets.newHashSet(); - } - mIncludedHeights.add(layout); - } else { - if (mNotIncludedHeights == null) { - mNotIncludedHeights = Sets.newHashSet(); - } - mNotIncludedHeights.add(layout); - } - } - - // ---- Implements UastScanner ---- - - @Override - @Nullable - public List<String> getApplicableMethodNames() { - return Collections.singletonList("inflate"); //$NON-NLS-1$ - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - // Handle - // View#inflate(Context context, int resource, ViewGroup root) - // LayoutInflater#inflate(int resource, ViewGroup root) - // LayoutInflater#inflate(int resource, ViewGroup root, boolean attachToRoot) - List<UExpression> args = call.getValueArguments(); - - String layout = null; - int index = 0; - ResourceEvaluator evaluator = new ResourceEvaluator(context); - for (UExpression expression : args) { - ResourceUrl url = evaluator.getResource(expression); - if (url != null && url.type == ResourceType.LAYOUT) { - layout = url.toString(); - break; - } - index++; - } - - if (layout == null) { - // Flow analysis didn't succeed - return; - } - - // In all the applicable signatures, the view root argument is immediately after - // the layout resource id. - int viewRootPos = index + 1; - if (viewRootPos < args.size()) { - UExpression viewRoot = args.get(viewRootPos); - if (UastLiteralUtils.isNullLiteral(viewRoot)) { - // Yep, this one inflates the given view with a null parent: - // Tag it as such. For now just use the include data structure since - // it has the same net effect - recordIncludeWidth(layout, true); - recordIncludeHeight(layout, true); - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RtlDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RtlDetector.java deleted file mode 100644 index 946dba5..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RtlDetector.java +++ /dev/null
@@ -1,609 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_DRAWABLE_END; -import static com.android.SdkConstants.ATTR_DRAWABLE_LEFT; -import static com.android.SdkConstants.ATTR_DRAWABLE_RIGHT; -import static com.android.SdkConstants.ATTR_DRAWABLE_START; -import static com.android.SdkConstants.ATTR_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_END; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_END; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_START; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_START; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_END; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_START; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_END_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_START_OF; -import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_END; -import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT; -import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT; -import static com.android.SdkConstants.ATTR_LIST_PREFERRED_ITEM_PADDING_START; -import static com.android.SdkConstants.ATTR_PADDING; -import static com.android.SdkConstants.ATTR_PADDING_END; -import static com.android.SdkConstants.ATTR_PADDING_LEFT; -import static com.android.SdkConstants.ATTR_PADDING_RIGHT; -import static com.android.SdkConstants.ATTR_PADDING_START; -import static com.android.SdkConstants.GRAVITY_VALUE_END; -import static com.android.SdkConstants.GRAVITY_VALUE_LEFT; -import static com.android.SdkConstants.GRAVITY_VALUE_RIGHT; -import static com.android.SdkConstants.GRAVITY_VALUE_START; -import static com.android.SdkConstants.TAG_APPLICATION; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LayoutDetector; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; - -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.USimpleNameReferenceExpression; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; - -/** - * Check which looks for RTL issues (right-to-left support) in layouts - */ -public class RtlDetector extends LayoutDetector implements Detector.UastScanner { - - @SuppressWarnings("unchecked") - private static final Implementation IMPLEMENTATION = new Implementation( - RtlDetector.class, - EnumSet.of(Scope.RESOURCE_FILE, Scope.JAVA_FILE, Scope.MANIFEST), - Scope.RESOURCE_FILE_SCOPE, - Scope.JAVA_FILE_SCOPE, - Scope.MANIFEST_SCOPE - ); - - public static final Issue USE_START = Issue.create( - "RtlHardcoded", //$NON-NLS-1$ - "Using left/right instead of start/end attributes", - - "Using `Gravity#LEFT` and `Gravity#RIGHT` can lead to problems when a layout is " + - "rendered in locales where text flows from right to left. Use `Gravity#START` " + - "and `Gravity#END` instead. Similarly, in XML `gravity` and `layout_gravity` " + - "attributes, use `start` rather than `left`.\n" + - "\n" + - "For XML attributes such as paddingLeft and `layout_marginLeft`, use `paddingStart` " + - "and `layout_marginStart`. *NOTE*: If your `minSdkVersion` is less than 17, you should " + - "add *both* the older left/right attributes *as well as* the new start/right " + - "attributes. On older platforms, where RTL is not supported and the start/right " + - "attributes are unknown and therefore ignored, you need the older left/right " + - "attributes. There is a separate lint check which catches that type of error.\n" + - "\n" + - "(Note: For `Gravity#LEFT` and `Gravity#START`, you can use these constants even " + - "when targeting older platforms, because the `start` bitmask is a superset of the " + - "`left` bitmask. Therefore, you can use `gravity=\"start\"` rather than " + - "`gravity=\"left|start\"`.)", - - Category.RTL, 5, Severity.WARNING, IMPLEMENTATION); - - public static final Issue COMPAT = Issue.create( - "RtlCompat", //$NON-NLS-1$ - "Right-to-left text compatibility issues", - - "API 17 adds a `textAlignment` attribute to specify text alignment. However, " + - "if you are supporting older versions than API 17, you must *also* specify a " + - "gravity or layout_gravity attribute, since older platforms will ignore the " + - "`textAlignment` attribute.", - - Category.RTL, 6, Severity.ERROR, IMPLEMENTATION); - - public static final Issue SYMMETRY = Issue.create( - "RtlSymmetry", //$NON-NLS-1$ - "Padding and margin symmetry", - - "If you specify padding or margin on the left side of a layout, you should " + - "probably also specify padding on the right side (and vice versa) for " + - "right-to-left layout symmetry.", - - Category.RTL, 6, Severity.WARNING, IMPLEMENTATION); - - - public static final Issue ENABLED = Issue.create( - "RtlEnabled", //$NON-NLS-1$ - "Using RTL attributes without enabling RTL support", - - "To enable right-to-left support, when running on API 17 and higher, you must " + - "set the `android:supportsRtl` attribute in the manifest `<application>` element.\n" + - "\n" + - "If you have started adding RTL attributes, but have not yet finished the " + - "migration, you can set the attribute to false to satisfy this lint check.", - - Category.RTL, 3, Severity.WARNING, IMPLEMENTATION); - - /* TODO: - public static final Issue FIELD = Issue.create( - "RtlFieldAccess", //$NON-NLS-1$ - "Accessing margin and padding fields directly", - - "Modifying the padding and margin constants in view objects directly is " + - "problematic when using RTL support, since it can lead to inconsistent states. You " + - "*must* use the corresponding setter methods instead (`View#setPadding` etc).", - - Category.RTL, 3, Severity.WARNING, IMPLEMENTATION).setEnabledByDefault(false); - - public static final Issue AWARE = Issue.create( - "RtlAware", //$NON-NLS-1$ - "View code not aware of RTL APIs", - - "When manipulating views, and especially when implementing custom layouts, " + - "the code may need to be aware of RTL APIs. This lint check looks for usages of " + - "APIs that frequently require adjustments for right-to-left text, and warns if it " + - "does not also see text direction look-ups indicating that the code has already " + - "been updated to handle RTL layouts.", - - Category.RTL, 3, Severity.WARNING, IMPLEMENTATION).setEnabledByDefault(false); - */ - - private static final String RIGHT_FIELD = "RIGHT"; //$NON-NLS-1$ - private static final String LEFT_FIELD = "LEFT"; //$NON-NLS-1$ - private static final String FQCN_GRAVITY = "android.view.Gravity"; //$NON-NLS-1$ - private static final String ATTR_TEXT_ALIGNMENT = "textAlignment"; //$NON-NLS-1$ - static final String ATTR_SUPPORTS_RTL = "supportsRtl"; //$NON-NLS-1$ - - /** API version in which RTL support was added */ - private static final int RTL_API = 17; - - private static final String LEFT = "Left"; - private static final String START = "Start"; - private static final String RIGHT = "Right"; - private static final String END = "End"; - - private Boolean mEnabledRtlSupport; - private boolean mUsesRtlAttributes; - - /** Constructs a new {@link RtlDetector} */ - public RtlDetector() { - } - - private boolean rtlApplies(@NonNull Context context) { - Project project = context.getMainProject(); - if (project.getTargetSdk() < RTL_API) { - return false; - } - - int buildTarget = project.getBuildSdk(); - if (buildTarget != -1 && buildTarget < RTL_API) { - return false; - } - - //noinspection RedundantIfStatement - if (mEnabledRtlSupport != null && !mEnabledRtlSupport) { - return false; - } - - return true; - } - - @Override - public void afterCheckProject(@NonNull Context context) { - if (mUsesRtlAttributes && mEnabledRtlSupport == null && rtlApplies(context)) { - List<File> manifestFile = context.getMainProject().getManifestFiles(); - if (!manifestFile.isEmpty()) { - Location location = Location.create(manifestFile.get(0)); - context.report(ENABLED, location, - "The project references RTL attributes, but does not explicitly enable " + - "or disable RTL support with `android:supportsRtl` in the manifest"); - } - } - } - - // ---- Implements XmlDetector ---- - - @VisibleForTesting - static final String[] ATTRIBUTES = new String[] { - // Pairs, from left/right constants to corresponding start/end constants - ATTR_LAYOUT_ALIGN_PARENT_LEFT, ATTR_LAYOUT_ALIGN_PARENT_START, - ATTR_LAYOUT_ALIGN_PARENT_RIGHT, ATTR_LAYOUT_ALIGN_PARENT_END, - ATTR_LAYOUT_MARGIN_LEFT, ATTR_LAYOUT_MARGIN_START, - ATTR_LAYOUT_MARGIN_RIGHT, ATTR_LAYOUT_MARGIN_END, - ATTR_PADDING_LEFT, ATTR_PADDING_START, - ATTR_PADDING_RIGHT, ATTR_PADDING_END, - ATTR_DRAWABLE_LEFT, ATTR_DRAWABLE_START, - ATTR_DRAWABLE_RIGHT, ATTR_DRAWABLE_END, - ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT, ATTR_LIST_PREFERRED_ITEM_PADDING_START, - ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT, ATTR_LIST_PREFERRED_ITEM_PADDING_END, - - // RelativeLayout - ATTR_LAYOUT_TO_LEFT_OF, ATTR_LAYOUT_TO_START_OF, - ATTR_LAYOUT_TO_RIGHT_OF, ATTR_LAYOUT_TO_END_OF, - ATTR_LAYOUT_ALIGN_LEFT, ATTR_LAYOUT_ALIGN_START, - ATTR_LAYOUT_ALIGN_RIGHT, ATTR_LAYOUT_ALIGN_END, - }; - static { - if (LintUtils.assertionsEnabled()) { - for (int i = 0; i < ATTRIBUTES.length; i += 2) { - String replace = ATTRIBUTES[i]; - String with = ATTRIBUTES[i + 1]; - assert with.equals(convertOldToNew(replace)); - assert replace.equals(convertNewToOld(with)); - } - } - } - - public static boolean isRtlAttributeName(@NonNull String attribute) { - for (int i = 1; i < ATTRIBUTES.length; i += 2) { - if (attribute.equals(ATTRIBUTES[i])) { - return true; - } - } - return false; - } - - @VisibleForTesting - static String convertOldToNew(String attribute) { - if (attribute.contains(LEFT)) { - return attribute.replace(LEFT, START); - } else { - return attribute.replace(RIGHT, END); - } - } - - @VisibleForTesting - static String convertNewToOld(String attribute) { - if (attribute.contains(START)) { - return attribute.replace(START, LEFT); - } else { - return attribute.replace(END, RIGHT); - } - } - - @VisibleForTesting - static String convertToOppositeDirection(String attribute) { - if (attribute.contains(LEFT)) { - return attribute.replace(LEFT, RIGHT); - } else if (attribute.contains(RIGHT)) { - return attribute.replace(RIGHT, LEFT); - } else if (attribute.contains(START)) { - return attribute.replace(START, END); - } else { - return attribute.replace(END, START); - } - } - - @Nullable - static String getTextAlignmentToGravity(String attribute) { - if (attribute.endsWith(START)) { // textStart, viewStart, ... - return GRAVITY_VALUE_START; - } else if (attribute.endsWith(END)) { // textEnd, viewEnd, ... - return GRAVITY_VALUE_END; - } else { - return null; // inherit, others - } - } - - @Override - public Collection<String> getApplicableAttributes() { - int size = ATTRIBUTES.length + 4; - List<String> attributes = new ArrayList<String>(size); - - // For detecting whether RTL support is enabled - attributes.add(ATTR_SUPPORTS_RTL); - - // For detecting left/right attributes which should probably be - // migrated to start/end - attributes.add(ATTR_GRAVITY); - attributes.add(ATTR_LAYOUT_GRAVITY); - - // For detecting existing attributes which indicate an attempt to - // use RTL - attributes.add(ATTR_TEXT_ALIGNMENT); - - // Add conversion attributes: left/right attributes to nominate - // attributes that should be added as start/end, and start/end - // attributes to use to look up elements that should have compatibility - // left/right ones as well - Collections.addAll(attributes, ATTRIBUTES); - - assert attributes.size() == size : attributes.size(); - - return attributes; - } - - @Override - public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { - Project project = context.getMainProject(); - String value = attribute.getValue(); - - if (!ANDROID_URI.equals(attribute.getNamespaceURI())) { - // Layout attribute not in the Android namespace (or a custom namespace). - // This is likely an application error (which should get caught by - // the MissingPrefixDetector) - return; - } - - String name = attribute.getLocalName(); - assert name != null : attribute.getName(); - - if (name.equals(ATTR_SUPPORTS_RTL)) { - mEnabledRtlSupport = Boolean.valueOf(value); - if (!attribute.getOwnerElement().getTagName().equals(TAG_APPLICATION)) { - context.report(ENABLED, attribute, context.getLocation(attribute), String.format( - "Wrong declaration: `%1$s` should be defined on the `<application>` element", - attribute.getName())); - } - int targetSdk = project.getTargetSdk(); - if (mEnabledRtlSupport && targetSdk < RTL_API) { - String message = String.format( - "You must set `android:targetSdkVersion` to at least %1$d when " - + "enabling RTL support (is %2$d)", - RTL_API, project.getTargetSdk()); - context.report(ENABLED, attribute, context.getValueLocation(attribute), message); - } - return; - } - - if (!rtlApplies(context)) { - return; - } - - if (name.equals(ATTR_TEXT_ALIGNMENT)) { - if (context.getProject().getReportIssues()) { - mUsesRtlAttributes = true; - } - - Element element = attribute.getOwnerElement(); - final String gravity; - final Attr gravityNode; - if (element.hasAttributeNS(ANDROID_URI, ATTR_GRAVITY)) { - gravityNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_GRAVITY); - gravity = gravityNode.getValue(); - } else if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY)) { - gravityNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY); - gravity = gravityNode.getValue(); - } else if (project.getMinSdk() < RTL_API) { - int folderVersion = context.getFolderVersion(); - if (folderVersion < RTL_API && context.isEnabled(COMPAT)) { - String expectedGravity = getTextAlignmentToGravity(value); - if (expectedGravity != null) { - String message = String.format( - "To support older versions than API 17 (project specifies %1$d) " - + "you must *also* specify `gravity` or `layout_gravity=\"%2$s\"`", - project.getMinSdk(), expectedGravity); - context.report(COMPAT, attribute, - context.getNameLocation(attribute), message); - } - } - return; - } else { - return; - } - - String expectedGravity = getTextAlignmentToGravity(value); - if (expectedGravity != null && !gravity.contains(expectedGravity) - && context.isEnabled(COMPAT)) { - String message = String.format("Inconsistent alignment specification between " - + "`textAlignment` and `gravity` attributes: was `%1$s`, expected `%2$s`", - gravity, expectedGravity); - Location location = context.getValueLocation(attribute); - context.report(COMPAT, attribute, location, message); - Location secondary = context.getValueLocation(gravityNode); - secondary.setMessage("Incompatible direction here"); - location.setSecondary(secondary); - } - return; - } - - if (name.equals(ATTR_GRAVITY) || name.equals(ATTR_LAYOUT_GRAVITY)) { - boolean isLeft = value.contains(GRAVITY_VALUE_LEFT); - boolean isRight = value.contains(GRAVITY_VALUE_RIGHT); - if (!isLeft && !isRight) { - if ((value.contains(GRAVITY_VALUE_START) || value.contains(GRAVITY_VALUE_END)) - && context.getProject().getReportIssues()) { - mUsesRtlAttributes = true; - } - return; - } - String message = String.format( - "Use \"`%1$s`\" instead of \"`%2$s`\" to ensure correct behavior in " - + "right-to-left locales", - isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END, - isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT); - if (context.isEnabled(USE_START)) { - context.report(USE_START, attribute, context.getValueLocation(attribute), message); - } - - return; - } - - // Some other left/right/start/end attribute - int targetSdk = project.getTargetSdk(); - - // TODO: If attribute is drawableLeft or drawableRight, add note that you might - // want to consider adding a specialized image in the -ldrtl folder as well - - Element element = attribute.getOwnerElement(); - boolean isPaddingAttribute = isPaddingAttribute(name); - if (isPaddingAttribute || isMarginAttribute(name)) { - String opposite = convertToOppositeDirection(name); - if (element.hasAttributeNS(ANDROID_URI, opposite)) { - String oldValue = element.getAttributeNS(ANDROID_URI, opposite); - if (value.equals(oldValue)) { - return; - } - } else if (isPaddingAttribute - && !element.hasAttributeNS(ANDROID_URI, - isOldAttribute(opposite) ? convertOldToNew(opposite) - : convertNewToOld(opposite)) && context.isEnabled(SYMMETRY)) { - String message = String.format( - "When you define `%1$s` you should probably also define `%2$s` for " - + "right-to-left symmetry", name, opposite); - context.report(SYMMETRY, attribute, context.getNameLocation(attribute), message); - } - } - - boolean isOld = isOldAttribute(name); - if (isOld) { - if (!context.isEnabled(USE_START)) { - return; - } - String rtl = convertOldToNew(name); - if (element.hasAttributeNS(ANDROID_URI, rtl)) { - if (project.getMinSdk() >= RTL_API || context.getFolderVersion() >= RTL_API) { - // Warn that left/right isn't needed - String message = String.format( - "Redundant attribute `%1$s`; already defining `%2$s` with " - + "`targetSdkVersion` %3$s", - name, rtl, targetSdk); - context.report(USE_START, attribute, - context.getNameLocation(attribute), message); - } - } else { - String message; - if (project.getMinSdk() >= RTL_API || context.getFolderVersion() >= RTL_API) { - message = String.format( - "Consider replacing `%1$s` with `%2$s:%3$s=\"%4$s\"` to better support " - + "right-to-left layouts", - attribute.getName(), attribute.getPrefix(), rtl, value); - } else { - message = String.format( - "Consider adding `%1$s:%2$s=\"%3$s\"` to better support " - + "right-to-left layouts", - attribute.getPrefix(), rtl, value); - } - context.report(USE_START, attribute, - context.getNameLocation(attribute), message); - } - } else { - if (project.getMinSdk() >= RTL_API || !context.isEnabled(COMPAT)) { - // Only supporting 17+: no need to define older attributes - return; - } - int folderVersion = context.getFolderVersion(); - if (folderVersion >= RTL_API) { - // In a -v17 folder or higher: no need to define older attributes - return; - } - String old = convertNewToOld(name); - if (element.hasAttributeNS(ANDROID_URI, old)) { - return; - } - String message = String.format( - "To support older versions than API 17 (project specifies %1$d) " - + "you should *also* add `%2$s:%3$s=\"%4$s\"`", - project.getMinSdk(), attribute.getPrefix(), old, - convertNewToOld(value)); - context.report(COMPAT, attribute, context.getNameLocation(attribute), message); - } - } - - private static boolean isOldAttribute(String name) { - return name.contains(LEFT) || name.contains(RIGHT); - } - - private static boolean isMarginAttribute(@NonNull String name) { - return name.startsWith(ATTR_LAYOUT_MARGIN); - } - - private static boolean isPaddingAttribute(@NonNull String name) { - return name.startsWith(ATTR_PADDING); - } - - // ---- Implements UastScanner ---- - - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - return Collections.<Class<? extends UElement>>singletonList(USimpleNameReferenceExpression.class); - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - if (rtlApplies(context)) { - return new IdentifierChecker(context); - } - - return null; - } - - private static class IdentifierChecker extends AbstractUastVisitor { - private final JavaContext mContext; - - public IdentifierChecker(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - String identifier = node.getIdentifier(); - boolean isLeft = LEFT_FIELD.equals(identifier); - boolean isRight = RIGHT_FIELD.equals(identifier); - if (!isLeft && !isRight) { - return super.visitSimpleNameReferenceExpression(node); - } - - PsiElement resolved = node.resolve(); - if (!(resolved instanceof PsiField)) { - return super.visitSimpleNameReferenceExpression(node); - } else { - PsiField field = (PsiField) resolved; - if (!JavaEvaluator.isMemberInClass(field, FQCN_GRAVITY)) { - return super.visitSimpleNameReferenceExpression(node); - } - } - - String message = String.format( - "Use \"`Gravity.%1$s`\" instead of \"`Gravity.%2$s`\" to ensure correct " - + "behavior in right-to-left locales", - (isLeft ? GRAVITY_VALUE_START : GRAVITY_VALUE_END).toUpperCase(Locale.US), - (isLeft ? GRAVITY_VALUE_LEFT : GRAVITY_VALUE_RIGHT).toUpperCase(Locale.US)); - - Location location = mContext.getUastLocation(node); - mContext.report(USE_START, node, location, message); - - return super.visitSimpleNameReferenceExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SQLiteDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SQLiteDetector.java deleted file mode 100644 index c75dbc0..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SQLiteDetector.java +++ /dev/null
@@ -1,115 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Detector which looks for problems related to SQLite usage - */ -public class SQLiteDetector extends Detector implements Detector.UastScanner { - private static final Implementation IMPLEMENTATION = new Implementation( - SQLiteDetector.class, Scope.JAVA_FILE_SCOPE); - - /** Using STRING instead of TEXT for columns */ - public static final Issue ISSUE = Issue.create( - "SQLiteString", //$NON-NLS-1$ - "Using STRING instead of TEXT", - - "In SQLite, any column can store any data type; the declared type for a column " + - "is more of a hint as to what the data should be cast to when stored.\n" + - "\n" + - "There are many ways to store a string. `TEXT`, `VARCHAR`, `CHARACTER` and `CLOB` " + - "are string types, *but `STRING` is not*. Columns defined as STRING are actually " + - "numeric.\n" + - "\n" + - "If you try to store a value in a numeric column, SQLite will try to cast it to a " + - "float or an integer before storing. If it can't, it will just store it as a " + - "string.\n" + - "\n" + - "This can lead to some subtle bugs. For example, when SQLite encounters a string " + - "like `1234567e1234`, it will parse it as a float, but the result will be out of " + - "range for floating point numbers, so `Inf` will be stored! Similarly, strings " + - "that look like integers will lose leading zeroes.\n" + - "\n" + - "To fix this, you can change your schema to use a `TEXT` type instead.", - - Category.CORRECTNESS, - 5, - Severity.WARNING, - IMPLEMENTATION) - .addMoreInfo("https://www.sqlite.org/datatype3.html"); //$NON-NLS-1$ - - // ---- Implements Detector.JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("execSQL"); //$NON-NLS-1$ - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod uMethod) { - PsiMethod method = uMethod.getPsi(); - JavaEvaluator evaluator = context.getEvaluator(); - - if (!JavaEvaluator.isMemberInClass(method, "android.database.sqlite.SQLiteDatabase")) { - return; - } - - int parameterCount = evaluator.getParameterCount(method); - if (parameterCount == 0) { - return; - } - if (!evaluator.parameterHasType(method, 0, TYPE_STRING)) { - return; - } - // Try to resolve the String and look for STRING keys - UExpression argument = call.getValueArguments().get(0); - String sql = ConstantEvaluator.evaluateString(context, argument, true); - if (sql != null && (sql.startsWith("CREATE TABLE") || sql.startsWith("ALTER TABLE")) - && sql.matches(".*\\bSTRING\\b.*")) { - String message = "Using column type STRING; did you mean to use TEXT? " - + "(STRING is a numeric type and its value can be adjusted; for example, " - + "strings that look like integers can drop leading zeroes. See issue " - + "explanation for details.)"; - context.report(ISSUE, call, context.getUastLocation(call), message); - } - - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SdCardDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SdCardDetector.java deleted file mode 100644 index 1eaf45a..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SdCardDetector.java +++ /dev/null
@@ -1,127 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.UastLiteralUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for hardcoded references to /sdcard/. - */ -public class SdCardDetector extends Detector implements Detector.UastScanner { - /** Hardcoded /sdcard/ references */ - public static final Issue ISSUE = Issue.create( - "SdCardPath", //$NON-NLS-1$ - "Hardcoded reference to `/sdcard`", - - "Your code should not reference the `/sdcard` path directly; instead use " + - "`Environment.getExternalStorageDirectory().getPath()`.\n" + - "\n" + - "Similarly, do not reference the `/data/data/` path directly; it can vary " + - "in multi-user scenarios. Instead, use " + - "`Context.getFilesDir().getPath()`.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - SdCardDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo( - "http://developer.android.com/guide/topics/data/data-storage.html#filesExternal"); //$NON-NLS-1$ - - /** Constructs a new {@link SdCardDetector} check */ - public SdCardDetector() { - } - - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - return Collections.<Class<? extends UElement>>singletonList(ULiteralExpression.class); - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new StringChecker(context); - } - - private static class StringChecker extends AbstractUastVisitor { - private final JavaContext mContext; - - private StringChecker(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitClass(@NotNull UClass node) { - return super.visitClass(node); - } - - @Override - public boolean visitLiteralExpression(ULiteralExpression node) { - String s = UastLiteralUtils.getValueIfStringLiteral(node); - if (s != null && !s.isEmpty()) { - char c = s.charAt(0); - if (c != '/' && c != 'f') { - return false; - } - - if (s.startsWith("/sdcard") //$NON-NLS-1$ - || s.startsWith("/mnt/sdcard/") //$NON-NLS-1$ - || s.startsWith("/system/media/sdcard") //$NON-NLS-1$ - || s.startsWith("file://sdcard/") //$NON-NLS-1$ - || s.startsWith("file:///sdcard/")) { //$NON-NLS-1$ - String message = "Do not hardcode \"/sdcard/\"; " + - "use `Environment.getExternalStorageDirectory().getPath()` instead"; - Location location = mContext.getUastLocation(node); - mContext.report(ISSUE, node, location, message); - } else if (s.startsWith("/data/data/") //$NON-NLS-1$ - || s.startsWith("/data/user/")) { //$NON-NLS-1$ - String message = "Do not hardcode \"`/data/`\"; " + - "use `Context.getFilesDir().getPath()` instead"; - Location location = mContext.getUastLocation(node); - mContext.report(ISSUE, node, location, message); - } - } - - return super.visitLiteralExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecureRandomDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecureRandomDetector.java deleted file mode 100644 index 63410a1..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecureRandomDetector.java +++ /dev/null
@@ -1,137 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TypeEvaluator; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiType; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Checks for hardcoded seeds with random numbers. - */ -public class SecureRandomDetector extends Detector implements Detector.UastScanner { - /** Unregistered activities and services */ - public static final Issue ISSUE = Issue.create( - "SecureRandom", //$NON-NLS-1$ - "Using a fixed seed with `SecureRandom`", - - "Specifying a fixed seed will cause the instance to return a predictable sequence " + - "of numbers. This may be useful for testing but it is not appropriate for secure use.", - - Category.SECURITY, - 9, - Severity.WARNING, - new Implementation( - SecureRandomDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo("http://developer.android.com/reference/java/security/SecureRandom.html"); - - private static final String SET_SEED = "setSeed"; //$NON-NLS-1$ - public static final String JAVA_SECURITY_SECURE_RANDOM = "java.security.SecureRandom"; - public static final String JAVA_UTIL_RANDOM = "java.util.Random"; - - /** Constructs a new {@link SecureRandomDetector} */ - public SecureRandomDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList(SET_SEED); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - List<UExpression> arguments = call.getValueArguments(); - if (arguments.isEmpty()) { - return; - } - UExpression seedArgument = arguments.get(0); - JavaEvaluator evaluator = context.getEvaluator(); - if (JavaEvaluator.isMemberInClass(method, JAVA_SECURITY_SECURE_RANDOM) - || evaluator.isMemberInSubClassOf(method, JAVA_UTIL_RANDOM, false) - && isSecureRandomReceiver(context, call)) { - // Called with a fixed seed? - Object seed = ConstantEvaluator.evaluate(context, seedArgument); - //noinspection VariableNotUsedInsideIf - if (seed != null) { - context.report(ISSUE, call, context.getUastLocation(call), - "Do not call `setSeed()` on a `SecureRandom` with a fixed seed: " + - "it is not secure. Use `getSeed()`."); - } else { - // Called with a simple System.currentTimeMillis() seed or something like that? - PsiElement resolvedArgument = UastUtils.tryResolve(seedArgument); - if (resolvedArgument instanceof PsiMethod) { - PsiMethod seedMethod = (PsiMethod) resolvedArgument; - String methodName = seedMethod.getName(); - if (methodName.equals("currentTimeMillis") - || methodName.equals("nanoTime")) { - context.report(ISSUE, call, context.getUastLocation(call), - "It is dangerous to seed `SecureRandom` with the current " - + "time because that value is more predictable to " - + "an attacker than the default seed."); - } - } - } - } - } - - /** - * Returns true if the given invocation is assigned a SecureRandom type - */ - private static boolean isSecureRandomReceiver( - @NonNull JavaContext context, - @NonNull UCallExpression call) { - UElement operand = call.getReceiver(); - return operand != null && isSecureRandomType(context, operand); - } - - /** - * Returns true if the node evaluates to an instance of type SecureRandom - */ - private static boolean isSecureRandomType( - @NonNull JavaContext context, - @NonNull UElement node) { - PsiType type = TypeEvaluator.evaluate(context, node); - return type != null && JAVA_SECURITY_SECURE_RANDOM.equals(type.getCanonicalText()); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecurityDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecurityDetector.java deleted file mode 100644 index 00a6647..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecurityDetector.java +++ /dev/null
@@ -1,458 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_EXPORTED; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_PATH; -import static com.android.SdkConstants.ATTR_PATH_PATTERN; -import static com.android.SdkConstants.ATTR_PATH_PREFIX; -import static com.android.SdkConstants.ATTR_PERMISSION; -import static com.android.SdkConstants.ATTR_READ_PERMISSION; -import static com.android.SdkConstants.ATTR_WRITE_PERMISSION; -import static com.android.SdkConstants.TAG_ACTIVITY; -import static com.android.SdkConstants.TAG_APPLICATION; -import static com.android.SdkConstants.TAG_GRANT_PERMISSION; -import static com.android.SdkConstants.TAG_INTENT_FILTER; -import static com.android.SdkConstants.TAG_PATH_PERMISSION; -import static com.android.SdkConstants.TAG_PROVIDER; -import static com.android.SdkConstants.TAG_RECEIVER; -import static com.android.SdkConstants.TAG_SERVICE; -import static com.android.xml.AndroidManifest.NODE_ACTION; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.USimpleNameReferenceExpression; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -/** - * Checks that exported services request a permission. - */ -public class SecurityDetector extends Detector implements XmlScanner, Detector.UastScanner { - - private static final Implementation IMPLEMENTATION_MANIFEST = new Implementation( - SecurityDetector.class, - Scope.MANIFEST_SCOPE); - - private static final Implementation IMPLEMENTATION_JAVA = new Implementation( - SecurityDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Exported services */ - public static final Issue EXPORTED_SERVICE = Issue.create( - "ExportedService", //$NON-NLS-1$ - "Exported service does not require permission", - "Exported services (services which either set `exported=true` or contain " + - "an intent-filter and do not specify `exported=false`) should define a " + - "permission that an entity must have in order to launch the service " + - "or bind to it. Without this, any application can use this service.", - Category.SECURITY, - 5, - Severity.WARNING, - IMPLEMENTATION_MANIFEST); - - /** Exported content providers */ - public static final Issue EXPORTED_PROVIDER = Issue.create( - "ExportedContentProvider", //$NON-NLS-1$ - "Content provider does not require permission", - "Content providers are exported by default and any application on the " + - "system can potentially use them to read and write data. If the content " + - "provider provides access to sensitive data, it should be protected by " + - "specifying `export=false` in the manifest or by protecting it with a " + - "permission that can be granted to other applications.", - Category.SECURITY, - 5, - Severity.WARNING, - IMPLEMENTATION_MANIFEST); - - /** Exported receivers */ - public static final Issue EXPORTED_RECEIVER = Issue.create( - "ExportedReceiver", //$NON-NLS-1$ - "Receiver does not require permission", - "Exported receivers (receivers which either set `exported=true` or contain " + - "an intent-filter and do not specify `exported=false`) should define a " + - "permission that an entity must have in order to launch the receiver " + - "or bind to it. Without this, any application can use this receiver.", - Category.SECURITY, - 5, - Severity.WARNING, - IMPLEMENTATION_MANIFEST); - - /** Content provides which grant all URIs access */ - public static final Issue OPEN_PROVIDER = Issue.create( - "GrantAllUris", //$NON-NLS-1$ - "Content provider shares everything", - "The `<grant-uri-permission>` element allows specific paths to be shared. " + - "This detector checks for a path URL of just '/' (everything), which is " + - "probably not what you want; you should limit access to a subset.", - Category.SECURITY, - 7, - Severity.WARNING, - IMPLEMENTATION_MANIFEST); - - /** Using java.io.File.setReadable(true, false) to set file world-readable */ - public static final Issue SET_READABLE = Issue.create( - "SetWorldReadable", - "`File.setReadable()` used to make file world-readable", - "Setting files world-readable is very dangerous, and likely to cause security " + - "holes in applications. It is strongly discouraged; instead, applications should " + - "use more formal mechanisms for interactions such as `ContentProvider`, " + - "`BroadcastReceiver`, and `Service`.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION_JAVA); - - /** Using java.io.File.setWritable(true, false) to set file world-writable */ - public static final Issue SET_WRITABLE = Issue.create( - "SetWorldWritable", - "`File.setWritable()` used to make file world-writable", - "Setting files world-writable is very dangerous, and likely to cause security " + - "holes in applications. It is strongly discouraged; instead, applications should " + - "use more formal mechanisms for interactions such as `ContentProvider`, " + - "`BroadcastReceiver`, and `Service`.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION_JAVA); - - /** Using the world-writable flag */ - public static final Issue WORLD_WRITEABLE = Issue.create( - "WorldWriteableFiles", //$NON-NLS-1$ - "`openFileOutput()` or similar call passing `MODE_WORLD_WRITEABLE`", - "There are cases where it is appropriate for an application to write " + - "world writeable files, but these should be reviewed carefully to " + - "ensure that they contain no private data, and that if the file is " + - "modified by a malicious application it does not trick or compromise " + - "your application.", - Category.SECURITY, - 4, - Severity.WARNING, - IMPLEMENTATION_JAVA); - - - /** Using the world-readable flag */ - public static final Issue WORLD_READABLE = Issue.create( - "WorldReadableFiles", //$NON-NLS-1$ - "`openFileOutput()` or similar call passing `MODE_WORLD_READABLE`", - "There are cases where it is appropriate for an application to write " + - "world readable files, but these should be reviewed carefully to " + - "ensure that they contain no private data that is leaked to other " + - "applications.", - Category.SECURITY, - 4, - Severity.WARNING, - IMPLEMENTATION_JAVA); - - private static final String FILE_CLASS = "java.io.File"; //$NON-NLS-1$ - - /** Constructs a new {@link SecurityDetector} check */ - public SecurityDetector() { - } - - // ---- Implements Detector.XmlScanner ---- - - @Override - public Collection<String> getApplicableElements() { - return Arrays.asList( - TAG_SERVICE, - TAG_GRANT_PERMISSION, - TAG_PROVIDER, - TAG_ACTIVITY, - TAG_RECEIVER - ); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - String tag = element.getTagName(); - if (tag.equals(TAG_SERVICE)) { - checkService(context, element); - } else if (tag.equals(TAG_GRANT_PERMISSION)) { - checkGrantPermission(context, element); - } else if (tag.equals(TAG_PROVIDER)) { - checkProvider(context, element); - } else if (tag.equals(TAG_RECEIVER)) { - checkReceiver(context, element); - } - } - - public static boolean getExported(Element element) { - // Used to check whether an activity, service or broadcast receiver is exported. - String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED); - if (exportValue != null && !exportValue.isEmpty()) { - return Boolean.valueOf(exportValue); - } else { - for (Element child : LintUtils.getChildren(element)) { - if (child.getTagName().equals(TAG_INTENT_FILTER)) { - return true; - } - } - } - - return false; - } - - private static boolean isUnprotectedByPermission(Element element) { - // Used to check whether an activity, service or broadcast receiver are - // protected by a permission. - String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION); - if (permission == null || permission.isEmpty()) { - Node parent = element.getParentNode(); - if (parent.getNodeType() == Node.ELEMENT_NODE - && parent.getNodeName().equals(TAG_APPLICATION)) { - Element application = (Element) parent; - permission = application.getAttributeNS(ANDROID_URI, ATTR_PERMISSION); - return permission == null || permission.isEmpty(); - } - } - - return false; - } - - private static boolean isWearableBindListener(@NonNull Element element) { - // Checks whether a service has an Android Wear bind listener - for (Element child : LintUtils.getChildren(element)) { - if (child.getTagName().equals(TAG_INTENT_FILTER)) { - for (Element innerChild : LintUtils.getChildren(child)) { - if (innerChild.getTagName().equals(NODE_ACTION)) { - String name = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME); - if ("com.google.android.gms.wearable.BIND_LISTENER".equals(name)) { - return true; - } - } - } - } - } - - return false; - } - - private static boolean isStandardReceiver(Element element) { - // Play Services also the following receiver which we'll consider standard - // in the sense that it doesn't require a separate permission - String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - if ("com.google.android.gms.tagmanager.InstallReferrerReceiver".equals(name)) { - return true; - } - - // Checks whether a broadcast receiver receives a standard Android action - for (Element child : LintUtils.getChildren(element)) { - if (child.getTagName().equals(TAG_INTENT_FILTER)) { - for (Element innerChild : LintUtils.getChildren(child)) { - if (innerChild.getTagName().equals(NODE_ACTION)) { - String categoryString = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME); - return categoryString.startsWith("android."); //$NON-NLS-1$ - } - } - } - } - - return false; - } - - private static void checkReceiver(XmlContext context, Element element) { - if (getExported(element) && isUnprotectedByPermission(element) && - !isStandardReceiver(element)) { - // No declared permission for this exported receiver: complain - context.report(EXPORTED_RECEIVER, element, context.getLocation(element), - "Exported receiver does not require permission"); - } - } - - private static void checkService(XmlContext context, Element element) { - if (getExported(element) && isUnprotectedByPermission(element) - && !isWearableBindListener(element)) { - // No declared permission for this exported service: complain - context.report(EXPORTED_SERVICE, element, context.getLocation(element), - "Exported service does not require permission"); - } - } - - private static void checkGrantPermission(XmlContext context, Element element) { - Attr path = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH); - Attr prefix = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PREFIX); - Attr pattern = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PATTERN); - - String msg = "Content provider shares everything; this is potentially dangerous."; - if (path != null && path.getValue().equals("/")) { //$NON-NLS-1$ - context.report(OPEN_PROVIDER, path, context.getLocation(path), msg); - } - if (prefix != null && prefix.getValue().equals("/")) { //$NON-NLS-1$ - context.report(OPEN_PROVIDER, prefix, context.getLocation(prefix), msg); - } - if (pattern != null && (pattern.getValue().equals("/") //$NON-NLS-1$ - /* || pattern.getValue().equals(".*")*/)) { - context.report(OPEN_PROVIDER, pattern, context.getLocation(pattern), msg); - } - } - - private static void checkProvider(XmlContext context, Element element) { - String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED); - // Content providers are exported by default - boolean exported = true; - if (exportValue != null && !exportValue.isEmpty()) { - exported = Boolean.valueOf(exportValue); - } - - if (exported) { - // Just check for some use of permissions. Other Lint checks can check the saneness - // of the permissions. We'll accept the permission, readPermission, or writePermission - // attributes on the provider element, or a path-permission element. - String permission = element.getAttributeNS(ANDROID_URI, ATTR_READ_PERMISSION); - if (permission == null || permission.isEmpty()) { - permission = element.getAttributeNS(ANDROID_URI, ATTR_WRITE_PERMISSION); - if (permission == null || permission.isEmpty()) { - permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION); - if (permission == null || permission.isEmpty()) { - // No permission attributes? Check for path-permission. - - // TODO: Add a Lint check to ensure the path-permission is good, similar to - // the grant-uri-permission check. - boolean hasPermission = false; - for (Element child : LintUtils.getChildren(element)) { - String tag = child.getTagName(); - if (tag.equals(TAG_PATH_PERMISSION)) { - hasPermission = true; - break; - } - } - - if (!hasPermission) { - context.report(EXPORTED_PROVIDER, element, - context.getLocation(element), - "Exported content providers can provide access to " + - "potentially sensitive data"); - } - } - } - } - } - } - - // ---- Implements Detector.JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - // These are the API calls that can accept a MODE_WORLD_READABLE/MODE_WORLD_WRITEABLE - // argument. - List<String> values = new ArrayList<String>(3); - values.add("openFileOutput"); //$NON-NLS-1$ - values.add("getSharedPreferences"); //$NON-NLS-1$ - values.add("getDir"); //$NON-NLS-1$ - // These API calls can be used to set files world-readable or world-writable - values.add("setReadable"); //$NON-NLS-1$ - values.add("setWritable"); //$NON-NLS-1$ - return values; - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - List<UExpression> args = node.getValueArguments(); - String methodName = node.getMethodName(); - if (context.getEvaluator().isMemberInSubClassOf(method, FILE_CLASS, false)) { - // Report calls to java.io.File.setReadable(true, false) or - // java.io.File.setWritable(true, false) - if ("setReadable".equals(methodName)) { - if (args.size() == 2 && - Boolean.TRUE.equals(ConstantEvaluator.evaluate(context, args.get(0))) && - Boolean.FALSE.equals(ConstantEvaluator.evaluate(context, args.get(1)))) { - context.report(SET_READABLE, node, context.getUastLocation(node), - "Setting file permissions to world-readable can be " + - "risky, review carefully"); - } - return; - } else if ("setWritable".equals(methodName)) { - if (args.size() == 2 && - Boolean.TRUE.equals(ConstantEvaluator.evaluate(context, args.get(0))) && - Boolean.FALSE.equals(ConstantEvaluator.evaluate(context, args.get(1)))) { - context.report(SET_WRITABLE, node, context.getUastLocation(node), - "Setting file permissions to world-writable can be " + - "risky, review carefully"); - } - return; - } - } - - assert visitor != null; - for (UExpression arg : args) { - arg.accept(visitor); - } - - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new IdentifierVisitor(context); - } - - private static class IdentifierVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - - private IdentifierVisitor(JavaContext context) { - super(); - mContext = context; - } - - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - String name = node.getIdentifier(); - - if ("MODE_WORLD_WRITEABLE".equals(name)) { //$NON-NLS-1$ - Location location = mContext.getUastLocation(node); - mContext.report(WORLD_WRITEABLE, node, location, - "Using `MODE_WORLD_WRITEABLE` when creating files can be " + - "risky, review carefully"); - } else if ("MODE_WORLD_READABLE".equals(name)) { //$NON-NLS-1$ - Location location = mContext.getUastLocation(node); - mContext.report(WORLD_READABLE, node, location, - "Using `MODE_WORLD_READABLE` when creating files can be " + - "risky, review carefully"); - } - - return super.visitSimpleNameReferenceExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ServiceCastDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ServiceCastDetector.java deleted file mode 100644 index a96c171..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ServiceCastDetector.java +++ /dev/null
@@ -1,205 +0,0 @@ -/* - * 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.google.common.collect.Maps; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; - -import org.jetbrains.uast.UBinaryExpressionWithType; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Detector looking for casts on th result of context.getSystemService which are suspect - */ -public class ServiceCastDetector extends Detector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "ServiceCast", //$NON-NLS-1$ - "Wrong system service casts", - - "When you call `Context#getSystemService()`, the result is typically cast to " + - "a specific interface. This lint check ensures that the cast is compatible with " + - "the expected type of the return value.", - - Category.CORRECTNESS, - 6, - Severity.FATAL, - new Implementation( - ServiceCastDetector.class, - Scope.JAVA_FILE_SCOPE)); - - /** Constructs a new {@link ServiceCastDetector} check */ - public ServiceCastDetector() { - } - - // ---- Implements JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("getSystemService"); //$NON-NLS-1$ - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - UElement parent = LintUtils.skipParentheses( - UastUtils.getQualifiedParentOrThis(call).getUastParent()); - if (UastExpressionUtils.isTypeCast(parent)) { - UBinaryExpressionWithType cast = (UBinaryExpressionWithType) parent; - - List<UExpression> args = call.getValueArguments(); - if (args.size() == 1 && args.get(0) instanceof UReferenceExpression) { - PsiElement resolvedServiceConst = ((UReferenceExpression) args.get(0)).resolve(); - if (!(resolvedServiceConst instanceof PsiField)) { - return; - } - String name = ((PsiField) resolvedServiceConst).getName(); - String expectedClass = getExpectedType(name); - if (expectedClass != null && cast != null) { - String castType = cast.getType().getCanonicalText(); - if (castType.indexOf('.') == -1) { - expectedClass = stripPackage(expectedClass); - } - if (!castType.equals(expectedClass)) { - // It's okay to mix and match - // android.content.ClipboardManager and android.text.ClipboardManager - if (isClipboard(castType) && isClipboard(expectedClass)) { - return; - } - - String message = String.format( - "Suspicious cast to `%1$s` for a `%2$s`: expected `%3$s`", - stripPackage(castType), name, stripPackage(expectedClass)); - context.report(ISSUE, call, context.getUastLocation(cast), message); - } - } - - } - } - } - - private static boolean isClipboard(String cls) { - return cls.equals("android.content.ClipboardManager") //$NON-NLS-1$ - || cls.equals("android.text.ClipboardManager"); //$NON-NLS-1$ - } - - private static String stripPackage(String fqcn) { - int index = fqcn.lastIndexOf('.'); - if (index != -1) { - fqcn = fqcn.substring(index + 1); - } - - return fqcn; - } - - @Nullable - private static String getExpectedType(@Nullable String value) { - return value != null ? getServiceMap().get(value) : null; - } - - @NonNull - private static Map<String, String> getServiceMap() { - if (sServiceMap == null) { - final int EXPECTED_SIZE = 55; - sServiceMap = Maps.newHashMapWithExpectedSize(EXPECTED_SIZE); - - sServiceMap.put("ACCESSIBILITY_SERVICE", "android.view.accessibility.AccessibilityManager"); - sServiceMap.put("ACCOUNT_SERVICE", "android.accounts.AccountManager"); - sServiceMap.put("ACTIVITY_SERVICE", "android.app.ActivityManager"); - sServiceMap.put("ALARM_SERVICE", "android.app.AlarmManager"); - sServiceMap.put("APPWIDGET_SERVICE", "android.appwidget.AppWidgetManager"); - sServiceMap.put("APP_OPS_SERVICE", "android.app.AppOpsManager"); - sServiceMap.put("AUDIO_SERVICE", "android.media.AudioManager"); - sServiceMap.put("BATTERY_SERVICE", "android.os.BatteryManager"); - sServiceMap.put("BLUETOOTH_SERVICE", "android.bluetooth.BluetoothManager"); - sServiceMap.put("CAMERA_SERVICE", "android.hardware.camera2.CameraManager"); - sServiceMap.put("CAPTIONING_SERVICE", "android.view.accessibility.CaptioningManager"); - sServiceMap.put("CARRIER_CONFIG_SERVICE", "android.telephony.CarrierConfigManager"); - sServiceMap.put("CLIPBOARD_SERVICE", "android.text.ClipboardManager"); // also allow @Deprecated android.content.ClipboardManager - sServiceMap.put("CONNECTIVITY_SERVICE", "android.net.ConnectivityManager"); - sServiceMap.put("CONSUMER_IR_SERVICE", "android.hardware.ConsumerIrManager"); - sServiceMap.put("DEVICE_POLICY_SERVICE", "android.app.admin.DevicePolicyManager"); - sServiceMap.put("DISPLAY_SERVICE", "android.hardware.display.DisplayManager"); - sServiceMap.put("DOWNLOAD_SERVICE", "android.app.DownloadManager"); - sServiceMap.put("DROPBOX_SERVICE", "android.os.DropBoxManager"); - sServiceMap.put("FINGERPRINT_SERVICE", "android.hardware.fingerprint.FingerprintManager"); - sServiceMap.put("INPUT_METHOD_SERVICE", "android.view.inputmethod.InputMethodManager"); - sServiceMap.put("INPUT_SERVICE", "android.hardware.input.InputManager"); - sServiceMap.put("JOB_SCHEDULER_SERVICE", "android.app.job.JobScheduler"); - sServiceMap.put("KEYGUARD_SERVICE", "android.app.KeyguardManager"); - sServiceMap.put("LAUNCHER_APPS_SERVICE", "android.content.pm.LauncherApps"); - sServiceMap.put("LAYOUT_INFLATER_SERVICE", "android.view.LayoutInflater"); - sServiceMap.put("LOCATION_SERVICE", "android.location.LocationManager"); - sServiceMap.put("MEDIA_PROJECTION_SERVICE", "android.media.projection.MediaProjectionManager"); - sServiceMap.put("MEDIA_ROUTER_SERVICE", "android.media.MediaRouter"); - sServiceMap.put("MEDIA_SESSION_SERVICE", "android.media.session.MediaSessionManager"); - sServiceMap.put("MIDI_SERVICE", "android.media.midi.MidiManager"); - sServiceMap.put("NETWORK_STATS_SERVICE", "android.app.usage.NetworkStatsManager"); - sServiceMap.put("NFC_SERVICE", "android.nfc.NfcManager"); - sServiceMap.put("NOTIFICATION_SERVICE", "android.app.NotificationManager"); - sServiceMap.put("NSD_SERVICE", "android.net.nsd.NsdManager"); - sServiceMap.put("POWER_SERVICE", "android.os.PowerManager"); - sServiceMap.put("PRINT_SERVICE", "android.print.PrintManager"); - sServiceMap.put("RESTRICTIONS_SERVICE", "android.content.RestrictionsManager"); - sServiceMap.put("SEARCH_SERVICE", "android.app.SearchManager"); - sServiceMap.put("SENSOR_SERVICE", "android.hardware.SensorManager"); - sServiceMap.put("STORAGE_SERVICE", "android.os.storage.StorageManager"); - sServiceMap.put("TELECOM_SERVICE", "android.telecom.TelecomManager"); - sServiceMap.put("TELEPHONY_SERVICE", "android.telephony.TelephonyManager"); - sServiceMap.put("TELEPHONY_SUBSCRIPTION_SERVICE", "android.telephony.SubscriptionManager"); - sServiceMap.put("TEXT_SERVICES_MANAGER_SERVICE", "android.view.textservice.TextServicesManager"); - sServiceMap.put("TV_INPUT_SERVICE", "android.media.tv.TvInputManager"); - sServiceMap.put("UI_MODE_SERVICE", "android.app.UiModeManager"); - sServiceMap.put("USAGE_STATS_SERVICE", "android.app.usage.UsageStatsManager"); - sServiceMap.put("USB_SERVICE", "android.hardware.usb.UsbManager"); - sServiceMap.put("USER_SERVICE", "android.os.UserManager"); - sServiceMap.put("VIBRATOR_SERVICE", "android.os.Vibrator"); - sServiceMap.put("WALLPAPER_SERVICE", "android.service.wallpaper.WallpaperService"); - sServiceMap.put("WIFI_P2P_SERVICE", "android.net.wifi.p2p.WifiP2pManager"); - sServiceMap.put("WIFI_SERVICE", "android.net.wifi.WifiManager"); - sServiceMap.put("WINDOW_SERVICE", "android.view.WindowManager"); - - assert sServiceMap.size() == EXPECTED_SIZE : sServiceMap.size(); - } - - return sServiceMap; - } - - private static Map<String, String> sServiceMap; -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetJavaScriptEnabledDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetJavaScriptEnabledDetector.java deleted file mode 100644 index a73612b..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetJavaScriptEnabledDetector.java +++ /dev/null
@@ -1,82 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ConstantEvaluator; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for invocations of android.webkit.WebSettings.setJavaScriptEnabled. - */ -public class SetJavaScriptEnabledDetector extends Detector implements Detector.UastScanner { - /** Invocations of setJavaScriptEnabled */ - public static final Issue ISSUE = Issue.create("SetJavaScriptEnabled", //$NON-NLS-1$ - "Using `setJavaScriptEnabled`", - - "Your code should not invoke `setJavaScriptEnabled` if you are not sure that " + - "your app really requires JavaScript support.", - - Category.SECURITY, - 6, - Severity.WARNING, - new Implementation( - SetJavaScriptEnabledDetector.class, - Scope.JAVA_FILE_SCOPE)) - .addMoreInfo( - "http://developer.android.com/guide/practices/security.html"); //$NON-NLS-1$ - - /** Constructs a new {@link SetJavaScriptEnabledDetector} check */ - public SetJavaScriptEnabledDetector() { - } - - // ---- Implements UastScanner ---- - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - List<UExpression> arguments = call.getValueArguments(); - if (arguments.size() == 1) { - Object constant = ConstantEvaluator.evaluate(context, arguments.get(0)); - if (constant != null && !Boolean.FALSE.equals(constant)) { - context.report(ISSUE, call, context.getUastLocation(call), - "Using `setJavaScriptEnabled` can introduce XSS vulnerabilities " + - "into you application, review carefully."); - } - } - } - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("setJavaScriptEnabled"); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetTextDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetTextDetector.java deleted file mode 100644 index cd141df..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetTextDetector.java +++ /dev/null
@@ -1,149 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.UBinaryExpression; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UQualifiedReferenceExpression; -import org.jetbrains.uast.UastBinaryOperator; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Checks for errors related to TextView#setText and internationalization - */ -public class SetTextDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - SetTextDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Constructing SimpleDateFormat without an explicit locale */ - public static final Issue SET_TEXT_I18N = Issue.create( - "SetTextI18n", //$NON-NLS-1$ - "TextView Internationalization", - - "When calling `TextView#setText`\n" + - "* Never call `Number#toString()` to format numbers; it will not handle fraction " + - "separators and locale-specific digits properly. Consider using `String#format` " + - "with proper format specifications (`%d` or `%f`) instead.\n" + - "* Do not pass a string literal (e.g. \"Hello\") to display text. Hardcoded " + - "text can not be properly translated to other languages. Consider using Android " + - "resource strings instead.\n" + - "* Do not build messages by concatenating text chunks. Such messages can not be " + - "properly translated.", - - Category.I18N, - 6, - Severity.WARNING, - IMPLEMENTATION) - .addMoreInfo("http://developer.android.com/guide/topics/resources/localization.html"); - - - private static final String METHOD_NAME = "setText"; - private static final String TO_STRING_NAME = "toString"; - private static final String CHAR_SEQUENCE_CLS = "java.lang.CharSequence"; - private static final String NUMBER_CLS = "java.lang.Number"; - private static final String TEXT_VIEW_CLS = "android.widget.TextView"; - - // Pattern to match string literal that require translation. These are those having word - // characters in it. - private static final String WORD_PATTERN = ".*\\w{2,}.*"; - - /** Constructs a new {@link SetTextDetector} */ - public SetTextDetector() { - } - - // ---- Implements JavaScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList(METHOD_NAME); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - JavaEvaluator evaluator = context.getEvaluator(); - if (!evaluator.isMemberInSubClassOf(method, TEXT_VIEW_CLS, false)) { - return; - } - if (method.getParameterList().getParametersCount() > 0 && - evaluator.parameterHasType(method, 0, CHAR_SEQUENCE_CLS)) { - checkNode(context, call.getValueArguments().get(0)); - } - } - - private static void checkNode(@NonNull JavaContext context, @Nullable UElement node) { - if (node instanceof ULiteralExpression) { - Object value = ((ULiteralExpression) node).getValue(); - if (value instanceof String && value.toString().matches(WORD_PATTERN)) { - context.report(SET_TEXT_I18N, node, context.getUastLocation(node), - "String literal in `setText` can not be translated. Use Android " - + "resources instead."); - } - } else if (node instanceof UCallExpression) { - PsiMethod calledMethod = ((UCallExpression) node).resolve(); - if (calledMethod != null && TO_STRING_NAME.equals(calledMethod.getName())) { - PsiClass containingClass = UastUtils.getContainingClass(calledMethod); - if (containingClass == null) { - return; - } - - PsiClass superClass = containingClass.getSuperClass(); - if (superClass != null && NUMBER_CLS.equals(superClass.getQualifiedName())) { - context.report(SET_TEXT_I18N, node, context.getUastLocation(node), - "Number formatting does not take into account locale settings. " + - "Consider using `String.format` instead."); - } - } - } else if (node instanceof UQualifiedReferenceExpression) { - UQualifiedReferenceExpression expression = (UQualifiedReferenceExpression) node; - checkNode(context, expression.getReceiver()); - checkNode(context, expression.getSelector()); - } else if (node instanceof UBinaryExpression) { - UBinaryExpression expression = (UBinaryExpression) node; - if (expression.getOperator() == UastBinaryOperator.PLUS) { - context.report(SET_TEXT_I18N, node, context.getUastLocation(node), - "Do not concatenate text displayed with `setText`. " - + "Use resource string with placeholders."); - } - checkNode(context, expression.getLeftOperand()); - checkNode(context, expression.getRightOperand()); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SslCertificateSocketFactoryDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SslCertificateSocketFactoryDetector.java deleted file mode 100644 index da68b8c..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SslCertificateSocketFactoryDetector.java +++ /dev/null
@@ -1,120 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiType; - -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.List; - -public class SslCertificateSocketFactoryDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION_JAVA = new Implementation( - SslCertificateSocketFactoryDetector.class, - Scope.JAVA_FILE_SCOPE); - - public static final Issue CREATE_SOCKET = Issue.create( - "SSLCertificateSocketFactoryCreateSocket", //$NON-NLS-1$ - "Insecure call to `SSLCertificateSocketFactory.createSocket()`", - "When `SSLCertificateSocketFactory.createSocket()` is called with an `InetAddress` " + - "as the first parameter, TLS/SSL hostname verification is not performed, which " + - "could result in insecure network traffic caused by trusting arbitrary " + - "hostnames in TLS/SSL certificates presented by peers. In this case, developers " + - "must ensure that the `InetAddress` is explicitly verified against the certificate " + - "through other means, such as by calling " + - "`SSLCertificateSocketFactory.getDefaultHostnameVerifier() to get a " + - "`HostnameVerifier` and calling `HostnameVerifier.verify()`.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION_JAVA); - - public static final Issue GET_INSECURE = Issue.create( - "SSLCertificateSocketFactoryGetInsecure", //$NON-NLS-1$ - "Call to `SSLCertificateSocketFactory.getInsecure()`", - "The `SSLCertificateSocketFactory.getInsecure()` method returns " + - "an SSLSocketFactory with all TLS/SSL security checks disabled, which " + - "could result in insecure network traffic caused by trusting arbitrary " + - "TLS/SSL certificates presented by peers. This method should be " + - "avoided unless needed for a special circumstance such as " + - "debugging. Instead, `SSLCertificateSocketFactory.getDefault()` " + - "should be used.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION_JAVA); - - private static final String INET_ADDRESS_CLASS = - "java.net.InetAddress"; - - private static final String SSL_CERTIFICATE_SOCKET_FACTORY_CLASS = - "android.net.SSLCertificateSocketFactory"; - - // ---- Implements JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - // Detect calls to potentially insecure SSLCertificateSocketFactory methods - return Arrays.asList("createSocket", "getInsecure"); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - if (context.getEvaluator().isMemberInSubClassOf(method, - SSL_CERTIFICATE_SOCKET_FACTORY_CLASS, false)) { - String methodName = method.getName(); - if ("createSocket".equals(methodName)) { - List<UExpression> args = call.getValueArguments(); - if (!args.isEmpty()) { - PsiType type = args.get(0).getExpressionType(); - if (type != null - && (INET_ADDRESS_CLASS.equals(type.getCanonicalText()) - || InheritanceUtil.isInheritor(((PsiClassType)type).resolve(), false, - INET_ADDRESS_CLASS))) { - context.report(CREATE_SOCKET, call, context.getUastLocation(call), - "Use of `SSLCertificateSocketFactory.createSocket()` " + - "with an InetAddress parameter can cause insecure " + - "network traffic due to trusting arbitrary hostnames in " + - "TLS/SSL certificates presented by peers"); - } - } - } else if ("getInsecure".equals(methodName)) { - context.report(GET_INSECURE, call, context.getUastLocation(call), - "Use of `SSLCertificateSocketFactory.getInsecure()` can cause " + - "insecure network traffic due to trusting arbitrary TLS/SSL " + - "certificates presented by peers"); - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringAuthLeakDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringAuthLeakDetector.java deleted file mode 100644 index 9a6e6ba..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringAuthLeakDetector.java +++ /dev/null
@@ -1,77 +0,0 @@ -package com.android.tools.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.Scope; - -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Detector that looks for leaked credentials in strings. - */ -public class StringAuthLeakDetector extends Detector implements Detector.UastScanner { - - /** Looks for hidden code */ - public static final Issue AUTH_LEAK = Issue.create( - "AuthLeak", "Code might contain an auth leak", - "Strings in java apps can be discovered by decompiling apps, this lint check looks " + - "for code which looks like it may contain an url with a username and password", - Category.SECURITY, 6, Severity.WARNING, - new Implementation(StringAuthLeakDetector.class, Scope.JAVA_FILE_SCOPE)); - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - return Collections.<Class<? extends UElement>>singletonList(ULiteralExpression.class); - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new AuthLeakChecker(context); - } - - private static class AuthLeakChecker extends AbstractUastVisitor { - private final static String LEGAL_CHARS = "([\\w_.!~*\'()%;&=+$,-]+)"; // From RFC 2396 - private final static Pattern AUTH_REGEXP = - Pattern.compile("([\\w+.-]+)://" + LEGAL_CHARS + ':' + LEGAL_CHARS + '@' + - LEGAL_CHARS); - - private final JavaContext mContext; - - private AuthLeakChecker(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitLiteralExpression(ULiteralExpression node) { - if (node.getValue() instanceof String) { - Matcher matcher = AUTH_REGEXP.matcher((String)node.getValue()); - if (matcher.find()) { - String password = matcher.group(3); - if (password == null || (password.startsWith("%") && password.endsWith("s"))) { - return super.visitLiteralExpression(node); - } - Location location = mContext.getUastLocation(node); - mContext.report(AUTH_LEAK, node, location, "Possible credential leak"); - } - } - return super.visitLiteralExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java deleted file mode 100644 index 4dbd361..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java +++ /dev/null
@@ -1,1494 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.CLASS_CONTEXT; -import static com.android.SdkConstants.CLASS_FRAGMENT; -import static com.android.SdkConstants.CLASS_RESOURCES; -import static com.android.SdkConstants.CLASS_V4_FRAGMENT; -import static com.android.SdkConstants.DOT_JAVA; -import static com.android.SdkConstants.FORMAT_METHOD; -import static com.android.SdkConstants.GET_STRING_METHOD; -import static com.android.SdkConstants.TAG_STRING; -import static com.android.tools.klint.client.api.JavaParser.TYPE_BOOLEAN_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_BYTE_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_CHARACTER_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_DOUBLE_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_FLOAT_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_INTEGER_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_LONG_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_OBJECT; -import static com.android.tools.klint.client.api.JavaParser.TYPE_SHORT_WRAPPER; -import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceItem; -import com.android.ide.common.resources.ResourceUrl; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Location.Handle; -import com.android.tools.klint.detector.api.Position; -import com.android.tools.klint.detector.api.ResourceEvaluator; -import com.android.tools.klint.detector.api.ResourceXmlDetector; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.android.utils.Pair; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiParameterList; -import com.intellij.psi.PsiType; -import com.intellij.psi.PsiVariable; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UReferenceExpression; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Check which looks for problems with formatting strings such as inconsistencies between - * translations or between string declaration and string usage in Java. - * <p> - * TODO: Handle Resources.getQuantityString as well - */ -public class StringFormatDetector extends ResourceXmlDetector implements Detector.UastScanner { - private static final Implementation IMPLEMENTATION_XML = new Implementation( - StringFormatDetector.class, - Scope.ALL_RESOURCES_SCOPE); - - @SuppressWarnings("unchecked") - private static final Implementation IMPLEMENTATION_XML_AND_JAVA = new Implementation( - StringFormatDetector.class, - EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE), - Scope.JAVA_FILE_SCOPE); - - - /** Whether formatting strings are invalid */ - public static final Issue INVALID = Issue.create( - "StringFormatInvalid", //$NON-NLS-1$ - "Invalid format string", - - "If a string contains a '%' character, then the string may be a formatting string " + - "which will be passed to `String.format` from Java code to replace each '%' " + - "occurrence with specific values.\n" + - "\n" + - "This lint warning checks for two related problems:\n" + - "(1) Formatting strings that are invalid, meaning that `String.format` will throw " + - "exceptions at runtime when attempting to use the format string.\n" + - "(2) Strings containing '%' that are not formatting strings getting passed to " + - "a `String.format` call. In this case the '%' will need to be escaped as '%%'.\n" + - "\n" + - "NOTE: Not all Strings which look like formatting strings are intended for " + - "use by `String.format`; for example, they may contain date formats intended " + - "for `android.text.format.Time#format()`. Lint cannot always figure out that " + - "a String is a date format, so you may get false warnings in those scenarios. " + - "See the suppress help topic for information on how to suppress errors in " + - "that case.", - - Category.MESSAGES, - 9, - Severity.ERROR, - IMPLEMENTATION_XML); - - /** Whether formatting argument types are consistent across translations */ - public static final Issue ARG_COUNT = Issue.create( - "StringFormatCount", //$NON-NLS-1$ - "Formatting argument types incomplete or inconsistent", - - "When a formatted string takes arguments, it usually needs to reference the " + - "same arguments in all translations (or all arguments if there are no " + - "translations.\n" + - "\n" + - "There are cases where this is not the case, so this issue is a warning rather " + - "than an error by default. However, this usually happens when a language is not " + - "translated or updated correctly.", - Category.MESSAGES, - 5, - Severity.WARNING, - IMPLEMENTATION_XML); - - /** Whether the string format supplied in a call to String.format matches the format string */ - public static final Issue ARG_TYPES = Issue.create( - "StringFormatMatches", //$NON-NLS-1$ - "`String.format` string doesn't match the XML format string", - - "This lint check ensures the following:\n" + - "(1) If there are multiple translations of the format string, then all translations " + - "use the same type for the same numbered arguments\n" + - "(2) The usage of the format string in Java is consistent with the format string, " + - "meaning that the parameter types passed to String.format matches those in the " + - "format string.", - Category.MESSAGES, - 9, - Severity.ERROR, - IMPLEMENTATION_XML_AND_JAVA); - - /** This plural does not use the quantity value */ - public static final Issue POTENTIAL_PLURAL = Issue.create( - "PluralsCandidate", //$NON-NLS-1$ - "Potential Plurals", - - "This lint check looks for potential errors in internationalization where you have " + - "translated a message which involves a quantity and it looks like other parts of " + - "the string may need grammatical changes.\n" + - "\n" + - "For example, rather than something like this:\n" + - " <string name=\"try_again\">Try again in %d seconds.</string>\n" + - "you should be using a plural:\n" + - " <plurals name=\"try_again\">\n" + - " <item quantity=\"one\">Try again in %d second</item>\n" + - " <item quantity=\"other\">Try again in %d seconds</item>\n" + - " </plurals>\n" + - "This will ensure that in other languages the right set of translations are " + - "provided for the different quantity classes.\n" + - "\n" + - "(This check depends on some heuristics, so it may not accurately determine whether " + - "a string really should be a quantity. You can use tools:ignore to filter out false " + - "positives.", - - Category.MESSAGES, - 5, - Severity.WARNING, - IMPLEMENTATION_XML).addMoreInfo( - "http://developer.android.com/guide/topics/resources/string-resource.html#Plurals"); - - /** - * Map from a format string name to a list of declaration file and actual - * formatting string content. We're using a list since a format string can be - * defined multiple times, usually for different translations. - */ - private Map<String, List<Pair<Handle, String>>> mFormatStrings; - - /** - * Map of strings that do not contain any formatting. - */ - private final Map<String, Handle> mNotFormatStrings = new HashMap<String, Handle>(); - - /** - * Set of strings that have an unknown format such as date formatting; we should not - * flag these as invalid when used from a String#format call - */ - private Set<String> mIgnoreStrings; - - /** Constructs a new {@link StringFormatDetector} check */ - public StringFormatDetector() { - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == ResourceFolderType.VALUES; - } - - @Override - public boolean appliesTo(@NonNull Context context, @NonNull File file) { - if (LintUtils.endsWith(file.getName(), DOT_JAVA)) { - return mFormatStrings != null; - } - - return super.appliesTo(context, file); - } - - @Override - public Collection<String> getApplicableElements() { - return Collections.singletonList(TAG_STRING); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { - NodeList childNodes = element.getChildNodes(); - if (childNodes.getLength() > 0) { - if (childNodes.getLength() == 1) { - Node child = childNodes.item(0); - if (child.getNodeType() == Node.TEXT_NODE) { - checkTextNode(context, element, stripQuotes(child.getNodeValue())); - } - } else { - // Concatenate children and build up a plain string. - // This is needed to handle xliff localization documents, - // but this needs more work so ignore compound XML documents as - // string values for now: - StringBuilder sb = new StringBuilder(); - addText(sb, element); - if (sb.length() > 0) { - checkTextNode(context, element, sb.toString()); - } - } - } - } - - private static void addText(StringBuilder sb, Node node) { - if (node.getNodeType() == Node.TEXT_NODE) { - sb.append(stripQuotes(node.getNodeValue().trim())); - } else { - NodeList childNodes = node.getChildNodes(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - addText(sb, childNodes.item(i)); - } - } - } - - /** - * Removes all the unescaped quotes. See - * <a href="http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling">Escaping apostrophes and quotes</a> - */ - @VisibleForTesting - static String stripQuotes(String s) { - StringBuilder sb = new StringBuilder(); - boolean isEscaped = false; - boolean isQuotedBlock = false; - for (int i = 0, len = s.length(); i < len; i++) { - char current = s.charAt(i); - if (isEscaped) { - sb.append(current); - isEscaped = false; - } else { - isEscaped = current == '\\'; // Next char will be escaped so we will just copy it - if (current == '"') { - isQuotedBlock = !isQuotedBlock; - } else if (current == '\'') { - if (isQuotedBlock) { - // We only add single quotes when they are within a quoted block - sb.append(current); - } - } else { - sb.append(current); - } - } - } - - return sb.toString(); - } - - private void checkTextNode(XmlContext context, Element element, String text) { - String name = element.getAttribute(ATTR_NAME); - boolean found = false; - boolean foundPlural = false; - - // Look at the String and see if it's a format string (contains - // positional %'s) - for (int j = 0, m = text.length(); j < m; j++) { - char c = text.charAt(j); - if (c == '\\') { - j++; - } - if (c == '%') { - // Also make sure this String isn't an unformatted String - String formatted = element.getAttribute("formatted"); //$NON-NLS-1$ - if (!formatted.isEmpty() && !Boolean.parseBoolean(formatted)) { - if (!mNotFormatStrings.containsKey(name)) { - Handle handle = context.createLocationHandle(element); - handle.setClientData(element); - mNotFormatStrings.put(name, handle); - } - return; - } - - // See if it's not a format string, e.g. "Battery charge is 100%!". - // If so we want to record this name in a special list such that we can - // make sure you don't attempt to reference this string from a String.format - // call. - Matcher matcher = FORMAT.matcher(text); - if (!matcher.find(j)) { - if (!mNotFormatStrings.containsKey(name)) { - Handle handle = context.createLocationHandle(element); - handle.setClientData(element); - mNotFormatStrings.put(name, handle); - } - return; - } - - String conversion = matcher.group(6); - int conversionClass = getConversionClass(conversion.charAt(0)); - if (conversionClass == CONVERSION_CLASS_UNKNOWN || matcher.group(5) != null) { - if (mIgnoreStrings == null) { - mIgnoreStrings = new HashSet<String>(); - } - mIgnoreStrings.add(name); - - // Don't process any other strings here; some of them could - // accidentally look like a string, e.g. "%H" is a hash code conversion - // in String.format (and hour in Time formatting). - return; - } - - if (conversionClass == CONVERSION_CLASS_INTEGER && !foundPlural) { - // See if there appears to be further text content here. - // Look for whitespace followed by a letter, with no punctuation in between - for (int k = matcher.end(); k < m; k++) { - char nc = text.charAt(k); - if (!Character.isWhitespace(nc)) { - if (Character.isLetter(nc)) { - foundPlural = checkPotentialPlural(context, element, text, k); - } - break; - } - } - } - - found = true; - j++; // Ensure that when we process a "%%" we don't separately check the second % - } - } - - if (!context.getProject().getReportIssues()) { - // If this is a library project not being analyzed, ignore it - return; - } - - if (name != null) { - Handle handle = context.createLocationHandle(element); - handle.setClientData(element); - if (found) { - // Record it for analysis when seen in Java code - if (mFormatStrings == null) { - mFormatStrings = new HashMap<String, List<Pair<Handle, String>>>(); - } - - List<Pair<Handle, String>> list = mFormatStrings.get(name); - if (list == null) { - list = new ArrayList<Pair<Handle, String>>(); - mFormatStrings.put(name, list); - } - list.add(Pair.of(handle, text)); - } else { - if (!isReference(text)) { - mNotFormatStrings.put(name, handle); - } - } - } - } - - private static boolean isReference(@NonNull String text) { - for (int i = 0, n = text.length(); i < n; i++) { - char c = text.charAt(i); - if (!Character.isWhitespace(c)) { - return c == '@' || c == '?'; - } - } - return false; - } - - /** - * Checks whether the text begins with a non-unit word, pointing to a string - * that should probably be a plural instead. This - */ - private static boolean checkPotentialPlural(XmlContext context, Element element, String text, - int wordBegin) { - // This method should only be called if the text is known to start with a word - assert Character.isLetter(text.charAt(wordBegin)); - - int wordEnd = wordBegin; - while (wordEnd < text.length()) { - if (!Character.isLetter(text.charAt(wordEnd))) { - break; - } - wordEnd++; - } - - // Eliminate units, since those are not sentences you need to use plurals for, e.g. - // "Elevation gain: %1$d m (%2$d ft)" - // We'll determine whether something is a unit by looking for - // (1) Multiple uppercase characters (e.g. KB, or MiB), or better yet, uppercase characters - // anywhere but as the first letter - // (2) No vowels (e.g. ft) - // (3) Adjacent consonants (e.g. ft); this one can eliminate some legitimate - // English words as well (e.g. "the") so we should really limit this to - // letter pairs that are not common in English. This is probably overkill - // so not handled yet. Instead we use a simpler heuristic: - // (4) Very short "words" (1-2 letters) - if (wordEnd - wordBegin <= 2) { - // Very short word (1-2 chars): possible unit, e.g. "m", "ft", "kb", etc - return false; - } - boolean hasVowel = false; - for (int i = wordBegin; i < wordEnd; i++) { - // Uppercase character anywhere but first character: probably a unit (e.g. KB) - char c = text.charAt(i); - if (i > wordBegin && Character.isUpperCase(c)) { - return false; - } - if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'y') { - hasVowel = true; - } - } - if (!hasVowel) { - // No vowels: likely unit - return false; - } - - String word = text.substring(wordBegin, wordEnd); - - // Some other known abbreviations that we don't want to count: - if (word.equals("min")) { - return false; - } - - // This heuristic only works in English! - if (LintUtils.isEnglishResource(context, true)) { - String message = String.format("Formatting %%d followed by words (\"%1$s\"): " - + "This should probably be a plural rather than a string", word); - context.report(POTENTIAL_PLURAL, element, - context.getLocation(element), - message); - // Avoid reporting multiple errors on the same string - // (if it contains more than one %d) - return true; - } - - return false; - } - - @Override - public void afterCheckProject(@NonNull Context context) { - if (mFormatStrings != null) { - boolean checkCount = context.isEnabled(ARG_COUNT); - boolean checkValid = context.isEnabled(INVALID); - boolean checkTypes = context.isEnabled(ARG_TYPES); - - // Ensure that all the format strings are consistent with respect to each other; - // e.g. they all have the same number of arguments, they all use all the - // arguments, and they all use the same types for all the numbered arguments - for (Map.Entry<String, List<Pair<Handle, String>>> entry : mFormatStrings.entrySet()) { - String name = entry.getKey(); - List<Pair<Handle, String>> list = entry.getValue(); - - // Check argument counts - if (checkCount) { - Handle notFormatted = mNotFormatStrings.get(name); - if (notFormatted != null) { - list = ImmutableList.<Pair<Handle, String>>builder() - .add(Pair.of(notFormatted, name)).addAll(list).build(); - } - checkArity(context, name, list); - } - - // Check argument types (and also make sure that the formatting strings are valid) - if (checkValid || checkTypes) { - checkTypes(context, checkValid, checkTypes, name, list); - } - } - } - } - - private static void checkTypes(Context context, boolean checkValid, - boolean checkTypes, String name, List<Pair<Handle, String>> list) { - Map<Integer, String> types = new HashMap<Integer, String>(); - Map<Integer, Handle> typeDefinition = new HashMap<Integer, Handle>(); - for (Pair<Handle, String> pair : list) { - Handle handle = pair.getFirst(); - String formatString = pair.getSecond(); - - //boolean warned = false; - Matcher matcher = FORMAT.matcher(formatString); - int index = 0; - int prevIndex = 0; - int nextNumber = 1; - while (true) { - if (matcher.find(index)) { - int matchStart = matcher.start(); - // Make sure this is not an escaped '%' - for (; prevIndex < matchStart; prevIndex++) { - char c = formatString.charAt(prevIndex); - if (c == '\\') { - prevIndex++; - } - } - if (prevIndex > matchStart) { - // We're in an escape, ignore this result - index = prevIndex; - continue; - } - - index = matcher.end(); // Ensure loop proceeds - String str = formatString.substring(matchStart, matcher.end()); - if (str.equals("%%") || str.equals("%n")) { //$NON-NLS-1$ //$NON-NLS-2$ - // Just an escaped % - continue; - } - - if (checkValid) { - // Make sure it's a valid format string - if (str.length() > 2 && str.charAt(str.length() - 2) == ' ') { - char last = str.charAt(str.length() - 1); - // If you forget to include the conversion character, e.g. - // "Weight=%1$ g" instead of "Weight=%1$d g", then - // you're going to end up with a format string interpreted as - // "%1$ g". This means that the space character is interpreted - // as a flag character, but it can only be a flag character - // when used in conjunction with the numeric conversion - // formats (d, o, x, X). If that's not the case, make a - // dedicated error message - if (last != 'd' && last != 'o' && last != 'x' && last != 'X') { - Object clientData = handle.getClientData(); - if (clientData instanceof Node) { - if (context.getDriver().isSuppressed(null, INVALID, - (Node) clientData)) { - return; - } - } - - Location location = handle.resolve(); - String message = String.format( - "Incorrect formatting string `%1$s`; missing conversion " + - "character in '`%2$s`' ?", name, str); - context.report(INVALID, location, message); - //warned = true; - continue; - } - } - } - - if (!checkTypes) { - continue; - } - - // Shouldn't throw a number format exception since we've already - // matched the pattern in the regexp - int number; - String numberString = matcher.group(1); - if (numberString != null) { - // Strip off trailing $ - numberString = numberString.substring(0, numberString.length() - 1); - number = Integer.parseInt(numberString); - nextNumber = number + 1; - } else { - number = nextNumber++; - } - String format = matcher.group(6); - String currentFormat = types.get(number); - if (currentFormat == null) { - types.put(number, format); - typeDefinition.put(number, handle); - } else if (!currentFormat.equals(format) - && isIncompatible(currentFormat.charAt(0), format.charAt(0))) { - - Object clientData = handle.getClientData(); - if (clientData instanceof Node) { - if (context.getDriver().isSuppressed(null, ARG_TYPES, - (Node) clientData)) { - return; - } - } - - Location location = handle.resolve(); - // Attempt to limit the location range to just the formatting - // string in question - location = refineLocation(context, location, formatString, - matcher.start(), matcher.end()); - Location otherLocation = typeDefinition.get(number).resolve(); - otherLocation.setMessage("Conflicting argument type here"); - location.setSecondary(otherLocation); - File f = otherLocation.getFile(); - String message = String.format( - "Inconsistent formatting types for argument #%1$d in " + - "format string `%2$s` ('%3$s'): Found both '`%4$s`' and '`%5$s`' " + - "(in %6$s)", - number, name, - str, - currentFormat, format, - f.getParentFile().getName() + File.separator + f.getName()); - //warned = true; - context.report(ARG_TYPES, location, message); - break; - } - } else { - break; - } - } - - // Check that the format string is valid by actually attempting to instantiate - // it. We only do this if we haven't already complained about this string - // for other reasons. - /* Check disabled for now: it had many false reports due to conversion - * errors (which is expected since we just pass in strings), but once those - * are eliminated there aren't really any other valid error messages returned - * (for example, calling the formatter with bogus formatting flags always just - * returns a "conversion" error. It looks like we'd need to actually pass compatible - * arguments to trigger other types of formatting errors such as precision errors. - if (!warned && checkValid) { - try { - formatter.format(formatString, "", "", "", "", "", "", "", - "", "", "", "", "", "", ""); - - } catch (IllegalFormatException t) { // TODO: UnknownFormatConversionException - if (!t.getLocalizedMessage().contains(" != ") - && !t.getLocalizedMessage().contains("Conversion")) { - Location location = handle.resolve(); - context.report(INVALID, location, - String.format("Wrong format for %1$s: %2$s", - name, t.getLocalizedMessage()), null); - } - } - } - */ - } - } - - /** - * Returns true if two String.format conversions are "incompatible" (meaning - * that using these two for the same argument across different translations - * is more likely an error than intentional. Some conversions are - * incompatible, e.g. "d" and "s" where one is a number and string, whereas - * others may work (e.g. float versus integer) but are probably not - * intentional. - */ - private static boolean isIncompatible(char conversion1, char conversion2) { - int class1 = getConversionClass(conversion1); - int class2 = getConversionClass(conversion2); - return class1 != class2 - && class1 != CONVERSION_CLASS_UNKNOWN - && class2 != CONVERSION_CLASS_UNKNOWN; - } - - private static final int CONVERSION_CLASS_UNKNOWN = 0; - private static final int CONVERSION_CLASS_STRING = 1; - private static final int CONVERSION_CLASS_CHARACTER = 2; - private static final int CONVERSION_CLASS_INTEGER = 3; - private static final int CONVERSION_CLASS_FLOAT = 4; - private static final int CONVERSION_CLASS_BOOLEAN = 5; - private static final int CONVERSION_CLASS_HASHCODE = 6; - private static final int CONVERSION_CLASS_PERCENT = 7; - private static final int CONVERSION_CLASS_NEWLINE = 8; - private static final int CONVERSION_CLASS_DATETIME = 9; - - private static int getConversionClass(char conversion) { - // See http://developer.android.com/reference/java/util/Formatter.html - switch (conversion) { - case 't': // Time/date conversion - case 'T': - return CONVERSION_CLASS_DATETIME; - case 's': // string - case 'S': // Uppercase string - return CONVERSION_CLASS_STRING; - case 'c': // character - case 'C': // Uppercase character - return CONVERSION_CLASS_CHARACTER; - case 'd': // decimal - case 'o': // octal - case 'x': // hex - case 'X': - return CONVERSION_CLASS_INTEGER; - case 'f': // decimal float - case 'e': // exponential float - case 'E': - case 'g': // decimal or exponential depending on size - case 'G': - case 'a': // hex float - case 'A': - return CONVERSION_CLASS_FLOAT; - case 'b': // boolean - case 'B': - return CONVERSION_CLASS_BOOLEAN; - case 'h': // boolean - case 'H': - return CONVERSION_CLASS_HASHCODE; - case '%': // literal - return CONVERSION_CLASS_PERCENT; - case 'n': // literal - return CONVERSION_CLASS_NEWLINE; - } - - return CONVERSION_CLASS_UNKNOWN; - } - - private static Location refineLocation(Context context, Location location, - String formatString, int substringStart, int substringEnd) { - Position startLocation = location.getStart(); - Position endLocation = location.getEnd(); - if (startLocation != null && endLocation != null) { - int startOffset = startLocation.getOffset(); - int endOffset = endLocation.getOffset(); - if (startOffset >= 0) { - String contents = context.getClient().readFile(location.getFile()); - if (endOffset <= contents.length() && startOffset < endOffset) { - int formatOffset = contents.indexOf(formatString, startOffset); - if (formatOffset != -1 && formatOffset <= endOffset) { - return Location.create(location.getFile(), contents, - formatOffset + substringStart, formatOffset + substringEnd); - } - } - } - } - - return location; - } - - /** - * Check that the number of arguments in the format string is consistent - * across translations, and that all arguments are used - */ - private static void checkArity(Context context, String name, List<Pair<Handle, String>> list) { - // Check to make sure that the argument counts and types are consistent - int prevCount = -1; - for (Pair<Handle, String> pair : list) { - Set<Integer> indices = new HashSet<Integer>(); - int count = getFormatArgumentCount(pair.getSecond(), indices); - Handle handle = pair.getFirst(); - if (prevCount != -1 && prevCount != count) { - Object clientData = handle.getClientData(); - if (clientData instanceof Node) { - if (context.getDriver().isSuppressed(null, ARG_COUNT, (Node) clientData)) { - return; - } - } - Location location = handle.resolve(); - Location secondary = list.get(0).getFirst().resolve(); - secondary.setMessage("Conflicting number of arguments here"); - location.setSecondary(secondary); - String message = String.format( - "Inconsistent number of arguments in formatting string `%1$s`; " + - "found both %2$d and %3$d", name, prevCount, count); - context.report(ARG_COUNT, location, message); - break; - } - - for (int i = 1; i <= count; i++) { - if (!indices.contains(i)) { - Object clientData = handle.getClientData(); - if (clientData instanceof Node) { - if (context.getDriver().isSuppressed(null, ARG_COUNT, - (Node) clientData)) { - return; - } - } - - Set<Integer> all = new HashSet<Integer>(); - for (int j = 1; j < count; j++) { - all.add(j); - } - all.removeAll(indices); - List<Integer> sorted = new ArrayList<Integer>(all); - Collections.sort(sorted); - Location location = handle.resolve(); - String message = String.format( - "Formatting string '`%1$s`' is not referencing numbered arguments %2$s", - name, sorted); - context.report(ARG_COUNT, location, message); - break; - } - } - - prevCount = count; - } - } - - // See java.util.Formatter docs - public static final Pattern FORMAT = Pattern.compile( - // Generic format: - // %[argument_index$][flags][width][.precision]conversion - // - "%" + //$NON-NLS-1$ - // Argument Index - "(\\d+\\$)?" + //$NON-NLS-1$ - // Flags - "([-+#, 0(<]*)?" + //$NON-NLS-1$ - // Width - "(\\d+)?" + //$NON-NLS-1$ - // Precision - "(\\.\\d+)?" + //$NON-NLS-1$ - // Conversion. These are all a single character, except date/time conversions - // which take a prefix of t/T: - "([tT])?" + //$NON-NLS-1$ - // The current set of conversion characters are - // b,h,s,c,d,o,x,e,f,g,a,t (as well as all those as upper-case characters), plus - // n for newlines and % as a literal %. And then there are all the time/date - // characters: HIKLm etc. Just match on all characters here since there should - // be at least one. - "([a-zA-Z%])"); //$NON-NLS-1$ - - /** Given a format string returns the format type of the given argument */ - @VisibleForTesting - @Nullable - static String getFormatArgumentType(String s, int argument) { - Matcher matcher = FORMAT.matcher(s); - int index = 0; - int prevIndex = 0; - int nextNumber = 1; - while (true) { - if (matcher.find(index)) { - String value = matcher.group(6); - if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$ - index = matcher.end(); - continue; - } - int matchStart = matcher.start(); - // Make sure this is not an escaped '%' - for (; prevIndex < matchStart; prevIndex++) { - char c = s.charAt(prevIndex); - if (c == '\\') { - prevIndex++; - } - } - if (prevIndex > matchStart) { - // We're in an escape, ignore this result - index = prevIndex; - continue; - } - - // Shouldn't throw a number format exception since we've already - // matched the pattern in the regexp - int number; - String numberString = matcher.group(1); - if (numberString != null) { - // Strip off trailing $ - numberString = numberString.substring(0, numberString.length() - 1); - number = Integer.parseInt(numberString); - nextNumber = number + 1; - } else { - number = nextNumber++; - } - - if (number == argument) { - return matcher.group(6); - } - index = matcher.end(); - } else { - break; - } - } - - return null; - } - - /** - * Given a format string returns the number of required arguments. If the - * {@code seenArguments} parameter is not null, put the indices of any - * observed arguments into it. - */ - static int getFormatArgumentCount(@NonNull String s, @Nullable Set<Integer> seenArguments) { - Matcher matcher = FORMAT.matcher(s); - int index = 0; - int prevIndex = 0; - int nextNumber = 1; - int max = 0; - while (true) { - if (matcher.find(index)) { - String value = matcher.group(6); - if ("%".equals(value) || "n".equals(value)) { //$NON-NLS-1$ //$NON-NLS-2$ - index = matcher.end(); - continue; - } - int matchStart = matcher.start(); - // Make sure this is not an escaped '%' - for (; prevIndex < matchStart; prevIndex++) { - char c = s.charAt(prevIndex); - if (c == '\\') { - prevIndex++; - } - } - if (prevIndex > matchStart) { - // We're in an escape, ignore this result - index = prevIndex; - continue; - } - - // Shouldn't throw a number format exception since we've already - // matched the pattern in the regexp - int number; - String numberString = matcher.group(1); - if (numberString != null) { - // Strip off trailing $ - numberString = numberString.substring(0, numberString.length() - 1); - number = Integer.parseInt(numberString); - nextNumber = number + 1; - } else { - number = nextNumber++; - } - - if (number > max) { - max = number; - } - if (seenArguments != null) { - seenArguments.add(number); - } - - index = matcher.end(); - } else { - break; - } - } - - return max; - } - - /** - * Determines whether the given {@link String#format(String, Object...)} - * formatting string is "locale dependent", meaning that its output depends - * on the locale. This is the case if it for example references decimal - * numbers of dates and times. - * - * @param format the format string - * @return true if the format is locale sensitive, false otherwise - */ - public static boolean isLocaleSpecific(@NonNull String format) { - if (format.indexOf('%') == -1) { - return false; - } - - Matcher matcher = FORMAT.matcher(format); - int index = 0; - int prevIndex = 0; - while (true) { - if (matcher.find(index)) { - int matchStart = matcher.start(); - // Make sure this is not an escaped '%' - for (; prevIndex < matchStart; prevIndex++) { - char c = format.charAt(prevIndex); - if (c == '\\') { - prevIndex++; - } - } - if (prevIndex > matchStart) { - // We're in an escape, ignore this result - index = prevIndex; - continue; - } - - String type = matcher.group(6); - if (!type.isEmpty()) { - char t = type.charAt(0); - - // The following formatting characters are locale sensitive: - switch (t) { - case 'd': // decimal integer - case 'e': // scientific - case 'E': - case 'f': // decimal float - case 'g': // general - case 'G': - case 't': // date/time - case 'T': - return true; - } - } - index = matcher.end(); - } else { - break; - } - } - - return false; - } - - @Override - public List<String> getApplicableMethodNames() { - return Arrays.asList(FORMAT_METHOD, GET_STRING_METHOD); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod method) { - if (mFormatStrings == null && !context.getClient().supportsProjectResources()) { - return; - } - - JavaEvaluator evaluator = context.getEvaluator(); - String methodName = method.getName(); - if (methodName.equals(FORMAT_METHOD)) { - if (JavaEvaluator.isMemberInClass(method, TYPE_STRING)) { - // Check formatting parameters for - // java.lang.String#format(String format, Object... formatArgs) - // java.lang.String#format(Locale locale, String format, Object... formatArgs) - checkStringFormatCall(context, method, node, - method.getParameterList().getParametersCount() == 3); - - // TODO: Consider also enforcing - // java.util.Formatter#format(String string, Object... formatArgs) - } - } else { - // Look up any of these string formatting methods: - // android.content.res.Resources#getString(@StringRes int resId, Object... formatArgs) - // android.content.Context#getString(@StringRes int resId, Object... formatArgs) - // android.app.Fragment#getString(@StringRes int resId, Object... formatArgs) - // android.support.v4.app.Fragment#getString(@StringRes int resId, Object... formatArgs) - - // Many of these also define a plain getString method: - // android.content.res.Resources#getString(@StringRes int resId) - // However, while it's possible that these contain formatting strings) it's - // also possible that they're looking up strings that are not intended to be used - // for formatting so while we may want to warn about this it's not necessarily - // an error. - if (method.getParameterList().getParametersCount() < 2) { - return; - } - - if (evaluator.isMemberInSubClassOf(method, CLASS_RESOURCES, false) || - evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT, false) || - evaluator.isMemberInSubClassOf(method, CLASS_FRAGMENT, false) || - evaluator.isMemberInSubClassOf(method, CLASS_V4_FRAGMENT, false)) { - checkStringFormatCall(context, method, node, false); - } - - // TODO: Consider also looking up - // android.content.res.Resources#getQuantityString(@PluralsRes int id, int quantity, - // Object... formatArgs) - // though this will require being smarter about cross referencing formatting - // strings since we'll need to go via the quantity string definitions - } - } - - /** - * Checks a String.format call that is using a string that doesn't contain format placeholders. - * @param context the context to report errors to - * @param call the AST node for the {@link String#format} - * @param name the string name - * @param handle the string location - */ - private static void checkNotFormattedHandle( - JavaContext context, - UCallExpression call, - String name, - Handle handle) { - Object clientData = handle.getClientData(); - if (clientData instanceof Node) { - if (context.getDriver().isSuppressed(null, INVALID, (Node) clientData)) { - return; - } - } - Location location = context.getUastLocation(call); - Location secondary = handle.resolve(); - secondary.setMessage("This definition does not require arguments"); - location.setSecondary(secondary); - String message = String.format( - "Format string '`%1$s`' is not a valid format string so it should not be " + - "passed to `String.format`", - name); - context.report(INVALID, call, location, message); - } - - /** - * Check the given String.format call (with the given arguments) to see if the string format is - * being used correctly - * @param context the context to report errors to - * @param calledMethod the method being called - * @param call the AST node for the {@link String#format} - * @param specifiesLocale whether the first parameter is a locale string, shifting the - */ - private void checkStringFormatCall( - JavaContext context, - PsiMethod calledMethod, - UCallExpression call, - boolean specifiesLocale) { - - int argIndex = specifiesLocale ? 1 : 0; - List<UExpression> args = call.getValueArguments(); - - if (args.size() <= argIndex) { - return; - } - - UExpression argument = args.get(argIndex); - ResourceUrl resource = ResourceEvaluator.getResource(context, argument); - if (resource == null || resource.framework || resource.type != ResourceType.STRING) { - return; - } - - String name = resource.name; - if (mIgnoreStrings != null && mIgnoreStrings.contains(name)) { - return; - } - - boolean passingVarArgsArray = false; - int callCount = args.size() - 1 - argIndex; - - if (callCount == 1) { - // If instead of a varargs call like - // getString(R.string.foo, arg1, arg2, arg3) - // the code is calling the varargs method with a packed Object array, as in - // getString(R.string.foo, new Object[] { arg1, arg2, arg3 }) - // we'll need to handle that such that we don't think this is a single - // argument - - UExpression lastArg = args.get(args.size() - 1); - PsiParameterList parameterList = calledMethod.getParameterList(); - int parameterCount = parameterList.getParametersCount(); - if (parameterCount > 0 && parameterList.getParameters()[parameterCount - 1].isVarArgs()) { - boolean knownArity = false; - - boolean argWasReference = false; - if (lastArg instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) lastArg).resolve(); - if (resolved instanceof PsiVariable) { - UExpression initializer = context.getUastContext().getInitializerBody ( - (PsiVariable) resolved); - if (initializer != null && - (UastExpressionUtils.isNewArray(initializer) || - UastExpressionUtils.isArrayInitializer(initializer))) { - argWasReference = true; - // Now handled by check below - lastArg = initializer; - } - } - } - - if (UastExpressionUtils.isNewArray(lastArg) || - UastExpressionUtils.isArrayInitializer(lastArg)) { - UCallExpression arrayInitializer = (UCallExpression) lastArg; - - if (UastExpressionUtils.isNewArrayWithInitializer(lastArg) || - UastExpressionUtils.isArrayInitializer(lastArg)) { - callCount = arrayInitializer.getValueArgumentCount(); - knownArity = true; - } else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) { - List<UExpression> arrayDimensions = arrayInitializer.getValueArguments(); - if (arrayDimensions.size() == 1) { - UExpression first = arrayDimensions.get(0); - if (first instanceof ULiteralExpression) { - Object o = ((ULiteralExpression) first).getValue(); - if (o instanceof Integer) { - callCount = (Integer)o; - knownArity = true; - } - } - } - } - - if (!knownArity) { - if (!argWasReference) { - return; - } - } else { - passingVarArgsArray = true; - } - } - } - } - - if (callCount > 0 && mNotFormatStrings.containsKey(name)) { - checkNotFormattedHandle(context, call, name, mNotFormatStrings.get(name)); - return; - } - - List<Pair<Handle, String>> list = mFormatStrings != null ? mFormatStrings.get(name) : null; - if (list == null) { - LintClient client = context.getClient(); - if (client.supportsProjectResources() && - !context.getScope().contains(Scope.RESOURCE_FILE)) { - AbstractResourceRepository resources = client - .getProjectResources(context.getMainProject(), true); - List<ResourceItem> items; - if (resources != null) { - items = resources.getResourceItem(ResourceType.STRING, name); - } else { - // Must be a non-Android module - items = null; - } - if (items != null) { - for (final ResourceItem item : items) { - ResourceValue v = item.getResourceValue(false); - if (v != null) { - String value = v.getRawXmlValue(); - if (value != null) { - // Make sure it's really a formatting string, - // not for example "Battery remaining: 90%" - boolean isFormattingString = value.indexOf('%') != -1; - for (int j = 0, m = value.length(); - j < m && isFormattingString; - j++) { - char c = value.charAt(j); - if (c == '\\') { - j++; - } else if (c == '%') { - Matcher matcher = FORMAT.matcher(value); - if (!matcher.find(j)) { - isFormattingString = false; - } else { - String conversion = matcher.group(6); - int conversionClass = getConversionClass( - conversion.charAt(0)); - if (conversionClass == CONVERSION_CLASS_UNKNOWN - || matcher.group(5) != null) { - // Some date format etc - don't process - return; - } - } - j++; // Don't process second % in a %% - } - // If the user marked the string with - } - - Handle handle = client.createResourceItemHandle(item); - if (isFormattingString) { - if (list == null) { - list = Lists.newArrayList(); - if (mFormatStrings == null) { - mFormatStrings = Maps.newHashMap(); - } - mFormatStrings.put(name, list); - } - list.add(Pair.of(handle, value)); - } else if (callCount > 0) { - checkNotFormattedHandle(context, call, name, handle); - } - } - } - } - } - } else { - return; - } - } - - if (list != null) { - Set<String> reported = null; - for (Pair<Handle, String> pair : list) { - String s = pair.getSecond(); - if (reported != null && reported.contains(s)) { - continue; - } - int count = getFormatArgumentCount(s, null); - Handle handle = pair.getFirst(); - if (count != callCount) { - Location location = context.getUastLocation(call); - Location secondary = handle.resolve(); - secondary.setMessage(String.format("This definition requires %1$d arguments", - count)); - location.setSecondary(secondary); - String message = String.format( - "Wrong argument count, format string `%1$s` requires `%2$d` but format " + - "call supplies `%3$d`", - name, count, callCount); - context.report(ARG_TYPES, call, location, message); - if (reported == null) { - reported = Sets.newHashSet(); - } - reported.add(s); - } else { - if (passingVarArgsArray) { - // Can't currently check these: make sure we don't incorrectly - // flag parameters on the Object[] instead of the wrapped parameters - return; - } - for (int i = 1; i <= count; i++) { - int argumentIndex = i + argIndex; - PsiType type = args.get(argumentIndex).getExpressionType(); - if (type != null) { - boolean valid = true; - String formatType = getFormatArgumentType(s, i); - if (formatType == null) { - continue; - } - char last = formatType.charAt(formatType.length() - 1); - if (formatType.length() >= 2 && - Character.toLowerCase( - formatType.charAt(formatType.length() - 2)) == 't') { - // Date time conversion. - // TODO - continue; - } - switch (last) { - // Booleans. It's okay to pass objects to these; - // it will print "true" if non-null, but it's - // unusual and probably not intended. - case 'b': - case 'B': - valid = isBooleanType(type); - break; - - // Numeric: integer and floats in various formats - case 'x': - case 'X': - case 'd': - case 'o': - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': - case 'a': - case 'A': - valid = isNumericType(type, true); - break; - case 'c': - case 'C': - // Unicode character - valid = isCharacterType(type); - break; - case 'h': - case 'H': // Hex print of hash code of objects - case 's': - case 'S': - // String. Can pass anything, but warn about - // numbers since you may have meant more - // specific formatting. Use special issue - // explanation for this? - valid = !isBooleanType(type) && - !isNumericType(type, false); - break; - } - - if (!valid) { - Location location = context.getUastLocation(args.get(argumentIndex)); - Location secondary = handle.resolve(); - secondary.setMessage("Conflicting argument declaration here"); - location.setSecondary(secondary); - String suggestion = null; - if (isBooleanType(type)) { - suggestion = "`b`"; - } else if (isCharacterType(type)) { - suggestion = "'c'"; - } else if (PsiType.INT.equals(type) - || PsiType.LONG.equals(type) - || PsiType.BYTE.equals(type) - || PsiType.SHORT.equals(type)) { - suggestion = "`d`, 'o' or `x`"; - } else if (PsiType.FLOAT.equals(type) - || PsiType.DOUBLE.equals(type)) { - suggestion = "`e`, 'f', 'g' or `a`"; - } else if (type instanceof PsiClassType) { - String fqn = type.getCanonicalText(); - if (TYPE_INTEGER_WRAPPER.equals(fqn) - || TYPE_LONG_WRAPPER.equals(fqn) - || TYPE_BYTE_WRAPPER.equals(fqn) - || TYPE_SHORT_WRAPPER.equals(fqn)) { - suggestion = "`d`, 'o' or `x`"; - } else if (TYPE_FLOAT_WRAPPER.equals(fqn) - || TYPE_DOUBLE_WRAPPER.equals(fqn)) { - suggestion = "`d`, 'o' or `x`"; - } else if (TYPE_OBJECT.equals(fqn)) { - suggestion = "'s' or 'h'"; - } - } - - if (suggestion != null) { - suggestion = " (Did you mean formatting character " - + suggestion + "?)"; - } else { - suggestion = ""; - } - - String canonicalText = type.getCanonicalText(); - canonicalText = canonicalText.substring( - canonicalText.lastIndexOf('.') + 1); - - String message = String.format( - "Wrong argument type for formatting argument '#%1$d' " + - "in `%2$s`: conversion is '`%3$s`', received `%4$s` " + - "(argument #%5$d in method call)%6$s", - i, name, formatType, canonicalText, - argumentIndex + 1, suggestion); - context.report(ARG_TYPES, call, location, message); - if (reported == null) { - reported = Sets.newHashSet(); - } - reported.add(s); - } - } - } - } - } - } - } - - private static boolean isCharacterType(PsiType type) { - //return PsiType.CHAR.isAssignableFrom(type); - if (type == PsiType.CHAR) { - return true; - } - if (type instanceof PsiClassType) { - String fqn = type.getCanonicalText(); - return TYPE_CHARACTER_WRAPPER.equals(fqn); - } - - return false; - } - - private static boolean isBooleanType(PsiType type) { - //return PsiType.BOOLEAN.isAssignableFrom(type); - if (type == PsiType.BOOLEAN) { - return true; - } - if (type instanceof PsiClassType) { - String fqn = type.getCanonicalText(); - return TYPE_BOOLEAN_WRAPPER.equals(fqn); - } - - return false; - } - - //PsiType:java.lang.Boolean - private static boolean isNumericType(@NonNull PsiType type, boolean allowBigNumbers) { - if (PsiType.INT.equals(type) - || PsiType.FLOAT.equals(type) - || PsiType.DOUBLE.equals(type) - || PsiType.LONG.equals(type) - || PsiType.BYTE.equals(type) - || PsiType.SHORT.equals(type)) { - return true; - } - - if (type instanceof PsiClassType) { - String fqn = type.getCanonicalText(); - if (TYPE_INTEGER_WRAPPER.equals(fqn) - || TYPE_FLOAT_WRAPPER.equals(fqn) - || TYPE_DOUBLE_WRAPPER.equals(fqn) - || TYPE_LONG_WRAPPER.equals(fqn) - || TYPE_BYTE_WRAPPER.equals(fqn) - || TYPE_SHORT_WRAPPER.equals(fqn)) { - return true; - } - if (allowBigNumbers) { - if ("java.math.BigInteger".equals(fqn) || - "java.math.BigDecimal".equals(fqn)) { - return true; - } - } - } - - return false; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java deleted file mode 100644 index fd19657..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java +++ /dev/null
@@ -1,1901 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.resources.ResourceType; -import com.android.sdklib.AndroidVersion; -import com.android.tools.klint.checks.PermissionFinder.Operation; -import com.android.tools.klint.checks.PermissionFinder.Result; -import com.android.tools.klint.checks.PermissionHolder.SetPermissionLookup; -import com.android.tools.klint.client.api.ExternalReferenceExpression; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.client.api.UastLintUtils; -import com.android.tools.klint.detector.api.*; -import com.android.utils.XmlUtils; -import com.google.common.base.Joiner; -import com.google.common.collect.Sets; -import com.intellij.psi.*; -import org.jetbrains.uast.*; -import org.jetbrains.uast.java.JavaUAnnotation; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.File; -import java.util.*; - -import static com.android.SdkConstants.*; -import static com.android.resources.ResourceType.*; -import static com.android.tools.klint.checks.PermissionFinder.Operation.*; -import static com.android.tools.klint.checks.PermissionRequirement.*; -import static com.android.tools.klint.detector.api.ResourceEvaluator.*; -import static org.jetbrains.uast.UastUtils.getQualifiedParentOrThis; - -/** - * Looks up annotations on method calls and enforces the various things they - * express, e.g. for {@code @CheckReturn} it makes sure the return value is used, - * for {@code ColorInt} it ensures that a proper color integer is passed in, etc. - * - * TODO: Throw in some annotation usage checks here too; e.g. specifying @Size without parameters, - * specifying toInclusive without setting to, combining @ColorInt with any @ResourceTypeRes, - * using @CheckResult on a void method, etc. - */ -@SuppressWarnings("WeakerAccess") -public class SupportAnnotationDetector extends Detector implements Detector.UastScanner { - - public static final Implementation IMPLEMENTATION - = new Implementation(SupportAnnotationDetector.class, Scope.JAVA_FILE_SCOPE); - - /** Method result should be used */ - public static final Issue RANGE = Issue.create( - "Range", //$NON-NLS-1$ - "Outside Range", - - "Some parameters are required to in a particular numerical range; this check " + - "makes sure that arguments passed fall within the range. For arrays, Strings " + - "and collections this refers to the size or length.", - - Category.CORRECTNESS, - 6, - Severity.ERROR, - IMPLEMENTATION); - - /** - * Attempting to set a resource id as a color - */ - public static final Issue RESOURCE_TYPE = Issue.create( - "ResourceType", //$NON-NLS-1$ - "Wrong Resource Type", - - "Ensures that resource id's passed to APIs are of the right type; for example, " + - "calling `Resources.getColor(R.string.name)` is wrong.", - - Category.CORRECTNESS, - 7, - Severity.FATAL, - IMPLEMENTATION); - - /** Attempting to set a resource id as a color */ - public static final Issue COLOR_USAGE = Issue.create( - "ResourceAsColor", //$NON-NLS-1$ - "Should pass resolved color instead of resource id", - - "Methods that take a color in the form of an integer should be passed " + - "an RGB triple, not the actual color resource id. You must call " + - "`getResources().getColor(resource)` to resolve the actual color value first.", - - Category.CORRECTNESS, - 7, - Severity.ERROR, - IMPLEMENTATION); - - /** Passing the wrong constant to an int or String method */ - public static final Issue TYPE_DEF = Issue.create( - "WrongConstant", //$NON-NLS-1$ - "Incorrect constant", - - "Ensures that when parameter in a method only allows a specific set " + - "of constants, calls obey those rules.", - - Category.SECURITY, - 6, - Severity.ERROR, - IMPLEMENTATION); - - /** Method result should be used */ - public static final Issue CHECK_RESULT = Issue.create( - "CheckResult", //$NON-NLS-1$ - "Ignoring results", - - "Some methods have no side effects, an calling them without doing something " + - "without the result is suspicious. ", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - IMPLEMENTATION); - - /** Failing to enforce security by just calling check permission */ - public static final Issue CHECK_PERMISSION = Issue.create( - "UseCheckPermission", //$NON-NLS-1$ - "Using the result of check permission calls", - - "You normally want to use the result of checking a permission; these methods " + - "return whether the permission is held; they do not throw an error if the permission " + - "is not granted. Code which does not do anything with the return value probably " + - "meant to be calling the enforce methods instead, e.g. rather than " + - "`Context#checkCallingPermission` it should call `Context#enforceCallingPermission`.", - - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION); - - /** Method result should be used */ - public static final Issue MISSING_PERMISSION = Issue.create( - "MissingPermission", //$NON-NLS-1$ - "Missing Permissions", - - "This check scans through your code and libraries and looks at the APIs being used, " + - "and checks this against the set of permissions required to access those APIs. If " + - "the code using those APIs is called at runtime, then the program will crash.\n" + - "\n" + - "Furthermore, for permissions that are revocable (with targetSdkVersion 23), client " + - "code must also be prepared to handle the calls throwing an exception if the user " + - "rejects the request for permission at runtime.", - - Category.CORRECTNESS, - 9, - Severity.ERROR, - IMPLEMENTATION); - - /** Passing the wrong constant to an int or String method */ - public static final Issue THREAD = Issue.create( - "WrongThread", //$NON-NLS-1$ - "Wrong Thread", - - "Ensures that a method which expects to be called on a specific thread, is actually " + - "called from that thread. For example, calls on methods in widgets should always " + - "be made on the UI thread.", - - Category.CORRECTNESS, - 6, - Severity.ERROR, - IMPLEMENTATION) - .addMoreInfo( - "http://developer.android.com/guide/components/processes-and-threads.html#Threads"); - - public static final String CHECK_RESULT_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "CheckResult"; //$NON-NLS-1$ - public static final String INT_RANGE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntRange"; //$NON-NLS-1$ - public static final String FLOAT_RANGE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "FloatRange"; //$NON-NLS-1$ - public static final String SIZE_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "Size"; //$NON-NLS-1$ - public static final String PERMISSION_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "RequiresPermission"; //$NON-NLS-1$ - public static final String UI_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "UiThread"; //$NON-NLS-1$ - public static final String MAIN_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "MainThread"; //$NON-NLS-1$ - public static final String WORKER_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "WorkerThread"; //$NON-NLS-1$ - public static final String BINDER_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "BinderThread"; //$NON-NLS-1$ - public static final String ANY_THREAD_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "AnyThread"; //$NON-NLS-1$ - public static final String PERMISSION_ANNOTATION_READ = PERMISSION_ANNOTATION + ".Read"; //$NON-NLS-1$ - public static final String PERMISSION_ANNOTATION_WRITE = PERMISSION_ANNOTATION + ".Write"; //$NON-NLS-1$ - - public static final String THREAD_SUFFIX = "Thread"; - public static final String ATTR_SUGGEST = "suggest"; - public static final String ATTR_TO = "to"; - public static final String ATTR_FROM = "from"; - public static final String ATTR_FROM_INCLUSIVE = "fromInclusive"; - public static final String ATTR_TO_INCLUSIVE = "toInclusive"; - public static final String ATTR_MULTIPLE = "multiple"; - public static final String ATTR_MIN = "min"; - public static final String ATTR_MAX = "max"; - public static final String ATTR_ALL_OF = "allOf"; - public static final String ATTR_ANY_OF = "anyOf"; - public static final String ATTR_CONDITIONAL = "conditional"; - - public static final String SECURITY_EXCEPTION = "java.lang.SecurityException"; - - /** - * Constructs a new {@link SupportAnnotationDetector} check - */ - public SupportAnnotationDetector() { - } - - private void checkMethodAnnotation( - @NonNull JavaContext context, - @NonNull PsiMethod method, - @NonNull UCallExpression call, - @NonNull UAnnotation annotation, - @NonNull List<UAnnotation> allMethodAnnotations, - @NonNull List<UAnnotation> allClassAnnotations) { - String signature = annotation.getQualifiedName(); - if (signature == null) { - return; - } - if (CHECK_RESULT_ANNOTATION.equals(signature) - // support findbugs annotation too - || signature.endsWith(".CheckReturnValue")) { - checkResult(context, call, method, annotation); - } else if (signature.equals(PERMISSION_ANNOTATION)) { - PermissionRequirement requirement = PermissionRequirement.create(annotation); - checkPermission(context, call, method, null, requirement); - } else if (signature.endsWith(THREAD_SUFFIX) - && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) { - checkThreading(context, call, method, signature, annotation, allMethodAnnotations, - allClassAnnotations); - } - } - - private void checkParameterAnnotations( - @NonNull JavaContext context, - @NonNull UExpression argument, - @NonNull UCallExpression call, - @NonNull PsiMethod method, - @NonNull List<UAnnotation> annotations) { - boolean handledResourceTypes = false; - for (UAnnotation annotation : annotations) { - String signature = annotation.getQualifiedName(); - if (signature == null) { - continue; - } - - if (COLOR_INT_ANNOTATION.equals(signature)) { - checkColor(context, argument); - } else if (signature.equals(PX_ANNOTATION)) { - checkPx(context, argument); - } else if (signature.equals(INT_RANGE_ANNOTATION)) { - checkIntRange(context, annotation, argument, annotations); - } else if (signature.equals(FLOAT_RANGE_ANNOTATION)) { - checkFloatRange(context, annotation, argument); - } else if (signature.equals(SIZE_ANNOTATION)) { - checkSize(context, annotation, argument); - } else if (signature.startsWith(PERMISSION_ANNOTATION)) { - // PERMISSION_ANNOTATION, PERMISSION_ANNOTATION_READ, PERMISSION_ANNOTATION_WRITE - // When specified on a parameter, that indicates that we're dealing with - // a permission requirement on this *method* which depends on the value - // supplied by this parameter - checkParameterPermission(context, signature, call, method, argument); - } else { - // We only run @IntDef, @StringDef and @<Type>Res checks if we're not - // running inside Android Studio / IntelliJ where there are already inspections - // covering the same warnings (using IntelliJ's own data flow analysis); we - // don't want to (a) create redundant warnings or (b) work harder than we - // have to - if (signature.equals(INT_DEF_ANNOTATION)) { - boolean flag = getAnnotationBooleanValue(annotation, TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE; - checkTypeDefConstant(context, annotation, argument, null, flag, - annotations); - } else if (signature.equals(STRING_DEF_ANNOTATION)) { - checkTypeDefConstant(context, annotation, argument, null, false, - annotations); - } else if (signature.endsWith(RES_SUFFIX)) { - if (handledResourceTypes) { - continue; - } - handledResourceTypes = true; - EnumSet<ResourceType> types = null; - // Handle all resource type annotations in one go: there could be multiple - // resource type annotations specified on the same element; we need to - // know about them all up front. - for (UAnnotation a : annotations) { - String s = a.getQualifiedName(); - if (s != null && s.endsWith(RES_SUFFIX)) { - String typeString = s.substring(SUPPORT_ANNOTATIONS_PREFIX.length(), - s.length() - RES_SUFFIX.length()).toLowerCase(Locale.US); - ResourceType type = ResourceType.getEnum(typeString); - if (type != null) { - if (types == null) { - types = EnumSet.of(type); - } else { - types.add(type); - } - } else if (typeString.equals("any")) { // @AnyRes - types = getAnyRes(); - break; - } - } - } - - if (types != null) { - checkResourceType(context, argument, types, call, method); - } - } - } - } - } - - private static EnumSet<ResourceType> getAnyRes() { - EnumSet<ResourceType> types = EnumSet.allOf(ResourceType.class); - types.remove(ResourceEvaluator.COLOR_INT_MARKER_TYPE); - types.remove(ResourceEvaluator.PX_MARKER_TYPE); - return types; - } - - private void checkParameterPermission( - @NonNull JavaContext context, - @NonNull String signature, - @NonNull UElement call, - @NonNull PsiMethod method, - @NonNull UExpression argument) { - Operation operation = null; - if (signature.equals(PERMISSION_ANNOTATION_READ)) { - operation = READ; - } else if (signature.equals(PERMISSION_ANNOTATION_WRITE)) { - operation = WRITE; - } else { - PsiType type = argument.getExpressionType(); - if (type != null && CLASS_INTENT.equals(type.getCanonicalText())) { - operation = ACTION; - } - } - if (operation == null) { - return; - } - Result result = PermissionFinder.findRequiredPermissions(operation, context, argument); - if (result != null) { - checkPermission(context, call, method, result, result.requirement); - } - } - - private static void checkColor(@NonNull JavaContext context, @NonNull UElement argument) { - if (argument instanceof UIfExpression) { - UIfExpression expression = (UIfExpression) argument; - if (expression.getThenExpression() != null) { - checkColor(context, expression.getThenExpression()); - } - if (expression.getElseExpression() != null) { - checkColor(context, expression.getElseExpression()); - } - return; - } - - EnumSet<ResourceType> types = ResourceEvaluator.getResourceTypes(context, argument); - - if (types != null && types.contains(COLOR) - && !isIgnoredInIde(COLOR_USAGE, context, argument)) { - String message = String.format( - "Should pass resolved color instead of resource id here: " + - "`getResources().getColor(%1$s)`", argument.asSourceString()); - context.report(COLOR_USAGE, argument, context.getUastLocation(argument), message); - } - } - - private static void checkPx(@NonNull JavaContext context, @NonNull UElement argument) { - if (argument instanceof UIfExpression) { - UIfExpression expression = (UIfExpression) argument; - if (expression.getThenExpression() != null) { - checkPx(context, expression.getThenExpression()); - } - if (expression.getElseExpression() != null) { - checkPx(context, expression.getElseExpression()); - } - return; - } - - EnumSet<ResourceType> types = ResourceEvaluator.getResourceTypes(context, argument); - - if (types != null && types.contains(DIMEN)) { - String message = String.format( - "Should pass resolved pixel dimension instead of resource id here: " + - "`getResources().getDimension*(%1$s)`", argument.asSourceString()); - context.report(COLOR_USAGE, argument, context.getUastLocation(argument), message); - } - } - - private static boolean isIgnoredInIde(@NonNull Issue issue, @NonNull JavaContext context, - @NonNull UElement node) { - // Historically, the IDE would treat *all* support annotation warnings as - // handled by the id "ResourceType", so look for that id too for issues - // deliberately suppressed prior to Android Studio 2.0. - Issue synonym = Issue.create("ResourceType", issue.getBriefDescription(TextFormat.RAW), - issue.getExplanation(TextFormat.RAW), issue.getCategory(), issue.getPriority(), - issue.getDefaultSeverity(), issue.getImplementation()); - return context.getDriver().isSuppressed(context, synonym, node); - } - - private void checkPermission( - @NonNull JavaContext context, - @NonNull UElement node, - @Nullable PsiMethod method, - @Nullable Result result, - @NonNull PermissionRequirement requirement) { - if (requirement.isConditional()) { - return; - } - PermissionHolder permissions = getPermissions(context); - if (!requirement.isSatisfied(permissions)) { - // See if it looks like we're holding the permission implicitly by @RequirePermission - // annotations in the surrounding context - permissions = addLocalPermissions(permissions, node); - if (!requirement.isSatisfied(permissions)) { - if (isIgnoredInIde(MISSING_PERMISSION, context, node)) { - return; - } - Operation operation; - String name; - if (result != null) { - name = result.name; - operation = result.operation; - } else { - assert method != null; - PsiClass containingClass = method.getContainingClass(); - if (containingClass != null) { - name = containingClass.getName() + "." + method.getName(); - } else { - name = method.getName(); - } - operation = Operation.CALL; - } - String message = getMissingPermissionMessage(requirement, name, permissions, - operation); - context.report(MISSING_PERMISSION, node, context.getUastLocation(node), message); - } - } else if (requirement.isRevocable(permissions) && - context.getMainProject().getTargetSdkVersion().getFeatureLevel() >= 23) { - - boolean handlesMissingPermission = handlesSecurityException(node); - - // If not, check to see if the code is deliberately checking to see if the - // given permission is available. - if (!handlesMissingPermission) { - UMethod methodNode = UastUtils.getParentOfType(node, UMethod.class, true); - if (methodNode != null) { - CheckPermissionVisitor visitor = new CheckPermissionVisitor(node); - methodNode.accept(visitor); - handlesMissingPermission = visitor.checksPermission(); - } - } - - if (!handlesMissingPermission && !isIgnoredInIde(MISSING_PERMISSION, context, node)) { - String message = getUnhandledPermissionMessage(); - context.report(MISSING_PERMISSION, node, context.getUastLocation(node), message); - } - } - } - - private static boolean handlesSecurityException(@NonNull UElement node) { - // Ensure that the caller is handling a security exception - // First check to see if we're inside a try/catch which catches a SecurityException - // (or some wider exception than that). Check for nested try/catches too. - UElement parent = node; - while (true) { - UTryExpression tryCatch = UastUtils.getParentOfType(parent, UTryExpression.class, true); - if (tryCatch == null) { - break; - } else { - for (UCatchClause catchClause : tryCatch.getCatchClauses()) { - if (containsSecurityException(catchClause.getTypes())) { - return true; - } - } - - parent = tryCatch; - } - } - - // If not, check to see if the method itself declares that it throws a - // SecurityException or something wider. - UMethod declaration = UastUtils.getParentOfType(parent, UMethod.class, false); - if (declaration != null) { - PsiClassType[] thrownTypes = declaration.getThrowsList().getReferencedTypes(); - if (containsSecurityException(Arrays.asList(thrownTypes))) { - return true; - } - } - - return false; - } - - @NonNull - private static PermissionHolder addLocalPermissions( - @NonNull PermissionHolder permissions, - @NonNull UElement node - ) { - // Accumulate @RequirePermissions available in the local context - UMethod method = UastUtils.getParentOfType(node, UMethod.class, true); - if (method == null) { - return permissions; - } - UAnnotation annotation = method.findAnnotation(PERMISSION_ANNOTATION); - permissions = mergeAnnotationPermissions(permissions, annotation); - - UClass containingClass = UastUtils.getContainingUClass(method); - if (containingClass != null) { - annotation = containingClass.findAnnotation(PERMISSION_ANNOTATION); - permissions = mergeAnnotationPermissions(permissions, annotation); - } - return permissions; - } - - @NonNull - private static PermissionHolder mergeAnnotationPermissions( - @NonNull PermissionHolder permissions, - @Nullable UAnnotation annotation - ) { - if (annotation != null) { - PermissionRequirement requirement = PermissionRequirement.create(annotation); - permissions = SetPermissionLookup.join(permissions, requirement); - } - - return permissions; - } - - /** Returns the error message shown when a given call is missing one or more permissions */ - public static String getMissingPermissionMessage(@NonNull PermissionRequirement requirement, - @NonNull String callName, @NonNull PermissionHolder permissions, - @NonNull Operation operation) { - return String.format("Missing permissions required %1$s %2$s: %3$s", operation.prefix(), - callName, requirement.describeMissingPermissions(permissions)); - } - - /** Returns the error message shown when a revocable permission call is not properly handled */ - public static String getUnhandledPermissionMessage() { - return "Call requires permission which may be rejected by user: code should explicitly " - + "check to see if permission is available (with `checkPermission`) or explicitly " - + "handle a potential `SecurityException`"; - } - - /** - * Visitor which looks through a method, up to a given call (the one requiring a - * permission) and checks whether it's preceeded by a call to checkPermission or - * checkCallingPermission or enforcePermission etc. - * <p> - * Currently it only looks for the presence of this check; it does not perform - * flow analysis to determine whether the check actually affects program flow - * up to the permission call, or whether the check permission is checking for - * permissions sufficient to satisfy the permission requirement of the target call, - * or whether the check return value (== PERMISSION_GRANTED vs != PERMISSION_GRANTED) - * is handled correctly, etc. - */ - private static class CheckPermissionVisitor extends AbstractUastVisitor { - private boolean mChecksPermission; - private boolean mDone; - private final UElement mTarget; - - public CheckPermissionVisitor(@NonNull UElement target) { - mTarget = target; - } - - @Override - public boolean visitElement(UElement node) { - return mDone || super.visitElement(node); - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (UastExpressionUtils.isMethodCall(node)) { - visitMethodCallExpression(node); - } - return super.visitCallExpression(node); - } - - private void visitMethodCallExpression(UCallExpression node) { - if (node == mTarget) { - mDone = true; - } - - String name = node.getMethodName(); - if (name != null - && (name.startsWith("check") || name.startsWith("enforce")) - && name.endsWith("Permission")) { - mChecksPermission = true; - mDone = true; - } - } - - public boolean checksPermission() { - return mChecksPermission; - } - } - - private static boolean containsSecurityException( - @NonNull List<? extends PsiType> types) { - for (PsiType type : types) { - if (type instanceof PsiClassType) { - PsiClass cls = ((PsiClassType) type).resolve(); - // In earlier versions we checked not just for java.lang.SecurityException but - // any super type as well, however that probably hides warnings in cases where - // users don't want that; see http://b.android.com/182165 - //return context.getEvaluator().extendsClass(cls, "java.lang.SecurityException", false); - if (cls != null && SECURITY_EXCEPTION.equals(cls.getQualifiedName())) { - return true; - } - } - } - - return false; - } - - private PermissionHolder mPermissions; - - private PermissionHolder getPermissions( - @NonNull JavaContext context) { - if (mPermissions == null) { - Set<String> permissions = Sets.newHashSetWithExpectedSize(30); - Set<String> revocable = Sets.newHashSetWithExpectedSize(4); - LintClient client = context.getClient(); - // Gather permissions from all projects that contribute to the - // main project. - Project mainProject = context.getMainProject(); - for (File manifest : mainProject.getManifestFiles()) { - addPermissions(client, permissions, revocable, manifest); - } - for (Project library : mainProject.getAllLibraries()) { - for (File manifest : library.getManifestFiles()) { - addPermissions(client, permissions, revocable, manifest); - } - } - - AndroidVersion minSdkVersion = mainProject.getMinSdkVersion(); - AndroidVersion targetSdkVersion = mainProject.getTargetSdkVersion(); - mPermissions = new SetPermissionLookup(permissions, revocable, minSdkVersion, - targetSdkVersion); - } - - return mPermissions; - } - - private static void addPermissions(@NonNull LintClient client, - @NonNull Set<String> permissions, - @NonNull Set<String> revocable, - @NonNull File manifest) { - Document document = XmlUtils.parseDocumentSilently(client.readFile(manifest), true); - if (document == null) { - return; - } - Element root = document.getDocumentElement(); - if (root == null) { - return; - } - NodeList children = root.getChildNodes(); - for (int i = 0, n = children.getLength(); i < n; i++) { - Node item = children.item(i); - if (item.getNodeType() != Node.ELEMENT_NODE) { - continue; - } - String nodeName = item.getNodeName(); - if (nodeName.equals(TAG_USES_PERMISSION) - || nodeName.equals(TAG_USES_PERMISSION_SDK_23) - || nodeName.equals(TAG_USES_PERMISSION_SDK_M)) { - Element element = (Element)item; - String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - if (!name.isEmpty()) { - permissions.add(name); - } - } else if (nodeName.equals(TAG_PERMISSION)) { - Element element = (Element)item; - String protectionLevel = element.getAttributeNS(ANDROID_URI, - ATTR_PROTECTION_LEVEL); - if (VALUE_DANGEROUS.equals(protectionLevel)) { - String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - if (!name.isEmpty()) { - revocable.add(name); - } - } - } - } - } - - private static void checkResult(@NonNull JavaContext context, @NonNull UCallExpression node, - @NonNull PsiMethod method, @NonNull UAnnotation annotation) { - if (isExpressionValueUnused(node)) { - String methodName = JavaContext.getMethodName(node); - String suggested = getAnnotationStringValue(annotation, ATTR_SUGGEST); - - // Failing to check permissions is a potential security issue (and had an existing - // dedicated issue id before which people may already have configured with a - // custom severity in their LintOptions etc) so continue to use that issue - // (which also has category Security rather than Correctness) for these: - Issue issue = CHECK_RESULT; - if (methodName != null && methodName.startsWith("check") - && methodName.contains("Permission")) { - issue = CHECK_PERMISSION; - } - - if (isIgnoredInIde(issue, context, node)) { - return; - } - - String message = String.format("The result of `%1$s` is not used", - methodName); - if (suggested != null) { - // TODO: Resolve suggest attribute (e.g. prefix annotation class if it starts - // with "#" etc? - message = String.format( - "The result of `%1$s` is not used; did you mean to call `%2$s`?", - methodName, suggested); - } else if ("intersect".equals(methodName) - && context.getEvaluator().isMemberInClass(method, "android.graphics.Rect")) { - message += ". If the rectangles do not intersect, no change is made and the " - + "original rectangle is not modified. These methods return false to " - + "indicate that this has happened."; - } - context.report(issue, node, context.getUastLocation(node), message); - } - } - - private static boolean isExpressionValueUnused(UExpression expression) { - return getQualifiedParentOrThis(expression).getUastParent() - instanceof UBlockExpression; - } - - private static void checkThreading( - @NonNull JavaContext context, - @NonNull UElement node, - @NonNull PsiMethod method, - @NonNull String signature, - @NonNull UAnnotation annotation, - @NonNull List<UAnnotation> allMethodAnnotations, - @NonNull List<UAnnotation> allClassAnnotations) { - List<String> threadContext = getThreadContext(context, node); - if (threadContext != null && !isCompatibleThread(threadContext, signature) - && !isIgnoredInIde(THREAD, context, node)) { - // If the annotation is specified on the class, ignore this requirement - // if there is another annotation specified on the method. - if (containsAnnotation(allClassAnnotations, annotation)) { - if (containsThreadingAnnotation(allMethodAnnotations)) { - return; - } - // Make sure ALL the other context annotations are acceptable! - } else { - assert containsAnnotation(allMethodAnnotations, annotation); - // See if any of the *other* annotations are compatible. - Boolean isFirst = null; - for (UAnnotation other : allMethodAnnotations) { - if (other == annotation) { - if (isFirst == null) { - isFirst = true; - } - continue; - } else if (!isThreadingAnnotation(other)) { - continue; - } - if (isFirst == null) { - // We'll be called for each annotation on the method. - // For each one we're checking *all* annotations on the target. - // Therefore, when we're seeing the second, third, etc annotation - // on the method we've already checked them, so return here. - return; - } - String s = other.getQualifiedName(); - if (s != null && isCompatibleThread(threadContext, s)) { - return; - } - } - } - - String name = method.getName(); - if ((name.startsWith("post") ) - && context.getEvaluator().isMemberInClass(method, CLASS_VIEW)) { - // The post()/postDelayed() methods are (currently) missing - // metadata (@AnyThread); they're on a class marked @UiThread - // but these specific methods are not @UiThread. - return; - } - - List<String> targetThreads = getThreads(context, method); - if (targetThreads == null) { - targetThreads = Collections.singletonList(signature); - } - - String message = String.format( - "%1$s %2$s must be called from the `%3$s` thread, currently inferred thread is `%4$s` thread", - method.isConstructor() ? "Constructor" : "Method", - method.getName(), describeThreads(targetThreads, true), - describeThreads(threadContext, false)); - context.report(THREAD, node, context.getUastLocation(node), message); - } - } - - public static boolean containsAnnotation( - @NonNull List<UAnnotation> array, - @NonNull UAnnotation annotation) { - for (UAnnotation a : array) { - if (a == annotation) { - return true; - } - } - - return false; - } - - public static boolean containsThreadingAnnotation(@NonNull List<UAnnotation> array) { - for (UAnnotation annotation : array) { - if (isThreadingAnnotation(annotation)) { - return true; - } - } - - return false; - } - - public static boolean isThreadingAnnotation(@NonNull UAnnotation annotation) { - String signature = annotation.getQualifiedName(); - return signature != null - && signature.endsWith(THREAD_SUFFIX) - && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX); - } - - @NonNull - public static String describeThreads(@NonNull List<String> annotations, boolean any) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < annotations.size(); i++) { - if (i > 0) { - if (i == annotations.size() - 1) { - if (any) { - sb.append(" or "); - } else { - sb.append(" and "); - } - } else { - sb.append(", "); - } - } - sb.append(describeThread(annotations.get(i))); - } - return sb.toString(); - } - - @NonNull - public static String describeThread(@NonNull String annotation) { - if (annotation.equals(UI_THREAD_ANNOTATION)) { - return "UI"; - } - else if (annotation.equals(MAIN_THREAD_ANNOTATION)) { - return "main"; - } - else if (annotation.equals(BINDER_THREAD_ANNOTATION)) { - return "binder"; - } - else if (annotation.equals(WORKER_THREAD_ANNOTATION)) { - return "worker"; - } - else if (annotation.equals(ANY_THREAD_ANNOTATION)) { - return "any"; - } - else { - return "other"; - } - } - - /** returns true if the two threads are compatible */ - public static boolean isCompatibleThread(@NonNull List<String> callers, - @NonNull String callee) { - // ALL calling contexts must be valid - assert !callers.isEmpty(); - for (String caller : callers) { - if (!isCompatibleThread(caller, callee)) { - return false; - } - } - - return true; - } - - /** returns true if the two threads are compatible */ - public static boolean isCompatibleThread(@NonNull String caller, @NonNull String callee) { - if (callee.equals(caller)) { - return true; - } - - if (callee.equals(ANY_THREAD_ANNOTATION)) { - return true; - } - - // Allow @UiThread and @MainThread to be combined - if (callee.equals(UI_THREAD_ANNOTATION)) { - if (caller.equals(MAIN_THREAD_ANNOTATION)) { - return true; - } - } else if (callee.equals(MAIN_THREAD_ANNOTATION)) { - if (caller.equals(UI_THREAD_ANNOTATION)) { - return true; - } - } - - return false; - } - - /** Attempts to infer the current thread context at the site of the given method call */ - @Nullable - private static List<String> getThreadContext(@NonNull JavaContext context, - @NonNull UElement methodCall) { - //noinspection unchecked - PsiMethod method = UastUtils.getParentOfType(methodCall, UMethod.class, true, - UAnonymousClass.class); - return getThreads(context, method); - } - - /** Attempts to infer the current thread context at the site of the given method call */ - @Nullable - private static List<String> getThreads(@NonNull JavaContext context, - @Nullable PsiMethod method) { - if (method != null) { - List<String> result = null; - PsiClass cls = method.getContainingClass(); - - while (method != null) { - for (PsiAnnotation annotation : method.getModifierList().getAnnotations()) { - String name = annotation.getQualifiedName(); - if (name != null && name.startsWith(SUPPORT_ANNOTATIONS_PREFIX) - && name.endsWith(THREAD_SUFFIX)) { - if (result == null) { - result = new ArrayList<String>(4); - } - result.add(name); - } - } - if (result != null) { - // We don't accumulate up the chain: one method replaces the requirements - // of its super methods. - return result; - } - method = context.getEvaluator().getSuperMethod(method); - } - - // See if we're extending a class with a known threading context - while (cls != null) { - PsiModifierList modifierList = cls.getModifierList(); - if (modifierList != null) { - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - String name = annotation.getQualifiedName(); - if (name != null && name.startsWith(SUPPORT_ANNOTATIONS_PREFIX) - && name.endsWith(THREAD_SUFFIX)) { - if (result == null) { - result = new ArrayList<String>(4); - } - result.add(name); - } - } - if (result != null) { - // We don't accumulate up the chain: one class replaces the requirements - // of its super classes. - return result; - } - } - cls = cls.getSuperClass(); - } - } - - // In the future, we could also try to infer the threading context using - // other heuristics. For example, if we're in a method with unknown threading - // context, but we see that the method is called by another method with a known - // threading context, we can infer that that threading context is the context for - // this thread too (assuming the call is direct). - - return null; - } - - private static boolean isNumber(@NonNull UElement argument) { - if (argument instanceof ULiteralExpression) { - Object value = ((ULiteralExpression) argument).getValue(); - return value instanceof Number; - } else if (argument instanceof UPrefixExpression) { - UPrefixExpression expression = (UPrefixExpression) argument; - UExpression operand = expression.getOperand(); - return isNumber(operand); - } else { - return false; - } - } - - private static boolean isZero(@NonNull UElement argument) { - if (argument instanceof ULiteralExpression) { - Object value = ((ULiteralExpression) argument).getValue(); - return value instanceof Number && ((Number)value).intValue() == 0; - } - return false; - } - - private static boolean isMinusOne(@NonNull UElement argument) { - if (argument instanceof UPrefixExpression) { - UPrefixExpression expression = (UPrefixExpression) argument; - UExpression operand = expression.getOperand(); - if (operand instanceof ULiteralExpression && - expression.getOperator() == UastPrefixOperator.UNARY_MINUS) { - Object value = ((ULiteralExpression) operand).getValue(); - return value instanceof Number && ((Number) value).intValue() == 1; - } else { - return false; - } - } else { - return false; - } - } - - private static void checkResourceType( - @NonNull JavaContext context, - @NonNull UElement argument, - @NonNull EnumSet<ResourceType> expectedType, - @NonNull UCallExpression call, - @NonNull PsiMethod calledMethod) { - EnumSet<ResourceType> actual = ResourceEvaluator.getResourceTypes(context, argument); - - if (actual == null && (!isNumber(argument) || isZero(argument) || isMinusOne(argument)) ) { - return; - } else if (actual != null && (!Sets.intersection(actual, expectedType).isEmpty() - || expectedType.contains(DRAWABLE) - && (actual.contains(COLOR) || actual.contains(MIPMAP)))) { - return; - } - - if (isIgnoredInIde(RESOURCE_TYPE, context, argument)) { - return; - } - - if (expectedType.contains(ResourceType.STYLEABLE) && (expectedType.size() == 1) - && JavaEvaluator.isMemberInClass(calledMethod, - "android.content.res.TypedArray") - && typeArrayFromArrayLiteral(call.getReceiver(), context)) { - // You're generally supposed to provide a styleable to the TypedArray methods, - // but you're also allowed to supply an integer array - return; - } - - String message; - if (actual != null && actual.size() == 1 && actual.contains( - ResourceEvaluator.COLOR_INT_MARKER_TYPE)) { - message = "Expected a color resource id (`R.color.`) but received an RGB integer"; - } else if (expectedType.contains(ResourceEvaluator.COLOR_INT_MARKER_TYPE)) { - message = String.format("Should pass resolved color instead of resource id here: " + - "`getResources().getColor(%1$s)`", argument.asSourceString()); - } else if (actual != null && actual.size() == 1 && actual.contains( - ResourceEvaluator.PX_MARKER_TYPE)) { - message = "Expected a dimension resource id (`R.color.`) but received a pixel integer"; - } else if (expectedType.contains(ResourceEvaluator.PX_MARKER_TYPE)) { - message = String.format("Should pass resolved pixel size instead of resource id here: " + - "`getResources().getDimension*(%1$s)`", argument.asSourceString()); - } else if (expectedType.size() < ResourceType.getNames().length - 2) { // -2: marker types - message = String.format("Expected resource of type %1$s", - Joiner.on(" or ").join(expectedType)); - } else { - message = "Expected resource identifier (`R`.type.`name`)"; - } - context.report(RESOURCE_TYPE, argument, context.getUastLocation(argument), message); - } - - /** - * Returns true if the node is pointing to a TypedArray whose value was obtained - * from an array literal - */ - public static boolean typeArrayFromArrayLiteral( - @Nullable UElement node, @NonNull JavaContext context) { - if (isMethodCall(node)) { - UCallExpression expression = (UCallExpression) node; - assert expression != null; - String name = expression.getMethodName(); - if (name != null && "obtainStyledAttributes".equals(name)) { - List<UExpression> expressions = expression.getValueArguments(); - if (!expressions.isEmpty()) { - int arg; - if (expressions.size() == 1) { - // obtainStyledAttributes(int[] attrs) - arg = 0; - } else if (expressions.size() == 2) { - // obtainStyledAttributes(AttributeSet set, int[] attrs) - // obtainStyledAttributes(int resid, int[] attrs) - for (arg = 0; arg < expressions.size(); arg++) { - PsiType type = expressions.get(arg).getExpressionType(); - if (type instanceof PsiArrayType) { - break; - } - } - if (arg == expressions.size()) { - return false; - } - } else if (expressions.size() == 4) { - // obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) - arg = 1; - } else { - return false; - } - - return ConstantEvaluator.isArrayLiteral(expressions.get(arg), context); - } - } - return false; - } else if (node instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) node).resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - UExpression lastAssignment = - UastLintUtils.findLastAssignment(variable, node, context); - - if (lastAssignment != null) { - return typeArrayFromArrayLiteral(lastAssignment, context); - } - } - } else if (UastExpressionUtils.isNewArrayWithInitializer(node)) { - return true; - } else if (UastExpressionUtils.isNewArrayWithDimensions(node)) { - return true; - } else if (node instanceof UParenthesizedExpression) { - UParenthesizedExpression parenthesizedExpression = (UParenthesizedExpression) node; - UExpression expression = parenthesizedExpression.getExpression(); - return typeArrayFromArrayLiteral(expression, context); - } else if (UastExpressionUtils.isTypeCast(node)) { - UBinaryExpressionWithType castExpression = (UBinaryExpressionWithType) node; - assert castExpression != null; - UExpression operand = castExpression.getOperand(); - return typeArrayFromArrayLiteral(operand, context); - } - - return false; - } - - private static boolean isMethodCall(UElement node) { - if (node instanceof UQualifiedReferenceExpression) { - UExpression last = getLastInQualifiedChain((UQualifiedReferenceExpression) node); - return UastExpressionUtils.isMethodCall(last); - } - - return UastExpressionUtils.isMethodCall(node); - } - - @NonNull - private static UExpression getLastInQualifiedChain(@NonNull UQualifiedReferenceExpression node) { - UExpression last = node.getSelector(); - while (last instanceof UQualifiedReferenceExpression) { - last = ((UQualifiedReferenceExpression) last).getSelector(); - } - return last; - } - - private static void checkIntRange( - @NonNull JavaContext context, - @NonNull UAnnotation annotation, - @NonNull UElement argument, - @NonNull List<UAnnotation> allAnnotations) { - String message = getIntRangeError(context, annotation, argument); - if (message != null) { - if (findIntDef(allAnnotations) != null) { - // Don't flag int range errors if there is an int def annotation there too; - // there could be a valid @IntDef constant. (The @IntDef check will - // perform range validation by calling getIntRange.) - return; - } - - if (isIgnoredInIde(RANGE, context, argument)) { - return; - } - - context.report(RANGE, argument, context.getUastLocation(argument), message); - } - } - - @Nullable - private static String getIntRangeError( - @NonNull JavaContext context, - @NonNull UAnnotation annotation, - @NonNull UElement argument) { - if (UastExpressionUtils.isNewArrayWithInitializer(argument)) { - UCallExpression newExpression = (UCallExpression) argument; - for (UExpression expression : newExpression.getValueArguments()) { - String error = getIntRangeError(context, annotation, expression); - if (error != null) { - return error; - } - } - } - - Object object = ConstantEvaluator.evaluate(context, argument); - if (!(object instanceof Number)) { - return null; - } - long value = ((Number)object).longValue(); - long from = getLongAttribute(annotation, ATTR_FROM, Long.MIN_VALUE); - long to = getLongAttribute(annotation, ATTR_TO, Long.MAX_VALUE); - - return getIntRangeError(value, from, to); - } - - /** - * Checks whether a given integer value is in the allowed range, and if so returns - * null; otherwise returns a suitable error message. - */ - private static String getIntRangeError(long value, long from, long to) { - String message = null; - if (value < from || value > to) { - StringBuilder sb = new StringBuilder(20); - if (value < from) { - sb.append("Value must be \u2265 "); - sb.append(Long.toString(from)); - } else { - assert value > to; - sb.append("Value must be \u2264 "); - sb.append(Long.toString(to)); - } - sb.append(" (was ").append(value).append(')'); - message = sb.toString(); - } - return message; - } - - private static void checkFloatRange( - @NonNull JavaContext context, - @NonNull UAnnotation annotation, - @NonNull UElement argument) { - Object object = ConstantEvaluator.evaluate(context, argument); - if (!(object instanceof Number)) { - return; - } - double value = ((Number)object).doubleValue(); - double from = getDoubleAttribute(annotation, ATTR_FROM, Double.NEGATIVE_INFINITY); - double to = getDoubleAttribute(annotation, ATTR_TO, Double.POSITIVE_INFINITY); - boolean fromInclusive = getBoolean(annotation, ATTR_FROM_INCLUSIVE, true); - boolean toInclusive = getBoolean(annotation, ATTR_TO_INCLUSIVE, true); - - String message = getFloatRangeError(value, from, to, fromInclusive, toInclusive, argument); - if (message != null && !isIgnoredInIde(RANGE, context, argument)) { - context.report(RANGE, argument, context.getUastLocation(argument), message); - } - } - - /** - * Checks whether a given floating point value is in the allowed range, and if so returns - * null; otherwise returns a suitable error message. - */ - @Nullable - private static String getFloatRangeError(double value, double from, double to, - boolean fromInclusive, boolean toInclusive, @NonNull UElement node) { - if (!((fromInclusive && value >= from || !fromInclusive && value > from) && - (toInclusive && value <= to || !toInclusive && value < to))) { - StringBuilder sb = new StringBuilder(20); - if (from != Double.NEGATIVE_INFINITY) { - if (to != Double.POSITIVE_INFINITY) { - if (fromInclusive && value < from || !fromInclusive && value <= from) { - sb.append("Value must be "); - if (fromInclusive) { - sb.append('\u2265'); // >= sign - } else { - sb.append('>'); - } - sb.append(' '); - sb.append(Double.toString(from)); - } else { - assert toInclusive && value > to || !toInclusive && value >= to; - sb.append("Value must be "); - if (toInclusive) { - sb.append('\u2264'); // <= sign - } else { - sb.append('<'); - } - sb.append(' '); - sb.append(Double.toString(to)); - } - } else { - sb.append("Value must be "); - if (fromInclusive) { - sb.append('\u2265'); // >= sign - } else { - sb.append('>'); - } - sb.append(' '); - sb.append(Double.toString(from)); - } - } else if (to != Double.POSITIVE_INFINITY) { - sb.append("Value must be "); - if (toInclusive) { - sb.append('\u2264'); // <= sign - } else { - sb.append('<'); - } - sb.append(' '); - sb.append(Double.toString(to)); - } - sb.append(" (was "); - if (node instanceof ULiteralExpression) { - // Use source text instead to avoid rounding errors involved in conversion, e.g - // Error: Value must be > 2.5 (was 2.490000009536743) [Range] - // printAtLeastExclusive(2.49f); // ERROR - // ~~~~~ - String str = node.asSourceString(); - if (str.endsWith("f") || str.endsWith("F")) { - str = str.substring(0, str.length() - 1); - } - sb.append(str); - } else { - sb.append(value); - } - sb.append(')'); - return sb.toString(); - } - return null; - } - - private static void checkSize( - @NonNull JavaContext context, - @NonNull UAnnotation annotation, - @NonNull UElement argument) { - int actual; - boolean isString = false; - - // TODO: Collections syntax, e.g. Arrays.asList ⇒ param count, emptyList=0, singleton=1, etc - // TODO: Flow analysis - // No flow analysis for this check yet, only checking literals passed in as parameters - - if (UastExpressionUtils.isNewArrayWithInitializer(argument)) { - actual = ((UCallExpression) argument).getValueArgumentCount(); - } else { - Object object = ConstantEvaluator.evaluate(context, argument); - // Check string length - if (object instanceof String) { - actual = ((String)object).length(); - isString = true; - } else { - return; - } - } - long exact = getLongAttribute(annotation, ATTR_VALUE, -1); - long min = getLongAttribute(annotation, ATTR_MIN, Long.MIN_VALUE); - long max = getLongAttribute(annotation, ATTR_MAX, Long.MAX_VALUE); - long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1); - - String unit; - if (isString) { - unit = "length"; - } else { - unit = "size"; - } - String message = getSizeError(actual, exact, min, max, multiple, unit); - if (message != null && !isIgnoredInIde(RANGE, context, argument)) { - context.report(RANGE, argument, context.getUastLocation(argument), message); - } - } - - /** - * Checks whether a given size follows the given constraints, and if so returns - * null; otherwise returns a suitable error message. - */ - private static String getSizeError(long actual, long exact, long min, long max, long multiple, - @NonNull String unit) { - String message = null; - if (exact != -1) { - if (exact != actual) { - message = String.format("Expected %1$s %2$d (was %3$d)", - unit, exact, actual); - } - } else if (actual < min || actual > max) { - StringBuilder sb = new StringBuilder(20); - if (actual < min) { - sb.append("Expected ").append(unit).append(" \u2265 "); - sb.append(Long.toString(min)); - } else { - assert actual > max; - sb.append("Expected ").append(unit).append(" \u2264 "); - sb.append(Long.toString(max)); - } - sb.append(" (was ").append(actual).append(')'); - message = sb.toString(); - } else if (actual % multiple != 0) { - message = String.format("Expected %1$s to be a multiple of %2$d (was %3$d " - + "and should be either %4$d or %5$d)", - unit, multiple, actual, (actual / multiple) * multiple, - (actual / multiple + 1) * multiple); - } - return message; - } - - @Nullable - private static UAnnotation findIntRange( - @NonNull List<UAnnotation> annotations) { - for (UAnnotation annotation : annotations) { - if (INT_RANGE_ANNOTATION.equals(annotation.getQualifiedName())) { - return annotation; - } - } - - return null; - } - - @Nullable - static UAnnotation findIntDef(@NonNull List<UAnnotation> annotations) { - for (UAnnotation annotation : annotations) { - if (INT_DEF_ANNOTATION.equals(annotation.getQualifiedName())) { - return annotation; - } - } - - return null; - } - - private static void checkTypeDefConstant( - @NonNull JavaContext context, - @NonNull UAnnotation annotation, - @Nullable UElement argument, - @Nullable UElement errorNode, - boolean flag, - @NonNull List<UAnnotation> allAnnotations) { - if (argument == null) { - return; - } - if (argument instanceof ULiteralExpression) { - Object value = ((ULiteralExpression) argument).getValue(); - if (value == null) { - // Accepted for @StringDef - //noinspection UnnecessaryReturnStatement - return; - } else if (value instanceof String) { - String string = (String) value; - checkTypeDefConstant(context, annotation, argument, errorNode, false, string, - allAnnotations); - } else if (value instanceof Integer || value instanceof Long) { - long v = value instanceof Long ? ((Long) value) : ((Integer) value).longValue(); - if (flag && v == 0) { - // Accepted for a flag @IntDef - return; - } - - checkTypeDefConstant(context, annotation, argument, errorNode, flag, value, - allAnnotations); - } - } else if (isMinusOne(argument)) { - // -1 is accepted unconditionally for flags - if (!flag) { - reportTypeDef(context, annotation, argument, errorNode, allAnnotations); - } - } else if (argument instanceof UPrefixExpression) { - UPrefixExpression expression = (UPrefixExpression) argument; - if (flag) { - checkTypeDefConstant(context, annotation, expression.getOperand(), - errorNode, true, allAnnotations); - } else { - UastOperator operator = expression.getOperator(); - if (operator == UastPrefixOperator.BITWISE_NOT) { - if (isIgnoredInIde(TYPE_DEF, context, expression)) { - return; - } - context.report(TYPE_DEF, expression, context.getUastLocation(expression), - "Flag not allowed here"); - } else if (operator == UastPrefixOperator.UNARY_MINUS) { - reportTypeDef(context, annotation, argument, errorNode, allAnnotations); - } - } - } else if (argument instanceof UParenthesizedExpression) { - UExpression expression = ((UParenthesizedExpression) argument).getExpression(); - if (expression != null) { - checkTypeDefConstant(context, annotation, expression, errorNode, flag, allAnnotations); - } - } else if (argument instanceof UIfExpression) { - UIfExpression expression = (UIfExpression) argument; - if (expression.getThenExpression() != null) { - checkTypeDefConstant(context, annotation, expression.getThenExpression(), errorNode, flag, - allAnnotations); - } - if (expression.getElseExpression() != null) { - checkTypeDefConstant(context, annotation, expression.getElseExpression(), errorNode, flag, - allAnnotations); - } - } else if (argument instanceof UBinaryExpression) { - // If it's ?: then check both the if and else clauses - UBinaryExpression expression = (UBinaryExpression) argument; - if (flag) { - checkTypeDefConstant(context, annotation, expression.getLeftOperand(), errorNode, true, - allAnnotations); - checkTypeDefConstant(context, annotation, expression.getRightOperand(), errorNode, true, - allAnnotations); - } else { - UastBinaryOperator operator = expression.getOperator(); - if (operator == UastBinaryOperator.BITWISE_AND - || operator == UastBinaryOperator.BITWISE_OR - || operator == UastBinaryOperator.BITWISE_XOR) { - if (isIgnoredInIde(TYPE_DEF, context, expression)) { - return; - } - context.report(TYPE_DEF, expression, context.getUastLocation(expression), - "Flag not allowed here"); - } - } - } if (argument instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) argument).resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - - if (variable.getType() instanceof PsiArrayType) { - // It's pointing to an array reference; we can't check these individual - // elements (because we can't jump from ResolvedNodes to AST elements; this - // is part of the motivation for the PSI change in lint 2.0), but we also - // don't want to flag it as invalid. - return; - } - - // If it's a constant (static/final) check that it's one of the allowed ones - if (variable.hasModifierProperty(PsiModifier.STATIC) - && variable.hasModifierProperty(PsiModifier.FINAL)) { - checkTypeDefConstant(context, annotation, argument, - errorNode != null ? errorNode : argument, - flag, resolved, allAnnotations); - } else { - UExpression lastAssignment = - UastLintUtils.findLastAssignment(variable, argument, context); - - if (lastAssignment != null) { - checkTypeDefConstant(context, annotation, - lastAssignment, - errorNode != null ? errorNode : argument, flag, - allAnnotations); - } - } - } - } else if (UastExpressionUtils.isNewArrayWithInitializer(argument)) { - UCallExpression arrayInitializer = (UCallExpression) argument; - PsiType type = arrayInitializer.getExpressionType(); - if (type != null) { - type = type.getDeepComponentType(); - } - if (PsiType.INT.equals(type) || PsiType.LONG.equals(type)) { - for (UExpression expression : arrayInitializer.getValueArguments()) { - checkTypeDefConstant(context, annotation, expression, errorNode, flag, - allAnnotations); - } - } - } - } - - private static void checkTypeDefConstant(@NonNull JavaContext context, - @NonNull UAnnotation annotation, @NonNull UElement argument, - @Nullable UElement errorNode, boolean flag, Object value, - @NonNull List<UAnnotation> allAnnotations) { - UAnnotation rangeAnnotation = findIntRange(allAnnotations); - if (rangeAnnotation != null) { - // Allow @IntRange on this number - if (getIntRangeError(context, rangeAnnotation, argument) == null) { - return; - } - } - - UExpression allowed = getAnnotationValue(annotation); - if (allowed == null) { - return; - } - - if (UastExpressionUtils.isArrayInitializer(allowed)) { - UCallExpression initializerExpression = (UCallExpression) allowed; - List<UExpression> initializers = initializerExpression.getValueArguments(); - for (UExpression expression : initializers) { - if (expression instanceof ULiteralExpression) { - if (value.equals(((ULiteralExpression)expression).getValue())) { - return; - } - } else if (expression instanceof ExternalReferenceExpression) { - PsiElement resolved = UastLintUtils.resolve( - (ExternalReferenceExpression) expression, argument); - if (resolved != null && resolved.equals(value)) { - return; - } - } else if (expression instanceof UReferenceExpression) { - PsiElement resolved = ((UReferenceExpression) expression).resolve(); - if (resolved != null && resolved.equals(value)) { - return; - } - } - } - - if (value instanceof PsiField) { - PsiField astNode = (PsiField)value; - UExpression initializer = context.getUastContext().getInitializerBody(astNode); - if (initializer != null) { - checkTypeDefConstant(context, annotation, initializer, errorNode, - flag, allAnnotations); - return; - } - } - - reportTypeDef(context, argument, errorNode, flag, - initializers, allAnnotations); - } - } - - private static void reportTypeDef( - @NonNull JavaContext context, - @NonNull UAnnotation annotation, - @NonNull UElement argument, - @Nullable UElement errorNode, - @NonNull List<UAnnotation> allAnnotations - ) { - // reportTypeDef(context, argument, errorNode, false, allowedValues, allAnnotations); - UExpression allowed = getAnnotationValue(annotation); - if (UastExpressionUtils.isArrayInitializer(allowed)) { - UCallExpression initializerExpression = - (UCallExpression) allowed; - List<UExpression> initializers = initializerExpression.getValueArguments(); - reportTypeDef(context, argument, errorNode, false, initializers, allAnnotations); - } - } - - private static void reportTypeDef(@NonNull JavaContext context, @NonNull UElement node, - @Nullable UElement errorNode, boolean flag, - @NonNull List<UExpression> allowedValues, - @NonNull List<UAnnotation> allAnnotations) { - if (errorNode == null) { - errorNode = node; - } - if (isIgnoredInIde(TYPE_DEF, context, errorNode)) { - return; - } - - String values = listAllowedValues(node, allowedValues); - String message; - if (flag) { - message = "Must be one or more of: " + values; - } else { - message = "Must be one of: " + values; - } - - UAnnotation rangeAnnotation = findIntRange(allAnnotations); - if (rangeAnnotation != null) { - // Allow @IntRange on this number - String rangeError = getIntRangeError(context, rangeAnnotation, node); - if (rangeError != null && !rangeError.isEmpty()) { - message += " or " + Character.toLowerCase(rangeError.charAt(0)) - + rangeError.substring(1); - } - } - - context.report(TYPE_DEF, errorNode, context.getUastLocation(errorNode), message); - } - - @Nullable - private static UExpression getAnnotationValue(@NonNull UAnnotation annotation) { - UExpression value = annotation.findDeclaredAttributeValue(ATTR_VALUE); - if (value == null) { - value = annotation.findDeclaredAttributeValue(null); - } - return value; - } - - private static String listAllowedValues(@NonNull UElement context, - @NonNull List<UExpression> allowedValues) { - StringBuilder sb = new StringBuilder(); - for (UExpression allowedValue : allowedValues) { - String s = null; - PsiElement resolved = null; - if (allowedValue instanceof ExternalReferenceExpression) { - resolved = UastLintUtils.resolve( - (ExternalReferenceExpression) allowedValue, context); - } else if (allowedValue instanceof UReferenceExpression) { - resolved = ((UReferenceExpression) allowedValue).resolve(); - } - - if (resolved instanceof PsiField) { - PsiField field = (PsiField) resolved; - String containingClassName = field.getContainingClass() != null - ? field.getContainingClass().getName() : null; - if (containingClassName == null) { - continue; - } - s = containingClassName + "." + field.getName(); - } - - if (s == null) { - s = allowedValue.asSourceString(); - } - if (sb.length() > 0) { - sb.append(", "); - } - sb.append(s); - } - return sb.toString(); - } - - static double getDoubleAttribute(@NonNull UAnnotation annotation, - @NonNull String name, double defaultValue) { - Double value = getAnnotationDoubleValue(annotation, name); - if (value != null) { - return value; - } - - return defaultValue; - } - - static long getLongAttribute(@NonNull UAnnotation annotation, - @NonNull String name, long defaultValue) { - Long value = getAnnotationLongValue(annotation, name); - if (value != null) { - return value; - } - - return defaultValue; - } - - static boolean getBoolean(@NonNull UAnnotation annotation, - @NonNull String name, boolean defaultValue) { - Boolean value = getAnnotationBooleanValue(annotation, name); - if (value != null) { - return value; - } - - return defaultValue; - } - - @NonNull - static PsiAnnotation[] filterRelevantAnnotations( - @NonNull JavaEvaluator evaluator, @NonNull PsiAnnotation[] annotations) { - List<PsiAnnotation> result = null; - int length = annotations.length; - if (length == 0) { - return annotations; - } - for (PsiAnnotation annotation : annotations) { - String signature = annotation.getQualifiedName(); - if (signature == null || signature.startsWith("java.")) { - // @Override, @SuppressWarnings etc. Ignore - continue; - } - - if (signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) { - // Bail on the nullness annotations early since they're the most commonly - // defined ones. They're not analyzed in lint yet. - if (signature.endsWith(".Nullable") || signature.endsWith(".NonNull")) { - continue; - } - - // Common case: there's just one annotation; no need to create a list copy - if (length == 1) { - return annotations; - } - if (result == null) { - result = new ArrayList<PsiAnnotation>(2); - } - result.add(annotation); - } - - // Special case @IntDef and @StringDef: These are used on annotations - // themselves. For example, you create a new annotation named @foo.bar.Baz, - // annotate it with @IntDef, and then use @foo.bar.Baz in your signatures. - // Here we want to map from @foo.bar.Baz to the corresponding int def. - // Don't need to compute this if performing @IntDef or @StringDef lookup - PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); - if (ref == null) { - continue; - } - PsiElement resolved = ref.resolve(); - if (!(resolved instanceof PsiClass) || !((PsiClass)resolved).isAnnotationType()) { - continue; - } - PsiClass cls = (PsiClass)resolved; - PsiAnnotation[] innerAnnotations = evaluator.getAllAnnotations(cls); - for (int j = 0; j < innerAnnotations.length; j++) { - PsiAnnotation inner = innerAnnotations[j]; - String a = inner.getQualifiedName(); - if (a == null || a.startsWith("java.")) { - // @Override, @SuppressWarnings etc. Ignore - continue; - } - if (a.equals(INT_DEF_ANNOTATION) - || a.equals(PERMISSION_ANNOTATION) - || a.equals(INT_RANGE_ANNOTATION) - || a.equals(STRING_DEF_ANNOTATION)) { - if (length == 1 && j == innerAnnotations.length - 1 && result == null) { - return innerAnnotations; - } - if (result == null) { - result = new ArrayList<PsiAnnotation>(2); - } - result.add(inner); - } - } - } - - return result != null - ? result.toArray(PsiAnnotation.EMPTY_ARRAY) : PsiAnnotation.EMPTY_ARRAY; - } - - // ---- Implements UastScanner ---- - - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(3); - types.add(UCallExpression.class); - types.add(UVariable.class); - return types; - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new CallVisitor(context); - } - - private class CallVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - - public CallVisitor(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitCallExpression(UCallExpression call) { - PsiMethod method = call.resolve(); - if (method != null) { - checkCall(method, call); - } - return super.visitCallExpression(call); - } - - @Override - public boolean visitVariable(UVariable node) { - if (node instanceof UEnumConstant) { - UEnumConstant constant = (UEnumConstant) node; - PsiMethod method = constant.resolveMethod(); - checkCall(method, constant); - } - return super.visitVariable(node); - } - - public void checkCall(PsiMethod method, UCallExpression call) { - JavaEvaluator evaluator = mContext.getEvaluator(); - - List<UAnnotation> methodAnnotations; - { - PsiAnnotation[] annotations = evaluator.getAllAnnotations(method); - methodAnnotations = JavaUAnnotation.wrap(filterRelevantAnnotations(evaluator, annotations)); - } - - // Look for annotations on the class as well: these trickle - // down to all the methods in the class - PsiClass containingClass = method.getContainingClass(); - List<UAnnotation> classAnnotations; - if (containingClass != null) { - PsiAnnotation[] annotations = evaluator.getAllAnnotations(containingClass); - classAnnotations = JavaUAnnotation.wrap(filterRelevantAnnotations(evaluator, annotations)); - } else { - classAnnotations = Collections.emptyList(); - } - - for (UAnnotation annotation : methodAnnotations) { - checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations, - classAnnotations); - } - - if (!classAnnotations.isEmpty()) { - for (UAnnotation annotation : classAnnotations) { - checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations, - classAnnotations); - } - } - - List<UExpression> arguments = call.getValueArguments(); - PsiParameterList parameterList = method.getParameterList(); - PsiParameter[] parameters = parameterList.getParameters(); - List<UAnnotation> annotations = null; - for (int i = 0, n = Math.min(parameters.length, arguments.size()); - i < n; - i++) { - UExpression argument = arguments.get(i); - PsiParameter parameter = parameters[i]; - annotations = JavaUAnnotation.wrap( - filterRelevantAnnotations(evaluator, evaluator.getAllAnnotations(parameter))); - checkParameterAnnotations(mContext, argument, call, method, annotations); - } - if (annotations != null) { - // last parameter is varargs (same parameter annotations) - for (int i = parameters.length; i < arguments.size(); i++) { - UExpression argument = arguments.get(i); - checkParameterAnnotations(mContext, argument, call, method, annotations); - } - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java deleted file mode 100644 index 366e17a..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java +++ /dev/null
@@ -1,148 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.*; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** Detector looking for Toast.makeText() without a corresponding show() call */ -public class ToastDetector extends Detector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "ShowToast", //$NON-NLS-1$ - "Toast created but not shown", - - "`Toast.makeText()` creates a `Toast` but does *not* show it. You must call " + - "`show()` on the resulting object to actually make the `Toast` appear.", - - Category.CORRECTNESS, - 6, - Severity.WARNING, - new Implementation( - ToastDetector.class, - Scope.JAVA_FILE_SCOPE)); - - - /** Constructs a new {@link ToastDetector} check */ - public ToastDetector() { - } - - // ---- Implements UastScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("makeText"); //$NON-NLS-1$ - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod uMethod) { - PsiMethod method = uMethod.getPsi(); - - if (!JavaEvaluator.isMemberInClass(method, "android.widget.Toast")) { - return; - } - - // Make sure you pass the right kind of duration: it's not a delay, it's - // LENGTH_SHORT or LENGTH_LONG - // (see http://code.google.com/p/android/issues/detail?id=3655) - List<UExpression> args = call.getValueArguments(); - if (args.size() == 3) { - UExpression duration = args.get(2); - if (duration instanceof ULiteralExpression) { - context.report(ISSUE, duration, context.getUastLocation(duration), - "Expected duration `Toast.LENGTH_SHORT` or `Toast.LENGTH_LONG`, a custom " + - "duration value is not supported"); - } - } - - UElement surroundingDeclaration = UastUtils.getParentOfType( - call, true, - UMethod.class, UBlockExpression.class, ULambdaExpression.class); - - if (surroundingDeclaration == null) { - return; - } - - ShowFinder finder = new ShowFinder(call); - surroundingDeclaration.accept(finder); - if (!finder.isShowCalled()) { - context.report(ISSUE, call, context.getUastNameLocation(call), - "Toast created but not shown: did you forget to call `show()` ?"); - } - - } - - private static class ShowFinder extends AbstractUastVisitor { - /** The target makeText call */ - private final UCallExpression mTarget; - /** Whether we've found the show method */ - private boolean mFound; - /** Whether we've seen the target makeText node yet */ - private boolean mSeenTarget; - - private ShowFinder(UCallExpression target) { - mTarget = target; - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (node.equals(mTarget)) { - mSeenTarget = true; - } else { - if ((mSeenTarget || mTarget.equals(node.getReceiver())) - && "show".equals(node.getMethodName())) { - // TODO: Do more flow analysis to see whether we're really calling show - // on the right type of object? - mFound = true; - } - } - - return super.visitCallExpression(node); - } - - @Override - public boolean visitReturnExpression(UReturnExpression node) { - if (UastUtils.isChildOf(mTarget, node.getReturnExpression(), true)) { - // If you just do "return Toast.makeText(...) don't warn - mFound = true; - } - - return super.visitReturnExpression(node); - } - - private boolean isShowCalled() { - return mFound; - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TrustAllX509TrustManagerDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TrustAllX509TrustManagerDetector.java deleted file mode 100644 index c070e82..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TrustAllX509TrustManagerDetector.java +++ /dev/null
@@ -1,194 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ClassContext; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.ClassScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.uast.UBlockExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UReturnExpression; -import org.jetbrains.uast.UastEmptyExpression; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.org.objectweb.asm.Opcodes; -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.jetbrains.org.objectweb.asm.tree.InsnList; -import org.jetbrains.org.objectweb.asm.tree.MethodNode; - -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; - -public class TrustAllX509TrustManagerDetector extends Detector implements Detector.UastScanner, - ClassScanner { - - @SuppressWarnings("unchecked") - private static final Implementation IMPLEMENTATION = - new Implementation(TrustAllX509TrustManagerDetector.class, - EnumSet.of(Scope.JAVA_LIBRARIES, Scope.JAVA_FILE), - Scope.JAVA_FILE_SCOPE); - - public static final Issue ISSUE = Issue.create("TrustAllX509TrustManager", - "Insecure TLS/SSL trust manager", - "This check looks for X509TrustManager implementations whose `checkServerTrusted` or " + - "`checkClientTrusted` methods do nothing (thus trusting any certificate chain) " + - "which could result in insecure network traffic caused by trusting arbitrary " + - "TLS/SSL certificates presented by peers.", - Category.SECURITY, - 6, - Severity.WARNING, - IMPLEMENTATION); - - public TrustAllX509TrustManagerDetector() { - } - - // ---- Implements UastScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList("javax.net.ssl.X509TrustManager"); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass cls) { - checkMethod(context, cls, "checkServerTrusted"); - checkMethod(context, cls, "checkClientTrusted"); - } - - private static void checkMethod(@NonNull JavaContext context, - @NonNull UClass cls, - @NonNull String methodName) { - JavaEvaluator evaluator = context.getEvaluator(); - for (PsiMethod method : cls.findMethodsByName(methodName, true)) { - if (evaluator.isAbstract(method)) { - continue; - } - - // For now very simple; only checks if nothing is done. - // Future work: Improve this check to be less sensitive to irrelevant - // instructions/statements/invocations (e.g. System.out.println) by - // looking for calls that could lead to a CertificateException being - // thrown, e.g. throw statement within the method itself or invocation - // of another method that may throw a CertificateException, and only - // reporting an issue if none of these calls are found. ControlFlowGraph - // may be useful here. - - UExpression body = context.getUastContext().getMethodBody(method); - - ComplexBodyVisitor visitor = new ComplexBodyVisitor(); - body.accept(visitor); - - if (!visitor.isComplex()) { - Location location = context.getNameLocation(method); - String message = getErrorMessage(methodName); - context.report(ISSUE, method, location, message); - } - } - } - - @NonNull - private static String getErrorMessage(String methodName) { - return "`" + methodName + "` is empty, which could cause " + - "insecure network traffic due to trusting arbitrary TLS/SSL " + - "certificates presented by peers"; - } - - private static class ComplexBodyVisitor extends AbstractUastVisitor { - private boolean isComplex = false; - - @Override - public boolean visitElement(@NotNull UElement node) { - if (node instanceof UExpression && - !(node instanceof UReturnExpression - || node instanceof UBlockExpression - || node instanceof UastEmptyExpression)) { - isComplex = true; - } - - return isComplex || super.visitElement(node); - } - - boolean isComplex() { - return isComplex; - } - } - - // ---- Implements ClassScanner ---- - // Only used for libraries where we have to analyze bytecode - - @Override - @SuppressWarnings("rawtypes") - public void checkClass(@NonNull final ClassContext context, - @NonNull ClassNode classNode) { - if (!context.isFromClassLibrary()) { - // Non-library code checked at the AST level - return; - } - if (!classNode.interfaces.contains("javax/net/ssl/X509TrustManager")) { - return; - } - List methodList = classNode.methods; - for (Object m : methodList) { - MethodNode method = (MethodNode) m; - if ("checkServerTrusted".equals(method.name) || - "checkClientTrusted".equals(method.name)) { - InsnList nodes = method.instructions; - boolean emptyMethod = true; // Stays true if method doesn't perform any "real" - // operations - for (int i = 0, n = nodes.size(); i < n; i++) { - // Future work: Improve this check to be less sensitive to irrelevant - // instructions/statements/invocations (e.g. System.out.println) by - // looking for calls that could lead to a CertificateException being - // thrown, e.g. throw statement within the method itself or invocation - // of another method that may throw a CertificateException, and only - // reporting an issue if none of these calls are found. ControlFlowGraph - // may be useful here. - AbstractInsnNode instruction = nodes.get(i); - int type = instruction.getType(); - if (type != AbstractInsnNode.LABEL && type != AbstractInsnNode.LINE && - !(type == AbstractInsnNode.INSN && - instruction.getOpcode() == Opcodes.RETURN)) { - emptyMethod = false; - break; - } - } - if (emptyMethod) { - Location location = context.getLocation(method, classNode); - context.report(ISSUE, location, getErrorMessage(method.name)); - } - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TypoLookup.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TypoLookup.java deleted file mode 100644 index 98fa742..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TypoLookup.java +++ /dev/null
@@ -1,776 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.DOT_XML; -import static com.android.tools.klint.detector.api.LintUtils.assertionsEnabled; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.detector.api.LintUtils; -import com.google.common.base.Charsets; -import com.google.common.base.Splitter; -import com.google.common.io.ByteSink; -import com.google.common.io.Files; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel.MapMode; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.WeakHashMap; - -/** - * Database of common typos / misspellings. - */ -public class TypoLookup { - private static final TypoLookup NONE = new TypoLookup(); - - /** String separating misspellings and suggested replacements in the text file */ - private static final String WORD_SEPARATOR = "->"; //$NON-NLS-1$ - - /** Relative path to the typos database file within the Lint installation */ - private static final String XML_FILE_PATH = "tools/support/typos-%1$s.txt"; //$NON-NLS-1$ - private static final String FILE_HEADER = "Typo database used by Android lint\000"; - private static final int BINARY_FORMAT_VERSION = 2; - private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false; - private static final boolean DEBUG_SEARCH = false; - private static final boolean WRITE_STATS = false; - /** Default size to reserve for each API entry when creating byte buffer to build up data */ - private static final int BYTES_PER_ENTRY = 28; - - private byte[] mData; - private int[] mIndices; - private int mWordCount; - - private static final WeakHashMap<String, TypoLookup> sInstanceMap = - new WeakHashMap<String, TypoLookup>(); - - /** - * Returns an instance of the Typo database for the given locale - * - * @param client the client to associate with this database - used only for - * logging. The database object may be shared among repeated - * invocations, and in that case client used will be the one - * originally passed in. In other words, this parameter may be - * ignored if the client created is not new. - * @param locale the locale to look up a typo database for (should be a - * language code (ISO 639-1, two lowercase character names) - * @param region the region to look up a typo database for (should be a two - * letter ISO 3166-1 alpha-2 country code in upper case) language - * code - * @return a (possibly shared) instance of the typo database, or null if its - * data can't be found - */ - @Nullable - public static TypoLookup get(@NonNull LintClient client, @NonNull String locale, - @Nullable String region) { - synchronized (TypoLookup.class) { - String key = locale; - - if (region != null && region.length() == 2) { // skip BCP-47 regions - // Allow for region-specific dictionaries. See for example - // http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences - assert region.length() == 2 - && Character.isUpperCase(region.charAt(0)) - && Character.isUpperCase(region.charAt(1)) : region; - // Look for typos-en-rUS.txt etc - key = locale + 'r' + region; - } - - TypoLookup db = sInstanceMap.get(key); - if (db == null) { - String path = String.format(XML_FILE_PATH, key); - File file = client.findResource(path); - if (file == null) { - // AOSP build environment? - String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$ - if (build != null) { - file = new File(build, ("sdk/files/" //$NON-NLS-1$ - + path.substring(path.lastIndexOf('/') + 1)) - .replace('/', File.separatorChar)); - } - } - - if (file == null || !file.exists()) { - //noinspection VariableNotUsedInsideIf - if (region != null) { - // Fall back to the generic locale (non-region-specific) database - return get(client, locale, null); - } - db = NONE; - } else { - db = get(client, file); - assert db != null : file; - } - sInstanceMap.put(key, db); - } - - if (db == NONE) { - return null; - } else { - return db; - } - } - } - - /** - * Returns an instance of the typo database - * - * @param client the client to associate with this database - used only for - * logging - * @param xmlFile the XML file containing configuration data to use for this - * database - * @return a (possibly shared) instance of the typo database, or null - * if its data can't be found - */ - @Nullable - private static TypoLookup get(LintClient client, File xmlFile) { - if (!xmlFile.exists()) { - client.log(null, "The typo database file %1$s does not exist", xmlFile); - return null; - } - - String name = xmlFile.getName(); - if (LintUtils.endsWith(name, DOT_XML)) { - name = name.substring(0, name.length() - DOT_XML.length()); - } - File cacheDir = client.getCacheDir(true/*create*/); - if (cacheDir == null) { - cacheDir = xmlFile.getParentFile(); - } - - File binaryData = new File(cacheDir, name - // Incorporate version number in the filename to avoid upgrade filename - // conflicts on Windows (such as issue #26663) - + '-' + BINARY_FORMAT_VERSION + ".bin"); //$NON-NLS-1$ - - if (DEBUG_FORCE_REGENERATE_BINARY) { - System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom " - + xmlFile + "\nto " + binaryData); - if (!createCache(client, xmlFile, binaryData)) { - return null; - } - } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified()) { - if (!createCache(client, xmlFile, binaryData)) { - return null; - } - } - - if (!binaryData.exists()) { - client.log(null, "The typo database file %1$s does not exist", binaryData); - return null; - } - - return new TypoLookup(client, xmlFile, binaryData); - } - - private static boolean createCache(LintClient client, File xmlFile, File binaryData) { - long begin = 0; - if (WRITE_STATS) { - begin = System.currentTimeMillis(); - } - - // Read in data - List<String> lines; - try { - lines = Files.readLines(xmlFile, Charsets.UTF_8); - } catch (IOException e) { - client.log(e, "Can't read typo database file"); - return false; - } - - if (WRITE_STATS) { - long end = System.currentTimeMillis(); - System.out.println("Reading data structures took " + (end - begin) + " ms)"); - } - - try { - writeDatabase(binaryData, lines); - return true; - } catch (IOException ioe) { - client.log(ioe, "Can't write typo cache file"); - } - - return false; - } - - /** Use one of the {@link #get} factory methods instead */ - private TypoLookup( - @NonNull LintClient client, - @NonNull File xmlFile, - @Nullable File binaryFile) { - if (binaryFile != null) { - readData(client, xmlFile, binaryFile); - } - } - - private TypoLookup() { - } - - private void readData(@NonNull LintClient client, @NonNull File xmlFile, - @NonNull File binaryFile) { - if (!binaryFile.exists()) { - client.log(null, "%1$s does not exist", binaryFile); - return; - } - long start = System.currentTimeMillis(); - try { - MappedByteBuffer buffer = Files.map(binaryFile, MapMode.READ_ONLY); - assert buffer.order() == ByteOrder.BIG_ENDIAN; - - // First skip the header - byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII); - buffer.rewind(); - for (byte anExpectedHeader : expectedHeader) { - if (anExpectedHeader != buffer.get()) { - client.log(null, "Incorrect file header: not an typo database cache " + - "file, or a corrupt cache file"); - return; - } - } - - // Read in the format number - if (buffer.get() != BINARY_FORMAT_VERSION) { - // Force regeneration of new binary data with up to date format - if (createCache(client, xmlFile, binaryFile)) { - readData(client, xmlFile, binaryFile); // Recurse - } - - return; - } - - mWordCount = buffer.getInt(); - - // Read in the word table indices; - int count = mWordCount; - int[] offsets = new int[count]; - - // Another idea: I can just store the DELTAS in the file (and add them up - // when reading back in) such that it takes just ONE byte instead of four! - - for (int i = 0; i < count; i++) { - offsets[i] = buffer.getInt(); - } - - // No need to read in the rest -- we'll just keep the whole byte array in memory - // TODO: Make this code smarter/more efficient. - int size = buffer.limit(); - byte[] b = new byte[size]; - buffer.rewind(); - buffer.get(b); - mData = b; - mIndices = offsets; - - // TODO: We only need to keep the data portion here since we've initialized - // the offset array separately. - // TODO: Investigate (profile) accessing the byte buffer directly instead of - // accessing a byte array. - } catch (IOException e) { - client.log(e, null); - } - if (WRITE_STATS) { - long end = System.currentTimeMillis(); - System.out.println("\nRead typo database in " + (end - start) - + " milliseconds."); - System.out.println("Size of data table: " + mData.length + " bytes (" - + Integer.toString(mData.length/1024) + "k)\n"); - } - } - - /** See the {@link #readData(LintClient,File,File)} for documentation on the data format. */ - private static void writeDatabase(File file, List<String> lines) throws IOException { - /* - * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded - * as ASCII characters. The purpose of the header is to identify what the file - * is for, for anyone attempting to open the file. - * 2. A file version number. If the binary file does not match the reader's expected - * version, it can ignore it (and regenerate the cache from XML). - */ - - // Drop comments etc - List<String> words = new ArrayList<String>(lines.size()); - for (String line : lines) { - if (!line.isEmpty() && Character.isLetter(line.charAt(0))) { - int end = line.indexOf(WORD_SEPARATOR); - if (end == -1) { - end = line.trim().length(); - } - String typo = line.substring(0, end).trim(); - String replacements = line.substring(end + WORD_SEPARATOR.length()).trim(); - if (replacements.isEmpty()) { - // We don't support empty replacements - continue; - } - String combined = typo + (char) 0 + replacements; - - words.add(combined); - } - } - - byte[][] wordArrays = new byte[words.size()][]; - for (int i = 0, n = words.size(); i < n; i++) { - String word = words.get(i); - wordArrays[i] = word.getBytes(Charsets.UTF_8); - } - // Sort words, using our own comparator to ensure that it matches the - // binary search in getTypos() - Comparator<byte[]> comparator = new Comparator<byte[]>() { - @Override - public int compare(byte[] o1, byte[] o2) { - return TypoLookup.compare(o1, 0, (byte) 0, o2, 0, o2.length); - } - }; - Arrays.sort(wordArrays, comparator); - - byte[] headerBytes = FILE_HEADER.getBytes(Charsets.US_ASCII); - int entryCount = wordArrays.length; - int capacity = entryCount * BYTES_PER_ENTRY + headerBytes.length + 5; - ByteBuffer buffer = ByteBuffer.allocate(capacity); - buffer.order(ByteOrder.BIG_ENDIAN); - // 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded - // as ASCII characters. The purpose of the header is to identify what the file - // is for, for anyone attempting to open the file. - buffer.put(headerBytes); - - // 2. A file version number. If the binary file does not match the reader's expected - // version, it can ignore it (and regenerate the cache from XML). - buffer.put((byte) BINARY_FORMAT_VERSION); - - // 3. The number of words [1 int] - buffer.putInt(entryCount); - - // 4. Word offset table (one integer per word, pointing to the byte offset in the - // file (relative to the beginning of the file) where each word begins. - // The words are always sorted alphabetically. - int wordOffsetTable = buffer.position(); - - // Reserve enough room for the offset table here: we will backfill it with pointers - // as we're writing out the data structures below - for (int i = 0, n = entryCount; i < n; i++) { - buffer.putInt(0); - } - - int nextEntry = buffer.position(); - int nextOffset = wordOffsetTable; - - // 7. Word entry table. Each word entry consists of the word, followed by the byte 0 - // as a terminator, followed by a comma separated list of suggestions (which - // may be empty), or a final 0. - for (byte[] word : wordArrays) { - buffer.position(nextOffset); - buffer.putInt(nextEntry); - nextOffset = buffer.position(); - buffer.position(nextEntry); - - buffer.put(word); // already embeds 0 to separate typo from words - buffer.put((byte)0); - - nextEntry = buffer.position(); - } - - int size = buffer.position(); - assert size <= buffer.limit(); - buffer.mark(); - - if (WRITE_STATS) { - System.out.println("Wrote " + words.size() + " word entries"); - System.out.print("Actual binary size: " + size + " bytes"); - System.out.println(String.format(" (%.1fM)", size/(1024*1024.f))); - - System.out.println("Allocated size: " + (entryCount * BYTES_PER_ENTRY) + " bytes"); - System.out.println("Required bytes per entry: " + (size/ entryCount) + " bytes"); - } - - // Now dump this out as a file - // There's probably an API to do this more efficiently; TODO: Look into this. - byte[] b = new byte[size]; - buffer.rewind(); - buffer.get(b); - ByteSink sink = Files.asByteSink(file); - sink.write(b); - } - - // For debugging only - private String dumpEntry(int offset) { - if (DEBUG_SEARCH) { - int end = offset; - while (mData[end] != 0) { - end++; - } - return new String(mData, offset, end - offset, Charsets.UTF_8); - } else { - return "<disabled>"; //$NON-NLS-1$ - } - } - - /** Comparison function: *only* used for ASCII strings */ - @VisibleForTesting - static int compare(byte[] data, int offset, byte terminator, CharSequence s, - int begin, int end) { - int i = offset; - int j = begin; - for (; ; i++, j++) { - byte b = data[i]; - if (b == ' ') { - // We've matched up to the space in a split-word typo, such as - // in German all zu⇒allzu; here we've matched just past "all". - // Rather than terminating, attempt to continue in the buffer. - if (j == end) { - int max = s.length(); - if (end < max && s.charAt(end) == ' ') { - // Find next word - for (; end < max; end++) { - char c = s.charAt(end); - if (!Character.isLetter(c)) { - if (c == ' ' && end == j) { - continue; - } - break; - } - } - } - } - } - - if (j == end) { - break; - } - - if (b == '*') { - // Glob match (only supported at the end) - return 0; - } - char c = s.charAt(j); - byte cb = (byte) c; - int delta = b - cb; - if (delta != 0) { - cb = (byte) Character.toLowerCase(c); - if (b != cb) { - // Ensure that it has the right sign - b = (byte) Character.toLowerCase(b); - delta = b - cb; - if (delta != 0) { - return delta; - } - } - } - } - - return data[i] - terminator; - } - - /** Comparison function used for general UTF-8 encoded strings */ - @VisibleForTesting - static int compare(byte[] data, int offset, byte terminator, byte[] s, - int begin, int end) { - int i = offset; - int j = begin; - for (; ; i++, j++) { - byte b = data[i]; - if (b == ' ') { - // We've matched up to the space in a split-word typo, such as - // in German all zu⇒allzu; here we've matched just past "all". - // Rather than terminating, attempt to continue in the buffer. - // We've matched up to the space in a split-word typo, such as - // in German all zu⇒allzu; here we've matched just past "all". - // Rather than terminating, attempt to continue in the buffer. - if (j == end) { - int max = s.length; - if (end < max && s[end] == ' ') { - // Find next word - for (; end < max; end++) { - byte cb = s[end]; - if (!isLetter(cb)) { - if (cb == ' ' && end == j) { - continue; - } - break; - } - } - } - } - } - - if (j == end) { - break; - } - if (b == '*') { - // Glob match (only supported at the end) - return 0; - } - byte cb = s[j]; - int delta = b - cb; - if (delta != 0) { - cb = toLowerCase(cb); - b = toLowerCase(b); - delta = b - cb; - if (delta != 0) { - return delta; - } - } - - if (b == terminator || cb == terminator) { - return delta; - } - } - - return data[i] - terminator; - } - - /** - * Look up whether this word is a typo, and if so, return the typo itself - * and one or more likely meanings - * - * @param text the string containing the word - * @param begin the index of the first character in the word - * @param end the index of the first character after the word. Note that the - * search may extend <b>beyond</b> this index, if for example the - * word matches a multi-word typo in the dictionary - * @return a list of the typo itself followed by the replacement strings if - * the word represents a typo, and null otherwise - */ - @Nullable - public List<String> getTypos(@NonNull CharSequence text, int begin, int end) { - assert end <= text.length(); - - if (assertionsEnabled()) { - for (int i = begin; i < end; i++) { - char c = text.charAt(i); - if (c >= 128) { - assert false : "Call the UTF-8 version of this method instead"; - return null; - } - } - } - - int low = 0; - int high = mWordCount - 1; - while (low <= high) { - int middle = (low + high) >>> 1; - int offset = mIndices[middle]; - - if (DEBUG_SEARCH) { - System.out.println("Comparing string " + text +" with entry at " + offset - + ": " + dumpEntry(offset)); - } - - // Compare the word at the given index. - int compare = compare(mData, offset, (byte) 0, text, begin, end); - - if (compare == 0) { - offset = mIndices[middle]; - - // Don't allow matching uncapitalized words, such as "enlish", when - // the dictionary word is capitalized, "Enlish". - if (mData[offset] != text.charAt(begin) - && Character.isLowerCase(text.charAt(begin))) { - return null; - } - - // Make sure there is a case match; we only want to allow - // matching capitalized words to capitalized typos or uncapitalized typos - // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized - // typos (e.g. "enlish" to "Enlish"). - String glob = null; - for (int i = begin; ; i++) { - byte b = mData[offset++]; - if (b == 0) { - offset--; - break; - } else if (b == '*') { - int globEnd = i; - while (globEnd < text.length() - && Character.isLetter(text.charAt(globEnd))) { - globEnd++; - } - glob = text.subSequence(i, globEnd).toString(); - break; - } - char c = text.charAt(i); - byte cb = (byte) c; - if (b != cb && i > begin) { - return null; - } - } - - return computeSuggestions(mIndices[middle], offset, glob); - } - - if (compare < 0) { - low = middle + 1; - } else if (compare > 0) { - high = middle - 1; - } else { - assert false; // compare == 0 already handled above - return null; - } - } - - return null; - } - - /** - * Look up whether this word is a typo, and if so, return the typo itself - * and one or more likely meanings - * - * @param utf8Text the string containing the word, encoded as UTF-8 - * @param begin the index of the first character in the word - * @param end the index of the first character after the word. Note that the - * search may extend <b>beyond</b> this index, if for example the - * word matches a multi-word typo in the dictionary - * @return a list of the typo itself followed by the replacement strings if - * the word represents a typo, and null otherwise - */ - @Nullable - public List<String> getTypos(@NonNull byte[] utf8Text, int begin, int end) { - assert end <= utf8Text.length; - - int low = 0; - int high = mWordCount - 1; - while (low <= high) { - int middle = (low + high) >>> 1; - int offset = mIndices[middle]; - - if (DEBUG_SEARCH) { - String s = new String(Arrays.copyOfRange(utf8Text, begin, end), Charsets.UTF_8); - System.out.println("Comparing string " + s +" with entry at " + offset - + ": " + dumpEntry(offset)); - System.out.println(" middle=" + middle + ", low=" + low + ", high=" + high); - } - - // Compare the word at the given index. - int compare = compare(mData, offset, (byte) 0, utf8Text, begin, end); - - if (DEBUG_SEARCH) { - System.out.println(" signum=" + (int)Math.signum(compare) + ", delta=" + compare); - } - - if (compare == 0) { - offset = mIndices[middle]; - - // Don't allow matching uncapitalized words, such as "enlish", when - // the dictionary word is capitalized, "Enlish". - if (mData[offset] != utf8Text[begin] && isUpperCase(mData[offset])) { - return null; - } - - // Make sure there is a case match; we only want to allow - // matching capitalized words to capitalized typos or uncapitalized typos - // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized - // typos (e.g. "enlish" to "Enlish"). - String glob = null; - for (int i = begin; ; i++) { - byte b = mData[offset++]; - if (b == 0) { - offset--; - break; - } else if (b == '*') { - int globEnd = i; - while (globEnd < utf8Text.length && isLetter(utf8Text[globEnd])) { - globEnd++; - } - glob = new String(utf8Text, i, globEnd - i, Charsets.UTF_8); - break; - } - byte cb = utf8Text[i]; - if (b != cb && i > begin) { - return null; - } - } - - return computeSuggestions(mIndices[middle], offset, glob); - } - - if (compare < 0) { - low = middle + 1; - } else if (compare > 0) { - high = middle - 1; - } else { - assert false; // compare == 0 already handled above - return null; - } - } - - return null; - } - - private List<String> computeSuggestions(int begin, int offset, String glob) { - String typo = new String(mData, begin, offset - begin, Charsets.UTF_8); - - if (glob != null) { - typo = typo.replaceAll("\\*", glob); //$NON-NLS-1$ - } - - assert mData[offset] == 0; - offset++; - int replacementEnd = offset; - while (mData[replacementEnd] != 0) { - replacementEnd++; - } - String replacements = new String(mData, offset, replacementEnd - offset, Charsets.UTF_8); - List<String> words = new ArrayList<String>(); - words.add(typo); - - // The first entry should be the typo itself. We need to pass this back since due - // to multi-match words and globbing it could extend beyond the initial word range - - for (String s : Splitter.on(',').omitEmptyStrings().trimResults().split(replacements)) { - if (glob != null) { - // Need to append the glob string to each result - words.add(s.replaceAll("\\*", glob)); //$NON-NLS-1$ - } else { - words.add(s); - } - } - - return words; - } - - // "Character" handling for bytes. This assumes that the bytes correspond to Unicode - // characters in the ISO 8859-1 range, which is are encoded the same way in UTF-8. - // This obviously won't work to for example uppercase to lowercase conversions for - // multi byte characters, which means we simply won't catch typos if the dictionaries - // contain these. None of the currently included dictionaries do. However, it does - // help us properly deal with punctuation and spacing characters. - - static boolean isUpperCase(byte b) { - return Character.isUpperCase((char) b); - } - - static byte toLowerCase(byte b) { - return (byte) Character.toLowerCase((char) b); - } - - static boolean isSpace(byte b) { - return Character.isWhitespace((char) b); - } - - static boolean isLetter(byte b) { - // Assume that multi byte characters represent letters in other languages. - // Obviously, it could be unusual punctuation etc but letters are more likely - // in this context. - return Character.isLetter((char) b) || (b & 0x80) != 0; - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeBroadcastReceiverDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeBroadcastReceiverDetector.java deleted file mode 100644 index c29068e..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeBroadcastReceiverDetector.java +++ /dev/null
@@ -1,569 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_PERMISSION; -import static com.android.SdkConstants.CLASS_BROADCASTRECEIVER; -import static com.android.SdkConstants.CLASS_CONTEXT; -import static com.android.SdkConstants.CLASS_INTENT; -import static com.android.SdkConstants.TAG_INTENT_FILTER; -import static com.android.SdkConstants.TAG_RECEIVER; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ClassContext; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Detector.XmlScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.XmlContext; -import com.google.common.collect.Sets; -import com.intellij.psi.JavaRecursiveElementVisitor; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiMethodCallExpression; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiReferenceExpression; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.USimpleNameReferenceExpression; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; -import org.w3c.dom.Element; - -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class UnsafeBroadcastReceiverDetector extends Detector - implements Detector.UastScanner, XmlScanner { - - /* Description of check implementations: - * - * UnsafeProtectedBroadcastReceiver check - * - * If a receiver is declared in the application manifest that has an intent-filter - * with an action string that matches a protected-broadcast action string, - * then if that receiver has an onReceive method, ensure that the method calls - * getAction at least once. - * - * With this check alone, false positives will occur if the onReceive method - * passes the received intent to another method that calls getAction. - * We look for any calls to aload_2 within the method bytecode, which could - * indicate loading the inputted intent onto the stack to use in a call - * to another method. In those cases, still report the issue, but - * report in the description that the finding may be a false positive. - * An alternative implementation option would be to omit reporting the issue - * at all when a call to aload_2 exists. - * - * UnprotectedSMSBroadcastReceiver check - * - * If a receiver is declared in AndroidManifest that has an intent-filter - * with action string SMS_DELIVER or SMS_RECEIVED, ensure that the - * receiver requires callers to have the BROADCAST_SMS permission. - * - * It is possible that the receiver may check the sender's permission by - * calling checkCallingPermission, which could cause a false positive. - * However, application developers should still be encouraged to declare - * the permission requirement in the manifest where it can be easily - * audited. - * - * Future work: Add checks for other action strings that should require - * particular permissions be checked, such as - * android.provider.Telephony.WAP_PUSH_DELIVER - * - * Note that neither of these checks address receivers dynamically created at runtime, - * only ones that are declared in the application manifest. - */ - - public static final Issue ACTION_STRING = Issue.create( - "UnsafeProtectedBroadcastReceiver", - "Unsafe Protected BroadcastReceiver", - "BroadcastReceivers that declare an intent-filter for a protected-broadcast action " + - "string must check that the received intent's action string matches the expected " + - "value, otherwise it is possible for malicious actors to spoof intents.", - Category.SECURITY, - 6, - Severity.WARNING, - new Implementation(UnsafeBroadcastReceiverDetector.class, - EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE))); - - public static final Issue BROADCAST_SMS = Issue.create( - "UnprotectedSMSBroadcastReceiver", - "Unprotected SMS BroadcastReceiver", - "BroadcastReceivers that declare an intent-filter for SMS_DELIVER or " + - "SMS_RECEIVED must ensure that the caller has the BROADCAST_SMS permission, " + - "otherwise it is possible for malicious actors to spoof intents.", - Category.SECURITY, - 6, - Severity.WARNING, - new Implementation(UnsafeBroadcastReceiverDetector.class, - Scope.MANIFEST_SCOPE)); - - /* List of protected broadcast strings. This list must be sorted alphabetically. - * Protected broadcast strings are defined by <protected-broadcast> entries in the - * manifest of system-level components or applications. - * The below list is copied from frameworks/base/core/res/AndroidManifest.xml - * and packages/services/Telephony/AndroidManifest.xml . - * It should be periodically updated. This list will likely not be complete, since - * protected-broadcast entries can be defined elsewhere, but should address - * most situations. - */ - @VisibleForTesting - static final String[] PROTECTED_BROADCASTS = new String[] { - "android.app.action.DEVICE_OWNER_CHANGED", - "android.app.action.ENTER_CAR_MODE", - "android.app.action.ENTER_DESK_MODE", - "android.app.action.EXIT_CAR_MODE", - "android.app.action.EXIT_DESK_MODE", - "android.app.action.NEXT_ALARM_CLOCK_CHANGED", - "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED", - "android.appwidget.action.APPWIDGET_DELETED", - "android.appwidget.action.APPWIDGET_DISABLED", - "android.appwidget.action.APPWIDGET_ENABLED", - "android.appwidget.action.APPWIDGET_HOST_RESTORED", - "android.appwidget.action.APPWIDGET_RESTORED", - "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS", - "android.backup.intent.CLEAR", - "android.backup.intent.INIT", - "android.backup.intent.RUN", - "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED", - "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED", - "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED", - "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.adapter.action.DISCOVERY_FINISHED", - "android.bluetooth.adapter.action.DISCOVERY_STARTED", - "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED", - "android.bluetooth.adapter.action.SCAN_MODE_CHANGED", - "android.bluetooth.adapter.action.STATE_CHANGED", - "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.device.action.ACL_CONNECTED", - "android.bluetooth.device.action.ACL_DISCONNECTED", - "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED", - "android.bluetooth.device.action.ALIAS_CHANGED", - "android.bluetooth.device.action.BOND_STATE_CHANGED", - "android.bluetooth.device.action.CLASS_CHANGED", - "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL", - "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY", - "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST", - "android.bluetooth.device.action.DISAPPEARED", - "android.bluetooth.device.action.FOUND", - "android.bluetooth.device.action.MAS_INSTANCE", - "android.bluetooth.device.action.NAME_CHANGED", - "android.bluetooth.device.action.NAME_FAILED", - "android.bluetooth.device.action.PAIRING_CANCEL", - "android.bluetooth.device.action.PAIRING_REQUEST", - "android.bluetooth.device.action.UUID", - "android.bluetooth.devicepicker.action.DEVICE_SELECTED", - "android.bluetooth.devicepicker.action.LAUNCH", - "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT", - "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED", - "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED", - "android.bluetooth.headsetclient.profile.action.AG_EVENT", - "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED", - "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.headsetclient.profile.action.LAST_VTAG", - "android.bluetooth.headsetclient.profile.action.RESULT", - "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED", - "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS", - "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED", - "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED", - "android.btopp.intent.action.CONFIRM", - "android.btopp.intent.action.HIDE", - "android.btopp.intent.action.HIDE_COMPLETE", - "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION", - "android.btopp.intent.action.LIST", - "android.btopp.intent.action.OPEN", - "android.btopp.intent.action.OPEN_INBOUND", - "android.btopp.intent.action.OPEN_OUTBOUND", - "android.btopp.intent.action.RETRY", - "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT", - "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED", - "android.hardware.usb.action.USB_ACCESSORY_ATTACHED", - "android.hardware.usb.action.USB_ACCESSORY_DETACHED", - "android.hardware.usb.action.USB_DEVICE_ATTACHED", - "android.hardware.usb.action.USB_DEVICE_DETACHED", - "android.hardware.usb.action.USB_PORT_CHANGED", - "android.hardware.usb.action.USB_STATE", - "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED", - "android.intent.action.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED", - "android.intent.action.ACTION_DEFAULT_SUBSCRIPTION_CHANGED", - "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED", - "android.intent.action.ACTION_IDLE_MAINTENANCE_END", - "android.intent.action.ACTION_IDLE_MAINTENANCE_START", - "android.intent.action.ACTION_POWER_CONNECTED", - "android.intent.action.ACTION_POWER_DISCONNECTED", - "android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE", - "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED", - "android.intent.action.ACTION_SHUTDOWN", - "android.intent.action.ACTION_SUBINFO_CONTENT_CHANGE", - "android.intent.action.ACTION_SUBINFO_RECORD_UPDATED", - "android.intent.action.ADVANCED_SETTINGS", - "android.intent.action.AIRPLANE_MODE", - "android.intent.action.ANY_DATA_STATE", - "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED", - "android.intent.action.BATTERY_CHANGED", - "android.intent.action.BATTERY_LOW", - "android.intent.action.BATTERY_OKAY", - "android.intent.action.BOOT_COMPLETED", - "android.intent.action.BUGREPORT_FINISHED", - "android.intent.action.CHARGING", - "android.intent.action.CLEAR_DNS_CACHE", - "android.intent.action.CONFIGURATION_CHANGED", - "android.intent.action.DATE_CHANGED", - "android.intent.action.DEVICE_STORAGE_FULL", - "android.intent.action.DEVICE_STORAGE_LOW", - "android.intent.action.DEVICE_STORAGE_NOT_FULL", - "android.intent.action.DEVICE_STORAGE_OK", - "android.intent.action.DISCHARGING", - "android.intent.action.DOCK_EVENT", - "android.intent.action.DREAMING_STARTED", - "android.intent.action.DREAMING_STOPPED", - "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE", - "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE", - "android.intent.action.HDMI_PLUGGED", - "android.intent.action.HEADSET_PLUG", - "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION", - "android.intent.action.LOCALE_CHANGED", - "android.intent.action.MASTER_CLEAR_NOTIFICATION", - "android.intent.action.MEDIA_BAD_REMOVAL", - "android.intent.action.MEDIA_CHECKING", - "android.intent.action.MEDIA_EJECT", - "android.intent.action.MEDIA_MOUNTED", - "android.intent.action.MEDIA_NOFS", - "android.intent.action.MEDIA_REMOVED", - "android.intent.action.MEDIA_SHARED", - "android.intent.action.MEDIA_UNMOUNTABLE", - "android.intent.action.MEDIA_UNMOUNTED", - "android.intent.action.MEDIA_UNSHARED", - "android.intent.action.MY_PACKAGE_REPLACED", - "android.intent.action.NEW_OUTGOING_CALL", - "android.intent.action.PACKAGE_ADDED", - "android.intent.action.PACKAGE_CHANGED", - "android.intent.action.PACKAGE_DATA_CLEARED", - "android.intent.action.PACKAGE_FIRST_LAUNCH", - "android.intent.action.PACKAGE_FULLY_REMOVED", - "android.intent.action.PACKAGE_INSTALL", - "android.intent.action.PACKAGE_NEEDS_VERIFICATION", - "android.intent.action.PACKAGE_REMOVED", - "android.intent.action.PACKAGE_REPLACED", - "android.intent.action.PACKAGE_RESTARTED", - "android.intent.action.PACKAGE_VERIFIED", - "android.intent.action.PERMISSION_RESPONSE_RECEIVED", - "android.intent.action.PHONE_STATE", - "android.intent.action.PROXY_CHANGE", - "android.intent.action.QUERY_PACKAGE_RESTART", - "android.intent.action.REBOOT", - "android.intent.action.REQUEST_PERMISSION", - "android.intent.action.SCREEN_OFF", - "android.intent.action.SCREEN_ON", - "android.intent.action.SUB_DEFAULT_CHANGED", - "android.intent.action.THERMAL_EVENT", - "android.intent.action.TIMEZONE_CHANGED", - "android.intent.action.TIME_SET", - "android.intent.action.TIME_TICK", - "android.intent.action.UID_REMOVED", - "android.intent.action.USER_ADDED", - "android.intent.action.USER_BACKGROUND", - "android.intent.action.USER_FOREGROUND", - "android.intent.action.USER_PRESENT", - "android.intent.action.USER_REMOVED", - "android.intent.action.USER_STARTED", - "android.intent.action.USER_STARTING", - "android.intent.action.USER_STOPPED", - "android.intent.action.USER_STOPPING", - "android.intent.action.USER_SWITCHED", - "android.internal.policy.action.BURN_IN_PROTECTION", - "android.location.GPS_ENABLED_CHANGE", - "android.location.GPS_FIX_CHANGE", - "android.location.MODE_CHANGED", - "android.location.PROVIDERS_CHANGED", - "android.media.ACTION_SCO_AUDIO_STATE_UPDATED", - "android.media.AUDIO_BECOMING_NOISY", - "android.media.MASTER_MUTE_CHANGED_ACTION", - "android.media.MASTER_VOLUME_CHANGED_ACTION", - "android.media.RINGER_MODE_CHANGED", - "android.media.SCO_AUDIO_STATE_CHANGED", - "android.media.VIBRATE_SETTING_CHANGED", - "android.media.VOLUME_CHANGED_ACTION", - "android.media.action.HDMI_AUDIO_PLUG", - "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED", - "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED", - "android.net.conn.CAPTIVE_PORTAL", - "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED", - "android.net.conn.CONNECTIVITY_CHANGE", - "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE", - "android.net.conn.DATA_ACTIVITY_CHANGE", - "android.net.conn.INET_CONDITION_ACTION", - "android.net.conn.NETWORK_CONDITIONS_MEASURED", - "android.net.conn.TETHER_STATE_CHANGED", - "android.net.nsd.STATE_CHANGED", - "android.net.proxy.PAC_REFRESH", - "android.net.scoring.SCORER_CHANGED", - "android.net.scoring.SCORE_NETWORKS", - "android.net.wifi.CONFIGURED_NETWORKS_CHANGE", - "android.net.wifi.LINK_CONFIGURATION_CHANGED", - "android.net.wifi.RSSI_CHANGED", - "android.net.wifi.SCAN_RESULTS", - "android.net.wifi.STATE_CHANGE", - "android.net.wifi.WIFI_AP_STATE_CHANGED", - "android.net.wifi.WIFI_CREDENTIAL_CHANGED", - "android.net.wifi.WIFI_SCAN_AVAILABLE", - "android.net.wifi.WIFI_STATE_CHANGED", - "android.net.wifi.p2p.CONNECTION_STATE_CHANGE", - "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE", - "android.net.wifi.p2p.PEERS_CHANGED", - "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED", - "android.net.wifi.p2p.STATE_CHANGED", - "android.net.wifi.p2p.THIS_DEVICE_CHANGED", - "android.net.wifi.supplicant.CONNECTION_CHANGE", - "android.net.wifi.supplicant.STATE_CHANGE", - "android.nfc.action.LLCP_LINK_STATE_CHANGED", - "android.nfc.action.TRANSACTION_DETECTED", - "android.nfc.handover.intent.action.HANDOVER_STARTED", - "android.nfc.handover.intent.action.TRANSFER_DONE", - "android.nfc.handover.intent.action.TRANSFER_PROGRESS", - "android.os.UpdateLock.UPDATE_LOCK_CHANGED", - "android.os.action.DEVICE_IDLE_MODE_CHANGED", - "android.os.action.POWER_SAVE_MODE_CHANGED", - "android.os.action.POWER_SAVE_MODE_CHANGING", - "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED", - "android.os.action.POWER_SAVE_WHITELIST_CHANGED", - "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED", - "android.os.action.SETTING_RESTORED", - "android.telecom.action.DEFAULT_DIALER_CHANGED", - "com.android.bluetooth.pbap.authcancelled", - "com.android.bluetooth.pbap.authchall", - "com.android.bluetooth.pbap.authresponse", - "com.android.bluetooth.pbap.userconfirmtimeout", - "com.android.nfc_extras.action.AID_SELECTED", - "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED", - "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED", - "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP", - "com.android.server.WifiManager.action.START_PNO", - "com.android.server.WifiManager.action.START_SCAN", - "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION", - }; - - private static final Set<String> PROTECTED_BROADCAST_SET = - Sets.newHashSet(PROTECTED_BROADCASTS); - - private final Set<String> mReceiversWithProtectedBroadcastIntentFilter = new HashSet<String>(); - - public UnsafeBroadcastReceiverDetector() { - } - - // ---- Implements XmlScanner ---- - - @Override - public Collection<String> getApplicableElements() { - return Collections.singletonList(TAG_RECEIVER); - } - - @Override - public void visitElement(@NonNull XmlContext context, - @NonNull Element element) { - String tag = element.getTagName(); - if (TAG_RECEIVER.equals(tag)) { - String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME); - String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION); - // If no permission attribute, then if any exists at the application - // element, it applies - if (permission == null || permission.isEmpty()) { - Element parent = (Element) element.getParentNode(); - permission = parent.getAttributeNS(ANDROID_URI, ATTR_PERMISSION); - } - List<Element> children = LintUtils.getChildren(element); - for (Element child : children) { - String tagName = child.getTagName(); - if (TAG_INTENT_FILTER.equals(tagName)) { - if (name.startsWith(".")) { - name = context.getProject().getPackage() + name; - } - name = name.replace('$', '.'); - List<Element> children2 = LintUtils.getChildren(child); - for (Element child2 : children2) { - if ("action".equals(child2.getTagName())) { - String actionName = child2.getAttributeNS( - ANDROID_URI, ATTR_NAME); - if (("android.provider.Telephony.SMS_DELIVER".equals(actionName) || - "android.provider.Telephony.SMS_RECEIVED". - equals(actionName)) && - !"android.permission.BROADCAST_SMS".equals(permission)) { - context.report( - BROADCAST_SMS, - element, - context.getLocation(element), - "BroadcastReceivers that declare an intent-filter for " + - "SMS_DELIVER or SMS_RECEIVED must ensure that the " + - "caller has the BROADCAST_SMS permission, otherwise it " + - "is possible for malicious actors to spoof intents."); - } - else if (PROTECTED_BROADCAST_SET.contains(actionName)) { - mReceiversWithProtectedBroadcastIntentFilter.add(name); - } - } - } - break; - } - } - } - } - - // ---- Implements JavaScanner ---- - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return mReceiversWithProtectedBroadcastIntentFilter.isEmpty() - ? null : Collections.singletonList(CLASS_BROADCASTRECEIVER); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - String name = declaration.getName(); - if (name == null) { - // anonymous classes can't be the ones referenced in the manifest - return; - } - String qualifiedName = declaration.getQualifiedName(); - if (qualifiedName == null) { - return; - } - if (!mReceiversWithProtectedBroadcastIntentFilter.contains(qualifiedName)) { - return; - } - JavaEvaluator evaluator = context.getEvaluator(); - for (PsiMethod method : declaration.findMethodsByName("onReceive", false)) { - if (evaluator.parametersMatch(method, CLASS_CONTEXT, CLASS_INTENT)) { - checkOnReceive(context, method); - } - } - } - - private static void checkOnReceive(@NonNull JavaContext context, - @NonNull PsiMethod method) { - // Search for call to getAction but also search for references to aload_2, - // which indicates that the method is making use of the received intent in - // some way. - // - // If the onReceive method doesn't call getAction but does make use of - // the received intent, it is possible that it is passing it to another - // method that might be performing the getAction check, so we warn that the - // finding may be a false positive. (An alternative option would be to not - // report a finding at all in this case.) - PsiParameter parameter = method.getParameterList().getParameters()[1]; - OnReceiveVisitor visitor = new OnReceiveVisitor(context.getEvaluator(), parameter); - context.getUastContext().getMethodBody(method).accept(visitor); - if (!visitor.getCallsGetAction()) { - String report; - if (!visitor.getUsesIntent()) { - report = "This broadcast receiver declares an intent-filter for a protected " + - "broadcast action string, which can only be sent by the system, " + - "not third-party applications. However, the receiver's onReceive " + - "method does not appear to call getAction to ensure that the " + - "received Intent's action string matches the expected value, " + - "potentially making it possible for another actor to send a " + - "spoofed intent with no action string or a different action " + - "string and cause undesired behavior."; - } else { - // An alternative implementation option is to not report a finding at all in - // this case, if we are worried about false positives causing confusion or - // resulting in developers ignoring other lint warnings. - report = "This broadcast receiver declares an intent-filter for a protected " + - "broadcast action string, which can only be sent by the system, " + - "not third-party applications. However, the receiver's onReceive " + - "method does not appear to call getAction to ensure that the " + - "received Intent's action string matches the expected value, " + - "potentially making it possible for another actor to send a " + - "spoofed intent with no action string or a different action " + - "string and cause undesired behavior. In this case, it is " + - "possible that the onReceive method passed the received Intent " + - "to another method that checked the action string. If so, this " + - "finding can safely be ignored."; - } - Location location = context.getNameLocation(method); - context.report(ACTION_STRING, method, location, report); - } - } - - private static class OnReceiveVisitor extends AbstractUastVisitor { - @NonNull private final JavaEvaluator mEvaluator; - @Nullable private final PsiParameter mParameter; - private boolean mCallsGetAction; - private boolean mUsesIntent; - - public OnReceiveVisitor(@NonNull JavaEvaluator context, @Nullable PsiParameter parameter) { - mEvaluator = context; - mParameter = parameter; - } - - public boolean getCallsGetAction() { - return mCallsGetAction; - } - - public boolean getUsesIntent() { - return mUsesIntent; - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (!mCallsGetAction && UastExpressionUtils.isMethodCall(node)) { - PsiMethod method = node.resolve(); - if (method != null && "getAction".equals(method.getName()) && - mEvaluator.isMemberInSubClassOf(method, CLASS_INTENT, false)) { - mCallsGetAction = true; - } - } - return super.visitCallExpression(node); - } - - @Override - public boolean visitSimpleNameReferenceExpression(USimpleNameReferenceExpression node) { - if (!mUsesIntent && mParameter != null) { - PsiElement resolved = node.resolve(); - if (mParameter.equals(resolved)) { - mUsesIntent = true; - } - } - return super.visitSimpleNameReferenceExpression(node); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeNativeCodeDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeNativeCodeDetector.java deleted file mode 100644 index f64cb6f..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeNativeCodeDetector.java +++ /dev/null
@@ -1,214 +0,0 @@ -/* - * Copyright (C) 2015 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.klint.checks; - -import static com.android.SdkConstants.DOT_NATIVE_LIBS; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Project; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.Speed; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class UnsafeNativeCodeDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - UnsafeNativeCodeDetector.class, - Scope.JAVA_FILE_SCOPE); - - public static final Issue LOAD = Issue.create( - "UnsafeDynamicallyLoadedCode", - "`load` used to dynamically load code", - "Dynamically loading code from locations other than the application's library " + - "directory or the Android platform's built-in library directories is dangerous, " + - "as there is an increased risk that the code could have been tampered with. " + - "Applications should use `loadLibrary` when possible, which provides increased " + - "assurance that libraries are loaded from one of these safer locations. " + - "Application developers should use the features of their development " + - "environment to place application native libraries into the lib directory " + - "of their compiled APKs.", - Category.SECURITY, - 4, - Severity.WARNING, - IMPLEMENTATION); - - public static final Issue UNSAFE_NATIVE_CODE_LOCATION = Issue.create( - "UnsafeNativeCodeLocation", //$NON-NLS-1$ - "Native code outside library directory", - "In general, application native code should only be placed in the application's " + - "library directory, not in other locations such as the res or assets directories. " + - "Placing the code in the library directory provides increased assurance that the " + - "code will not be tampered with after application installation. Application " + - "developers should use the features of their development environment to place " + - "application native libraries into the lib directory of their compiled " + - "APKs. Embedding non-shared library native executables into applications should " + - "be avoided when possible.", - Category.SECURITY, - 4, - Severity.WARNING, - IMPLEMENTATION); - - private static final String RUNTIME_CLASS = "java.lang.Runtime"; //$NON-NLS-1$ - private static final String SYSTEM_CLASS = "java.lang.System"; //$NON-NLS-1$ - - private static final byte[] ELF_MAGIC_VALUE = { (byte) 0x7F, (byte) 0x45, (byte) 0x4C, (byte) 0x46 }; - - @NonNull - @Override - public Speed getSpeed() { - return Speed.NORMAL; - } - - // ---- Implements Detector.UastScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - // Identify calls to Runtime.load() and System.load() - return Collections.singletonList("load"); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - // Report calls to Runtime.load() and System.load() - if ("load".equals(method.getName())) { - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.isMemberInSubClassOf(method, RUNTIME_CLASS, false) || - evaluator.isMemberInSubClassOf(method, SYSTEM_CLASS, false)) { - context.report(LOAD, call, context.getUastLocation(call), - "Dynamically loading code using `load` is risky, please use " + - "`loadLibrary` instead when possible"); - } - } - } - - // ---- Look for code in resource and asset directories ---- - - @Override - public void afterCheckLibraryProject(@NonNull Context context) { - if (!context.getProject().getReportIssues()) { - // If this is a library project not being analyzed, ignore it - return; - } - - checkResourceFolders(context, context.getProject()); - } - - @Override - public void afterCheckProject(@NonNull Context context) { - if (!context.getProject().getReportIssues()) { - // If this is a library project not being analyzed, ignore it - return; - } - - checkResourceFolders(context, context.getProject()); - } - - private static boolean isNativeCode(File file) { - if (!file.isFile()) { - return false; - } - - try { - FileInputStream fis = new FileInputStream(file); - try { - byte[] bytes = new byte[4]; - int length = fis.read(bytes); - return (length == 4) && (Arrays.equals(ELF_MAGIC_VALUE, bytes)); - } finally { - fis.close(); - } - } catch (IOException ex) { - return false; - } - } - - private static void checkResourceFolders(Context context, @NonNull Project project) { - if (!context.getScope().contains(Scope.RESOURCE_FOLDER)) { - // Don't do work when doing in-editor analysis of Java files - return; - } - List<File> resourceFolders = project.getResourceFolders(); - for (File res : resourceFolders) { - File[] folders = res.listFiles(); - if (folders != null) { - for (File typeFolder : folders) { - if (typeFolder.getName().startsWith(SdkConstants.FD_RES_RAW)) { - File[] rawFiles = typeFolder.listFiles(); - if (rawFiles != null) { - for (File rawFile : rawFiles) { - checkFile(context, rawFile); - } - } - } - } - } - } - - List<File> assetFolders = project.getAssetFolders(); - for (File assetFolder : assetFolders) { - File[] assets = assetFolder.listFiles(); - if (assets != null) { - for (File asset : assets) { - checkFile(context, asset); - } - } - } - } - - private static void checkFile(@NonNull Context context, @NonNull File file) { - if (isNativeCode(file)) { - if (LintUtils.endsWith(file.getPath(), DOT_NATIVE_LIBS)) { - context.report(UNSAFE_NATIVE_CODE_LOCATION, Location.create(file), - "Shared libraries should not be placed in the res or assets " + - "directories. Please use the features of your development " + - "environment to place shared libraries in the lib directory of " + - "the compiled APK."); - } else { - context.report(UNSAFE_NATIVE_CODE_LOCATION, Location.create(file), - "Embedding non-shared library native executables into applications " + - "should be avoided when possible, as there is an increased risk that " + - "the executables could be tampered with after installation. Instead, " + - "native code should be placed in a shared library, and the features of " + - "the development environment should be used to place the shared library " + - "in the lib directory of the compiled APK."); - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewConstructorDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewConstructorDetector.java deleted file mode 100644 index a7b57c1..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewConstructorDetector.java +++ /dev/null
@@ -1,151 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.CLASS_ATTRIBUTE_SET; -import static com.android.SdkConstants.CLASS_CONTEXT; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.ClassContext; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Detector.JavaPsiScanner; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiAnonymousClass; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiParameterList; -import com.intellij.psi.PsiType; - -import org.jetbrains.uast.UClass; -import org.jetbrains.org.objectweb.asm.tree.ClassNode; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for custom views that do not define the view constructors needed by UI builders - */ -public class ViewConstructorDetector extends Detector implements Detector.UastScanner { - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "ViewConstructor", //$NON-NLS-1$ - "Missing View constructors for XML inflation", - - "Some layout tools (such as the Android layout editor) need to " + - "find a constructor with one of the following signatures:\n" + - "* `View(Context context)`\n" + - "* `View(Context context, AttributeSet attrs)`\n" + - "* `View(Context context, AttributeSet attrs, int defStyle)`\n" + - "\n" + - "If your custom view needs to perform initialization which does not apply when " + - "used in a layout editor, you can surround the given code with a check to " + - "see if `View#isInEditMode()` is false, since that method will return `false` " + - "at runtime but true within a user interface editor.", - - Category.USABILITY, - 3, - Severity.WARNING, - new Implementation( - ViewConstructorDetector.class, - Scope.JAVA_FILE_SCOPE)); - - /** Constructs a new {@link ViewConstructorDetector} check */ - public ViewConstructorDetector() { - } - - // ---- Implements JavaScanner ---- - - private static boolean isXmlConstructor( - @NonNull JavaEvaluator evaluator, - @NonNull PsiMethod method) { - // Accept - // android.content.Context - // android.content.Context,android.util.AttributeSet - // android.content.Context,android.util.AttributeSet,int - PsiParameterList parameterList = method.getParameterList(); - int argumentCount = parameterList.getParametersCount(); - if (argumentCount == 0 || argumentCount > 3) { - return false; - } - PsiParameter[] parameters = parameterList.getParameters(); - if (!evaluator.typeMatches(parameters[0].getType(), CLASS_CONTEXT)) { - return false; - } - if (argumentCount == 1) { - return true; - } - if (!evaluator.typeMatches(parameters[1].getType(), CLASS_ATTRIBUTE_SET)) { - return false; - } - //noinspection SimplifiableIfStatement - if (argumentCount == 2) { - return true; - } - return PsiType.INT.equals(parameters[2].getType()); - } - - @Nullable - @Override - public List<String> applicableSuperClasses() { - return Collections.singletonList(SdkConstants.CLASS_VIEW); - } - - @Override - public void checkClass(@NonNull JavaContext context, @NonNull UClass declaration) { - // Only applies to concrete classes - JavaEvaluator evaluator = context.getEvaluator(); - if (evaluator.isAbstract(declaration) || declaration instanceof PsiAnonymousClass) { - // Ignore abstract classes - return; - } - - if (declaration.getContainingClass() != null && - !evaluator.isStatic(declaration)) { - // Ignore inner classes that aren't static: we can't create these - // anyway since we'd need the outer instance - return; - } - - boolean found = false; - for (PsiMethod constructor : declaration.getConstructors()) { - if (isXmlConstructor(evaluator, constructor)) { - found = true; - break; - } - } - - if (!found) { - String message = String.format( - "Custom view `%1$s` is missing constructor used by tools: " - + "`(Context)` or `(Context,AttributeSet)` " - + "or `(Context,AttributeSet,int)`", - declaration.getName()); - Location location = context.getUastNameLocation(declaration); - context.reportUast(ISSUE, declaration, location, message /*data*/); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewHolderDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewHolderDetector.java deleted file mode 100644 index 94fc4986..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewHolderDetector.java +++ /dev/null
@@ -1,187 +0,0 @@ -/* - * 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 com.android.tools.klint.checks; - -import static com.android.SdkConstants.CLASS_VIEW; -import static com.android.SdkConstants.CLASS_VIEWGROUP; -import static com.android.tools.klint.client.api.JavaParser.TYPE_INT; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.google.common.collect.Lists; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UIfExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.USwitchExpression; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.util.UastExpressionUtils; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Looks for ListView scrolling performance: should use view holder pattern - */ -public class ViewHolderDetector extends Detector implements Detector.UastScanner { - - private static final Implementation IMPLEMENTATION = new Implementation( - ViewHolderDetector.class, - Scope.JAVA_FILE_SCOPE); - - /** Using a view inflater unconditionally in an AdapterView */ - public static final Issue ISSUE = Issue.create( - "ViewHolder", //$NON-NLS-1$ - "View Holder Candidates", - - "When implementing a view Adapter, you should avoid unconditionally inflating a " + - "new layout; if an available item is passed in for reuse, you should try to " + - "use that one instead. This helps make for example ListView scrolling much " + - "smoother.", - - Category.PERFORMANCE, - 5, - Severity.WARNING, - IMPLEMENTATION) - .addMoreInfo( - "http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder"); - - private static final String GET_VIEW = "getView"; //$NON-NLS-1$ - static final String INFLATE = "inflate"; //$NON-NLS-1$ - - /** Constructs a new {@link ViewHolderDetector} check */ - public ViewHolderDetector() { - } - - // ---- Implements JavaScanner ---- - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - return Collections.<Class<? extends UElement>>singletonList(UMethod.class); - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new ViewAdapterVisitor(context); - } - - private static class ViewAdapterVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - - public ViewAdapterVisitor(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitMethod(UMethod method) { - if (isViewAdapterMethod(mContext, method)) { - InflationVisitor visitor = new InflationVisitor(mContext); - method.accept(visitor); - visitor.finish(); - } - return super.visitMethod(method); - } - - /** - * Returns true if this method looks like it's overriding android.widget.Adapter's getView - * method: getView(int position, View convertView, ViewGroup parent) - */ - private static boolean isViewAdapterMethod(JavaContext context, PsiMethod node) { - JavaEvaluator evaluator = context.getEvaluator(); - return GET_VIEW.equals(node.getName()) && evaluator.parametersMatch(node, - TYPE_INT, CLASS_VIEW, CLASS_VIEWGROUP); - } - } - - private static class InflationVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - private List<UElement> mNodes; - private boolean mHaveConditional; - - public InflationVisitor(JavaContext context) { - mContext = context; - } - - @Override - public boolean visitCallExpression(UCallExpression node) { - if (UastExpressionUtils.isMethodCall(node)) { - checkMethodCall(node); - } - return super.visitCallExpression(node); - } - - private void checkMethodCall(UCallExpression node) { - UExpression receiver = node.getReceiver(); - if (receiver != null) { - String methodName = node.getMethodName(); - if (INFLATE.equals(methodName) - && node.getValueArgumentCount() >= 1) { - // See if we're inside a conditional - boolean insideIf = false; - //noinspection unchecked - if (UastUtils.getParentOfType(node, true, UIfExpression.class, - USwitchExpression.class) != null) { - insideIf = true; - mHaveConditional = true; - } - if (!insideIf) { - // Rather than reporting immediately, we only report if we didn't - // find any conditionally executed inflate statements in the method. - // This is because there are cases where getView method is complicated - // and inflates not just the top level layout but also children - // of the view, and we don't want to flag these. (To be more accurate - // should perform flow analysis and only report unconditional inflation - // of layouts that wind up flowing to the return value; that requires - // more work, and this simple heuristic is good enough for nearly all test - // cases I've come across. - if (mNodes == null) { - mNodes = Lists.newArrayList(); - } - mNodes.add(node); - } - } - } - } - - public void finish() { - if (!mHaveConditional && mNodes != null) { - for (UElement node : mNodes) { - String message = "Unconditional layout inflation from view adapter: " - + "Should use View Holder pattern (use recycled view passed " - + "into this method as the second parameter) for smoother " - + "scrolling"; - mContext.report(ISSUE, node, mContext.getUastLocation(node), message); - } - } - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTagDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTagDetector.java deleted file mode 100644 index 93ea1dd..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTagDetector.java +++ /dev/null
@@ -1,130 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.CLASS_VIEW; -import static com.android.tools.klint.checks.CleanupDetector.CURSOR_CLS; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TypeEvaluator; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiType; - -import com.intellij.psi.util.InheritanceUtil; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Checks for missing view tag detectors - */ -public class ViewTagDetector extends Detector implements Detector.UastScanner { - /** Using setTag and leaking memory */ - public static final Issue ISSUE = Issue.create( - "ViewTag", //$NON-NLS-1$ - "Tagged object leaks", - - "Prior to Android 4.0, the implementation of `View.setTag(int, Object)` would " + - "store the objects in a static map, where the values were strongly referenced. " + - "This means that if the object contains any references pointing back to the " + - "context, the context (which points to pretty much everything else) will leak. " + - "If you pass a view, the view provides a reference to the context " + - "that created it. Similarly, view holders typically contain a view, and cursors " + - "are sometimes also associated with views.", - - Category.PERFORMANCE, - 6, - Severity.WARNING, - new Implementation( - ViewTagDetector.class, - Scope.JAVA_FILE_SCOPE)); - - /** Constructs a new {@link ViewTagDetector} */ - public ViewTagDetector() { - } - - // ---- Implements JavaScanner ---- - - @Nullable - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("setTag"); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - // The leak behavior is fixed in ICS: - // http://code.google.com/p/android/issues/detail?id=18273 - if (context.getMainProject().getMinSdk() >= 14) { - return; - } - - JavaEvaluator evaluator = context.getEvaluator(); - if (!evaluator.isMemberInSubClassOf(method, CLASS_VIEW, false)) { - return; - } - - List<UExpression> arguments = call.getValueArguments(); - if (arguments.size() != 2) { - return; - } - UExpression tagArgument = arguments.get(1); - if (tagArgument == null) { - return; - } - - PsiType type = TypeEvaluator.evaluate(context, tagArgument); - if ((!(type instanceof PsiClassType))) { - return; - } - PsiClass typeClass = ((PsiClassType) type).resolve(); - if (typeClass == null) { - return; - } - - String objectType; - if (InheritanceUtil.isInheritor(typeClass, false, CLASS_VIEW)) { - objectType = "views"; - } else if (InheritanceUtil.isInheritor(typeClass, false, CURSOR_CLS)) { - objectType = "cursors"; - } else if (typeClass.getName() != null && typeClass.getName().endsWith("ViewHolder")) { - objectType = "view holders"; - } else { - return; - } - String message = String.format("Avoid setting %1$s as values for `setTag`: " + - "Can lead to memory leaks in versions older than Android 4.0", - objectType); - - context.report(ISSUE, call, context.getUastLocation(tagArgument), message); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTypeDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTypeDetector.java deleted file mode 100644 index c86be27..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTypeDetector.java +++ /dev/null
@@ -1,308 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceFile; -import com.android.ide.common.res2.ResourceItem; -import com.android.ide.common.resources.ResourceUrl; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.detector.api.*; -import com.android.utils.XmlUtils; -import com.google.common.base.Joiner; -import com.google.common.collect.*; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiType; -import org.jetbrains.uast.*; -import org.jetbrains.uast.visitor.UastVisitor; -import org.w3c.dom.*; - -import java.io.File; -import java.util.*; - -import static com.android.SdkConstants.*; - -/** Detector for finding inconsistent usage of views and casts - * <p> - * TODO: Check findFragmentById - * <pre> - * ((ItemListFragment) getSupportFragmentManager() - * .findFragmentById(R.id.item_list)) - * .setActivateOnItemClick(true); - * </pre> - * Here we should check the {@code <fragment>} tag pointed to by the id, and - * check its name or class attributes to make sure the cast is compatible with - * the named fragment class! - */ -public class ViewTypeDetector extends ResourceXmlDetector implements Detector.UastScanner { - /** Mismatched view types */ - @SuppressWarnings("unchecked") - public static final Issue ISSUE = Issue.create( - "WrongViewCast", //$NON-NLS-1$ - "Mismatched view type", - "Keeps track of the view types associated with ids and if it finds a usage of " + - "the id in the Java code it ensures that it is treated as the same type.", - Category.CORRECTNESS, - 9, - Severity.FATAL, - new Implementation( - ViewTypeDetector.class, - EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES), - Scope.JAVA_FILE_SCOPE)); - - /** Flag used to do no work if we're running in incremental mode in a .java file without - * a client supporting project resources */ - private Boolean mIgnore = null; - - private final Map<String, Object> mIdToViewTag = new HashMap<String, Object>(50); - - @NonNull - @Override - public Speed getSpeed() { - return Speed.SLOW; - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == ResourceFolderType.LAYOUT; - } - - @Override - public Collection<String> getApplicableAttributes() { - return Collections.singletonList(ATTR_ID); - } - - @Override - public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { - String view = attribute.getOwnerElement().getTagName(); - String value = attribute.getValue(); - String id = null; - if (value.startsWith(ID_PREFIX)) { - id = value.substring(ID_PREFIX.length()); - } else if (value.startsWith(NEW_ID_PREFIX)) { - id = value.substring(NEW_ID_PREFIX.length()); - } // else: could be @android id - - if (id != null) { - if (view.equals(VIEW_TAG)) { - view = attribute.getOwnerElement().getAttribute(ATTR_CLASS); - } - - Object existing = mIdToViewTag.get(id); - if (existing == null) { - mIdToViewTag.put(id, view); - } else if (existing instanceof String) { - String existingString = (String) existing; - if (!existingString.equals(view)) { - // Convert to list - List<String> list = new ArrayList<String>(2); - list.add((String) existing); - list.add(view); - mIdToViewTag.put(id, list); - } - } else if (existing instanceof List<?>) { - @SuppressWarnings("unchecked") - List<String> list = (List<String>) existing; - if (!list.contains(view)) { - list.add(view); - } - } - } - } - - // ---- Implements Detector.JavaScanner ---- - - @Override - public List<String> getApplicableMethodNames() { - return Collections.singletonList("findViewById"); //$NON-NLS-1$ - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression call, @NonNull UMethod method) { - LintClient client = context.getClient(); - if (mIgnore == Boolean.TRUE) { - return; - } else if (mIgnore == null) { - mIgnore = !context.getScope().contains(Scope.ALL_RESOURCE_FILES) && - !client.supportsProjectResources(); - if (mIgnore) { - return; - } - } - assert method.getName().equals("findViewById"); - UElement node = LintUtils.skipParentheses(call); - while (node != null && node.getUastParent() instanceof UParenthesizedExpression) { - node = node.getUastParent(); - } - if (node.getUastParent() instanceof UBinaryExpressionWithType) { - UBinaryExpressionWithType cast = (UBinaryExpressionWithType) node.getUastParent(); - PsiType type = cast.getType(); - String castType = null; - if (type instanceof PsiClassType) { - castType = type.getCanonicalText(); - } - if (castType == null) { - return; - } - - List<UExpression> args = call.getValueArguments(); - if (args.size() == 1) { - UExpression first = args.get(0); - ResourceUrl resourceUrl = ResourceEvaluator.getResource(context, first); - if (resourceUrl != null && resourceUrl.type == ResourceType.ID && - !resourceUrl.framework) { - String id = resourceUrl.name; - - if (client.supportsProjectResources()) { - AbstractResourceRepository resources = client - .getProjectResources(context.getMainProject(), true); - if (resources == null) { - return; - } - - List<ResourceItem> items = resources.getResourceItem(ResourceType.ID, - id); - if (items != null && !items.isEmpty()) { - Set<String> compatible = Sets.newHashSet(); - for (ResourceItem item : items) { - Collection<String> tags = getViewTags(context, item); - if (tags != null) { - compatible.addAll(tags); - } - } - if (!compatible.isEmpty()) { - ArrayList<String> layoutTypes = Lists.newArrayList(compatible); - checkCompatible(context, castType, null, layoutTypes, cast); - } - } - } else { - Object types = mIdToViewTag.get(id); - if (types instanceof String) { - String layoutType = (String) types; - checkCompatible(context, castType, layoutType, null, cast); - } else if (types instanceof List<?>) { - @SuppressWarnings("unchecked") - List<String> layoutTypes = (List<String>) types; - checkCompatible(context, castType, null, layoutTypes, cast); - } - } - } - } - } - } - - @Nullable - protected Collection<String> getViewTags( - @NonNull Context context, - @NonNull ResourceItem item) { - // Check view tag in this file. Can I do it cheaply? Try with - // an XML pull parser. Or DOM if we have multiple resources looked - // up? - ResourceFile source = item.getSource(); - if (source != null) { - File file = source.getFile(); - Multimap<String,String> map = getIdToTagsIn(context, file); - if (map != null) { - return map.get(item.getName()); - } - } - - return null; - } - - - private Map<File, Multimap<String, String>> mFileIdMap; - - @Nullable - private Multimap<String, String> getIdToTagsIn(@NonNull Context context, @NonNull File file) { - if (!file.getPath().endsWith(DOT_XML)) { - return null; - } - if (mFileIdMap == null) { - mFileIdMap = Maps.newHashMap(); - } - Multimap<String, String> map = mFileIdMap.get(file); - if (map == null) { - map = ArrayListMultimap.create(); - mFileIdMap.put(file, map); - - String xml = context.getClient().readFile(file); - // TODO: Use pull parser instead for better performance! - Document document = XmlUtils.parseDocumentSilently(xml, true); - if (document != null && document.getDocumentElement() != null) { - addViewTags(map, document.getDocumentElement()); - } - } - return map; - } - - private static void addViewTags(Multimap<String, String> map, Element element) { - String id = element.getAttributeNS(ANDROID_URI, ATTR_ID); - if (id != null && !id.isEmpty()) { - id = LintUtils.stripIdPrefix(id); - if (!map.containsEntry(id, element.getTagName())) { - map.put(id, element.getTagName()); - } - } - - NodeList children = element.getChildNodes(); - for (int i = 0, n = children.getLength(); i < n; i++) { - Node child = children.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - addViewTags(map, (Element) child); - } - } - } - - /** Check if the view and cast type are compatible */ - private static void checkCompatible(JavaContext context, String castType, String layoutType, - List<String> layoutTypes, UBinaryExpressionWithType node) { - assert layoutType == null || layoutTypes == null; // Should only specify one or the other - boolean compatible = true; - if (layoutType != null) { - if (!layoutType.equals(castType) - && !context.getSdkInfo().isSubViewOf(castType, layoutType)) { - compatible = false; - } - } else { - compatible = false; - assert layoutTypes != null; - for (String type : layoutTypes) { - if (type.equals(castType) - || context.getSdkInfo().isSubViewOf(castType, type)) { - compatible = true; - break; - } - } - } - - if (!compatible) { - if (layoutType == null) { - layoutType = Joiner.on("|").join(layoutTypes); - } - String message = String.format( - "Unexpected cast to `%1$s`: layout tag was `%2$s`", - castType.substring(castType.lastIndexOf('.') + 1), layoutType); - context.report(ISSUE, node, context.getUastLocation(node), message); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongCallDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongCallDetector.java deleted file mode 100644 index 65a2209..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongCallDetector.java +++ /dev/null
@@ -1,153 +0,0 @@ -/* - * Copyright (C) 2012 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.klint.checks; - -import static com.android.SdkConstants.CLASS_VIEW; -import static com.android.tools.klint.checks.JavaPerformanceDetector.ON_DRAW; -import static com.android.tools.klint.checks.JavaPerformanceDetector.ON_LAYOUT; -import static com.android.tools.klint.checks.JavaPerformanceDetector.ON_MEASURE; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.LintUtils; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.android.tools.klint.detector.api.TextFormat; -import com.intellij.psi.PsiMethod; - -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.USuperExpression; -import org.jetbrains.uast.UastUtils; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Arrays; -import java.util.List; - -/** - * Checks for cases where the wrong call is being made - */ -public class WrongCallDetector extends Detector implements Detector.UastScanner { - /** Calling the wrong method */ - public static final Issue ISSUE = Issue.create( - "WrongCall", //$NON-NLS-1$ - "Using wrong draw/layout method", - - "Custom views typically need to call `measure()` on their children, not `onMeasure`. " + - "Ditto for onDraw, onLayout, etc.", - - Category.CORRECTNESS, - 6, - Severity.FATAL, - new Implementation( - WrongCallDetector.class, - Scope.JAVA_FILE_SCOPE)); - - /** Constructs a new {@link WrongCallDetector} */ - public WrongCallDetector() { - } - - // ---- Implements UastScanner ---- - - @Override - @Nullable - public List<String> getApplicableMethodNames() { - return Arrays.asList( - ON_DRAW, - ON_MEASURE, - ON_LAYOUT - ); - } - - @Override - public void visitMethod(@NonNull JavaContext context, @Nullable UastVisitor visitor, - @NonNull UCallExpression node, @NonNull UMethod calledMethod) { - // Call is only allowed if it is both only called on the super class (invoke special) - // as well as within the same overriding method (e.g. you can't call super.onLayout - // from the onMeasure method) - UExpression operand = node.getReceiver(); - if (!(operand instanceof USuperExpression)) { - report(context, node, calledMethod); - return; - } - - PsiMethod method = UastUtils.getParentOfType(node, UMethod.class, true); - if (method != null) { - String callName = node.getMethodName(); - if (callName != null && !callName.equals(method.getName())) { - report(context, node, calledMethod); - } - } - } - - private static void report( - @NonNull JavaContext context, - @NonNull UCallExpression node, - @NonNull PsiMethod method) { - // Make sure the call is on a view - if (!context.getEvaluator().isMemberInSubClassOf(method, CLASS_VIEW, false)) { - return; - } - - String name = method.getName(); - String suggestion = Character.toLowerCase(name.charAt(2)) + name.substring(3); - String message = String.format( - // Keep in sync with {@link #getOldValue} and {@link #getNewValue} below! - "Suspicious method call; should probably call \"`%1$s`\" rather than \"`%2$s`\"", - suggestion, name); - context.report(ISSUE, node, context.getUastNameLocation(node), message); - } - - /** - * Given an error message produced by this lint detector for the given issue type, - * returns the old value to be replaced in the source code. - * <p> - * Intended for IDE quickfix implementations. - * - * @param errorMessage the error message associated with the error - * @param format the format of the error message - * @return the corresponding old value, or null if not recognized - */ - @Nullable - public static String getOldValue(@NonNull String errorMessage, @NonNull TextFormat format) { - errorMessage = format.toText(errorMessage); - return LintUtils.findSubstring(errorMessage, "than \"", "\""); - } - - /** - * Given an error message produced by this lint detector for the given issue type, - * returns the new value to be put into the source code. - * <p> - * Intended for IDE quickfix implementations. - * - * @param errorMessage the error message associated with the error - * @param format the format of the error message - * @return the corresponding new value, or null if not recognized - */ - @Nullable - public static String getNewValue(@NonNull String errorMessage, @NonNull TextFormat format) { - errorMessage = format.toText(errorMessage); - return LintUtils.findSubstring(errorMessage, "call \"", "\""); - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongImportDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongImportDetector.java deleted file mode 100644 index 6d3f85f..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongImportDetector.java +++ /dev/null
@@ -1,114 +0,0 @@ -/* - * Copyright (C) 2011 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.klint.checks; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.detector.api.Category; -import com.android.tools.klint.detector.api.Detector; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Scope; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; - -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UImportStatement; -import org.jetbrains.uast.visitor.AbstractUastVisitor; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.Collections; -import java.util.List; - -/** - * Checks for "import android.R", which seems to be a common source of confusion - * (see for example http://stackoverflow.com/questions/885009/r-cannot-be-resolved-android-error - * and many other forums). - * <p> - * The root cause is probably this (from http://source.android.com/source/using-eclipse.html) : - * <blockquote> Note: Eclipse sometimes likes to add an import android.R - * statement at the top of your files that use resources, especially when you - * ask eclipse to sort or otherwise manage imports. This will cause your make to - * break. Look out for these erroneous import statements and delete them. - * </blockquote> - */ -public class WrongImportDetector extends Detector implements Detector.UastScanner { - /** Is android.R being imported? */ - public static final Issue ISSUE = Issue.create( - "SuspiciousImport", //$NON-NLS-1$ - "'`import android.R`' statement", - "Importing `android.R` is usually not intentional; it sometimes happens when " + - "you use an IDE and ask it to automatically add imports at a time when your " + - "project's R class it not present.\n" + - "\n" + - "Once the import is there you might get a lot of \"confusing\" error messages " + - "because of course the fields available on `android.R` are not the ones you'd " + - "expect from just looking at your own `R` class.", - Category.CORRECTNESS, - 9, - Severity.WARNING, - new Implementation( - WrongImportDetector.class, - Scope.JAVA_FILE_SCOPE)); - - /** Constructs a new {@link WrongImportDetector} check */ - public WrongImportDetector() { - } - - // ---- Implements JavaScanner ---- - - - @Nullable - @Override - public List<Class<? extends UElement>> getApplicableUastTypes() { - return Collections.<Class<? extends UElement>>singletonList(UImportStatement.class); - } - - @Nullable - @Override - public UastVisitor createUastVisitor(@NonNull JavaContext context) { - return new ImportVisitor(context); - } - - private static class ImportVisitor extends AbstractUastVisitor { - private final JavaContext mContext; - - private ImportVisitor(JavaContext context) { - super(); - mContext = context; - } - - @Override - public boolean visitImportStatement(UImportStatement statement) { - PsiElement resolved = statement.resolve(); - if (resolved instanceof PsiClass) { - String qualifiedName = ((PsiClass) resolved).getQualifiedName(); - if ("android.R".equals(qualifiedName)) { - Location location = mContext.getUastLocation(statement); - mContext.report(ISSUE, statement, location, - "Don't include `android.R` here; use a fully qualified name for " - + "each usage instead"); - } - } - - return super.visitImportStatement(statement); - } - } -}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/api-versions-support-library.xml b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/api-versions-support-library.xml deleted file mode 100644 index faa8970..0000000 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/api-versions-support-library.xml +++ /dev/null
@@ -1,275 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2015 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. ---> -<!-- This file is generated by ApiLookupTest#generateSupportLibraryFile() --> -<api version="2"> - <class name="android/support/design/internal/ForegroundLinearLayout" since="7"> - <extends name="android/support/v7/widget/LinearLayoutCompat" /> - <method name="drawableHotspotChanged(FF)V" since="7" /> - <method name="getForeground()Landroid/graphics/drawable/Drawable;" since="7" /> - <method name="getForegroundGravity()I" since="7" /> - <method name="jumpDrawablesToCurrentState()V" since="7" /> - <method name="setForeground(Landroid/graphics/drawable/Drawable;)V" since="7" /> - <method name="setForegroundGravity(I)V" since="7" /> - </class> - <class name="android/support/design/widget/CoordinatorLayout" since="7"> - <extends name="android/view/ViewGroup" /> - <method name="getNestedScrollAxes()I" since="7" /> - <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="7" /> - <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="7" /> - <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="7" /> - <method name="onNestedScroll(Landroid/view/View;IIII)V" since="7" /> - <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="7" /> - <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="7" /> - <method name="onStopNestedScroll(Landroid/view/View;)V" since="7" /> - </class> - <class name="android/support/design/widget/FloatingActionButton" since="7"> - <extends name="android/widget/ImageButton" /> - <method name="getBackgroundTintList()Landroid/content/res/ColorStateList;" since="7" /> - <method name="getBackgroundTintMode()Landroid/graphics/PorterDuff$Mode;" since="7" /> - <method name="jumpDrawablesToCurrentState()V" since="7" /> - <method name="setBackgroundTintList(Landroid/content/res/ColorStateList;)V" since="7" /> - <method name="setBackgroundTintMode(Landroid/graphics/PorterDuff$Mode;)V" since="7" /> - </class> - <class name="android/support/design/widget/TextInputLayout" since="7"> - <extends name="android/widget/LinearLayout" /> - <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" since="7" /> - </class> - <class name="android/support/v17/leanback/transition/FadeAndShortSlide" since="17"> - <extends name="android/transition/Visibility" /> - <method name="<init>()V" since="17" /> - <method name="addListener(Landroid/transition/Transition$TransitionListener;)Landroid/transition/Transition;" since="17" /> - <method name="captureEndValues(Landroid/transition/TransitionValues;)V" since="17" /> - <method name="captureStartValues(Landroid/transition/TransitionValues;)V" since="17" /> - <method name="clone()Landroid/transition/Transition;" since="17" /> - <method name="clone()Ljava/lang/Object;" since="17" /> - <method name="onAppear(Landroid/view/ViewGroup;Landroid/view/View;Landroid/transition/TransitionValues;Landroid/transition/TransitionValues;)Landroid/animation/Animator;" since="17" /> - <method name="onDisappear(Landroid/view/ViewGroup;Landroid/view/View;Landroid/transition/TransitionValues;Landroid/transition/TransitionValues;)Landroid/animation/Animator;" since="17" /> - <method name="removeListener(Landroid/transition/Transition$TransitionListener;)Landroid/transition/Transition;" since="17" /> - <method name="setEpicenterCallback(Landroid/transition/Transition$EpicenterCallback;)V" since="17" /> - </class> - <class name="android/support/v17/leanback/transition/SlideNoPropagation" since="17"> - <extends name="android/transition/Slide" /> - <method name="<init>()V" since="17" /> - <method name="<init>(I)V" since="17" /> - <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;)V" since="17" /> - <method name="setSlideEdge(I)V" since="17" /> - </class> - <class name="android/support/v4/view/ViewPager" since="4"> - <extends name="android/view/ViewGroup" /> - <method name="canScrollHorizontally(I)Z" since="4" /> - </class> - <class name="android/support/v4/widget/DrawerLayout" since="4"> - <extends name="android/view/ViewGroup" /> - <method name="onRtlPropertiesChanged(I)V" since="4" /> - </class> - <class name="android/support/v4/widget/NestedScrollView" since="4"> - <extends name="android/widget/FrameLayout" /> - <method name="dispatchNestedFling(FFZ)Z" since="4" /> - <method name="dispatchNestedPreFling(FF)Z" since="4" /> - <method name="dispatchNestedPreScroll(II[I[I)Z" since="4" /> - <method name="dispatchNestedScroll(IIII[I)Z" since="4" /> - <method name="getNestedScrollAxes()I" since="4" /> - <method name="hasNestedScrollingParent()Z" since="4" /> - <method name="isNestedScrollingEnabled()Z" since="4" /> - <method name="onGenericMotionEvent(Landroid/view/MotionEvent;)Z" since="4" /> - <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="4" /> - <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="4" /> - <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="4" /> - <method name="onNestedScroll(Landroid/view/View;IIII)V" since="4" /> - <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="4" /> - <method name="onOverScrolled(IIZZ)V" since="4" /> - <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="4" /> - <method name="onStopNestedScroll(Landroid/view/View;)V" since="4" /> - <method name="setNestedScrollingEnabled(Z)V" since="4" /> - <method name="shouldDelayChildPressedState()Z" since="4" /> - <method name="startNestedScroll(I)Z" since="4" /> - <method name="stopNestedScroll()V" since="4" /> - </class> - <class name="android/support/v4/widget/SwipeRefreshLayout" since="4"> - <extends name="android/view/ViewGroup" /> - <method name="dispatchNestedFling(FFZ)Z" since="4" /> - <method name="dispatchNestedPreFling(FF)Z" since="4" /> - <method name="dispatchNestedPreScroll(II[I[I)Z" since="4" /> - <method name="dispatchNestedScroll(IIII[I)Z" since="4" /> - <method name="getNestedScrollAxes()I" since="4" /> - <method name="hasNestedScrollingParent()Z" since="4" /> - <method name="isNestedScrollingEnabled()Z" since="4" /> - <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="4" /> - <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="4" /> - <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="4" /> - <method name="onNestedScroll(Landroid/view/View;IIII)V" since="4" /> - <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="4" /> - <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="4" /> - <method name="onStopNestedScroll(Landroid/view/View;)V" since="4" /> - <method name="setNestedScrollingEnabled(Z)V" since="4" /> - <method name="startNestedScroll(I)Z" since="4" /> - <method name="stopNestedScroll()V" since="4" /> - </class> - <class name="android/support/v7/app/AppCompatDialog" since="7"> - <extends name="android/app/Dialog" /> - <method name="invalidateOptionsMenu()V" since="7" /> - </class> - <class name="android/support/v7/app/MediaRouteButton" since="7"> - <extends name="android/view/View" /> - <method name="jumpDrawablesToCurrentState()V" since="7" /> - </class> - <class name="android/support/v7/graphics/drawable/DrawableWrapper" since="7"> - <extends name="android/graphics/drawable/Drawable" /> - <method name="isAutoMirrored()Z" since="7" /> - <method name="jumpToCurrentState()V" since="7" /> - <method name="setAutoMirrored(Z)V" since="7" /> - <method name="setHotspot(FF)V" since="7" /> - <method name="setHotspotBounds(IIII)V" since="7" /> - <method name="setTint(I)V" since="7" /> - <method name="setTintList(Landroid/content/res/ColorStateList;)V" since="7" /> - <method name="setTintMode(Landroid/graphics/PorterDuff$Mode;)V" since="7" /> - </class> - <class name="android/support/v7/internal/widget/PreferenceImageView" since="7"> - <extends name="android/widget/ImageView" /> - <method name="getMaxHeight()I" since="7" /> - <method name="getMaxWidth()I" since="7" /> - </class> - <class name="android/support/v7/view/SupportActionModeWrapper" since="7"> - <extends name="android/view/ActionMode" /> - <method name="finish()V" since="7" /> - <method name="getCustomView()Landroid/view/View;" since="7" /> - <method name="getMenu()Landroid/view/Menu;" since="7" /> - <method name="getMenuInflater()Landroid/view/MenuInflater;" since="7" /> - <method name="getSubtitle()Ljava/lang/CharSequence;" since="7" /> - <method name="getTag()Ljava/lang/Object;" since="7" /> - <method name="getTitle()Ljava/lang/CharSequence;" since="7" /> - <method name="getTitleOptionalHint()Z" since="7" /> - <method name="invalidate()V" since="7" /> - <method name="isTitleOptional()Z" since="7" /> - <method name="setCustomView(Landroid/view/View;)V" since="7" /> - <method name="setSubtitle(I)V" since="7" /> - <method name="setSubtitle(Ljava/lang/CharSequence;)V" since="7" /> - <method name="setTag(Ljava/lang/Object;)V" since="7" /> - <method name="setTitle(I)V" since="7" /> - <method name="setTitle(Ljava/lang/CharSequence;)V" since="7" /> - <method name="setTitleOptionalHint(Z)V" since="7" /> - </class> - <class name="android/support/v7/view/menu/ActionMenuItemView" since="7"> - <extends name="android/support/v7/widget/AppCompatTextView" /> - <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" /> - </class> - <class name="android/support/v7/view/menu/ListMenuItemView" since="7"> - <extends name="android/widget/LinearLayout" /> - <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" since="7" /> - </class> - <class name="android/support/v7/widget/ActionBarContainer" since="7"> - <extends name="android/widget/FrameLayout" /> - <method name="jumpDrawablesToCurrentState()V" since="7" /> - <method name="startActionModeForChild(Landroid/view/View;Landroid/view/ActionMode$Callback;)Landroid/view/ActionMode;" since="7" /> - </class> - <class name="android/support/v7/widget/ActionBarContextView" since="7"> - <extends name="android/support/v7/widget/AbsActionBarView" /> - <method name="onHoverEvent(Landroid/view/MotionEvent;)Z" since="7" /> - <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" /> - <method name="shouldDelayChildPressedState()Z" since="7" /> - </class> - <class name="android/support/v7/widget/ActionBarOverlayLayout" since="7"> - <extends name="android/view/ViewGroup" /> - <method name="getNestedScrollAxes()I" since="7" /> - <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" /> - <method name="onNestedFling(Landroid/view/View;FFZ)Z" since="7" /> - <method name="onNestedPreFling(Landroid/view/View;FF)Z" since="7" /> - <method name="onNestedPreScroll(Landroid/view/View;II[I)V" since="7" /> - <method name="onNestedScroll(Landroid/view/View;IIII)V" since="7" /> - <method name="onNestedScrollAccepted(Landroid/view/View;Landroid/view/View;I)V" since="7" /> - <method name="onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z" since="7" /> - <method name="onStopNestedScroll(Landroid/view/View;)V" since="7" /> - <method name="onWindowSystemUiVisibilityChanged(I)V" since="7" /> - <method name="shouldDelayChildPressedState()Z" since="7" /> - </class> - <class name="android/support/v7/widget/ActionMenuView" since="7"> - <extends name="android/support/v7/widget/LinearLayoutCompat" /> - <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" /> - </class> - <class name="android/support/v7/widget/AppCompatButton" since="7"> - <extends name="android/widget/Button" /> - <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" /> - <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" /> - </class> - <class name="android/support/v7/widget/AppCompatPopupWindow" since="7"> - <extends name="android/widget/PopupWindow" /> - <method name="showAsDropDown(Landroid/view/View;III)V" since="7" /> - </class> - <class name="android/support/v7/widget/AppCompatSpinner" since="7"> - <extends name="android/widget/Spinner" /> - <method name="<init>(Landroid/content/Context;I)V" since="7" /> - <method name="<init>(Landroid/content/Context;Landroid/util/AttributeSet;II)V" since="7" /> - <method name="getDropDownHorizontalOffset()I" since="7" /> - <method name="getDropDownVerticalOffset()I" since="7" /> - <method name="getDropDownWidth()I" since="7" /> - <method name="getPopupBackground()Landroid/graphics/drawable/Drawable;" since="7" /> - <method name="getPopupContext()Landroid/content/Context;" since="7" /> - <method name="setDropDownHorizontalOffset(I)V" since="7" /> - <method name="setDropDownVerticalOffset(I)V" since="7" /> - <method name="setDropDownWidth(I)V" since="7" /> - <method name="setPopupBackgroundDrawable(Landroid/graphics/drawable/Drawable;)V" since="7" /> - <method name="setPopupBackgroundResource(I)V" since="7" /> - </class> - <class name="android/support/v7/widget/CardView" since="7"> - <extends name="android/widget/FrameLayout" /> - <method name="setPaddingRelative(IIII)V" since="7" /> - </class> - <class name="android/support/v7/widget/LinearLayoutCompat" since="7"> - <extends name="android/view/ViewGroup" /> - <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" /> - <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" /> - <method name="shouldDelayChildPressedState()Z" since="7" /> - </class> - <class name="android/support/v7/widget/RecyclerView" since="7"> - <extends name="android/view/ViewGroup" /> - <method name="dispatchNestedFling(FFZ)Z" since="7" /> - <method name="dispatchNestedPreFling(FF)Z" since="7" /> - <method name="dispatchNestedPreScroll(II[I[I)Z" since="7" /> - <method name="dispatchNestedScroll(IIII[I)Z" since="7" /> - <method name="hasNestedScrollingParent()Z" since="7" /> - <method name="isAttachedToWindow()Z" since="7" /> - <method name="isNestedScrollingEnabled()Z" since="7" /> - <method name="onGenericMotionEvent(Landroid/view/MotionEvent;)Z" since="7" /> - <method name="setNestedScrollingEnabled(Z)V" since="7" /> - <method name="startNestedScroll(I)Z" since="7" /> - <method name="stopNestedScroll()V" since="7" /> - </class> - <class name="android/support/v7/widget/ScrollingTabContainerView" since="7"> - <extends name="android/widget/HorizontalScrollView" /> - <method name="onConfigurationChanged(Landroid/content/res/Configuration;)V" since="7" /> - </class> - <class name="android/support/v7/widget/SwitchCompat" since="7"> - <extends name="android/widget/CompoundButton" /> - <method name="drawableHotspotChanged(FF)V" since="7" /> - <method name="jumpDrawablesToCurrentState()V" since="7" /> - <method name="onInitializeAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" /> - <method name="onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V" since="7" /> - <method name="onPopulateAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="7" /> - </class> - <class name="android/support/v7/widget/Toolbar" since="7"> - <extends name="android/view/ViewGroup" /> - <method name="onHoverEvent(Landroid/view/MotionEvent;)Z" since="7" /> - <method name="onRtlPropertiesChanged(I)V" since="7" /> - </class> - <!-- Referenced Super Classes --> - <class name="android/support/v7/widget/AbsActionBarView" since="7"> - <extends name="android/view/ViewGroup" /> - </class> - <class name="android/support/v7/widget/AppCompatTextView" since="7"> - <extends name="android/widget/TextView" /> - </class> -</api> \ No newline at end of file
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt deleted file mode 100644 index 5cf05ff6..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt +++ /dev/null
@@ -1,93 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * 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.klint - -import com.android.SdkConstants -import com.android.tools.klint.checks.ApiDetector.REQUIRES_API_ANNOTATION -import com.intellij.codeInsight.FileModificationService -import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.android.util.AndroidBundle -import org.jetbrains.kotlin.android.hasBackingField -import org.jetbrains.kotlin.idea.util.addAnnotation -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.* - - -class AddTargetApiQuickFix( - val api: Int, - val useRequiresApi: Boolean -) : AndroidLintQuickFix { - - private companion object { - val FQNAME_TARGET_API = FqName(SdkConstants.FQCN_TARGET_API) - val FQNAME_REQUIRES_API = FqName(REQUIRES_API_ANNOTATION) - } - - override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean = - getAnnotationContainer(startElement, useRequiresApi) != null - - override fun getName(): String = getAnnotationValue(false).let { - if (useRequiresApi) { - // Not Available in Android plugin 2.0 - // AndroidBundle.message("android.lint.fix.add.requires.api", it) - "Add @RequiresApi($it) Annotation" - } else { - AndroidBundle.message("android.lint.fix.add.target.api", it) - } - } - - override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) { - val annotationContainer = getAnnotationContainer(startElement, useRequiresApi) ?: return - if (!FileModificationService.getInstance().preparePsiElementForWrite(annotationContainer)) { - return - } - - if (annotationContainer is KtModifierListOwner) { - annotationContainer.addAnnotation( - if (useRequiresApi) FQNAME_REQUIRES_API else FQNAME_TARGET_API, - getAnnotationValue(true), - whiteSpaceText = if (annotationContainer.isNewLineNeededForAnnotation()) "\n" else " ") - } - } - - private fun KtElement.isNewLineNeededForAnnotation() = !(this is KtParameter || this is KtTypeParameter || this is KtPropertyAccessor) - - private fun getAnnotationValue(fullyQualified: Boolean) = getVersionField(api, fullyQualified) - - private fun getAnnotationContainer(element: PsiElement, useRequiresApi: Boolean): PsiElement? { - return PsiTreeUtil.findFirstParent(element) { - if (useRequiresApi) - it.isRequiresApiAnnotationValidTarget() - else - it.isTargetApiAnnotationValidTarget() - } - } - - private fun PsiElement.isRequiresApiAnnotationValidTarget(): Boolean { - return this is KtClassOrObject || - (this is KtFunction && this !is KtFunctionLiteral) || - (this is KtProperty && !isLocal && hasBackingField()) || - this is KtPropertyAccessor - } - - private fun PsiElement.isTargetApiAnnotationValidTarget(): Boolean { - return this is KtClassOrObject || - (this is KtFunction && this !is KtFunctionLiteral) || - this is KtPropertyAccessor - } -} \ No newline at end of file
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt deleted file mode 100644 index 347a402..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt +++ /dev/null
@@ -1,92 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * 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.klint - -import com.intellij.codeInsight.FileModificationService -import com.intellij.psi.PsiDocumentManager -import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.codeInsight.surroundWith.statement.KotlinIfSurrounder -import org.jetbrains.kotlin.idea.core.ShortenReferences -import org.jetbrains.kotlin.idea.inspections.findExistingEditor -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode - - -class AddTargetVersionCheckQuickFix(val api: Int) : AndroidLintQuickFix { - - override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) { - val targetExpression = getTargetExpression(startElement) - val project = targetExpression?.project ?: return - val editor = targetExpression.findExistingEditor() ?: return - - val file = targetExpression.containingFile - val documentManager = PsiDocumentManager.getInstance(project) - val document = documentManager.getDocument(file) ?: return - - if (!FileModificationService.getInstance().prepareFileForWrite(file)) { - return - } - - val surrounder = getSurrounder(targetExpression, "\"VERSION.SDK_INT < ${getVersionField(api, false)}\"") - val conditionRange = surrounder.surroundElements(project, editor, arrayOf(targetExpression)) ?: return - val conditionText = "android.os.Build.VERSION.SDK_INT >= ${getVersionField(api, true)}" - document.replaceString(conditionRange.startOffset, conditionRange.endOffset, conditionText) - documentManager.commitDocument(document) - - ShortenReferences.DEFAULT.process(documentManager.getPsiFile(document) as KtFile, - conditionRange.startOffset, - conditionRange.startOffset + conditionText.length) - } - - override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean = - getTargetExpression(startElement) != null - - override fun getName(): String = "Surround with if (VERSION.SDK_INT >= VERSION_CODES.${getVersionField(api, false)}) { ... }" - - private fun getTargetExpression(element: PsiElement): KtElement? { - var current = PsiTreeUtil.getParentOfType(element, KtExpression::class.java) - while (current != null) { - if (current.parent is KtBlockExpression || - current.parent is KtContainerNode || - current.parent is KtWhenEntry || - current.parent is KtFunction || - current.parent is KtPropertyAccessor || - current.parent is KtProperty || - current.parent is KtReturnExpression || - current.parent is KtDestructuringDeclaration) { - break - } - current = PsiTreeUtil.getParentOfType(current, KtExpression::class.java, true) - } - - return current - } - - private fun getSurrounder(element: KtElement, todoText: String?): KotlinIfSurrounder { - val used = element.analyze(BodyResolveMode.PARTIAL)[BindingContext.USED_AS_EXPRESSION, element] ?: false - return if (used) { - object : KotlinIfSurrounder() { - override fun getCodeTemplate(): String = "if (a) { \n} else {\nTODO(${todoText ?: ""})\n}" - } - } else { - KotlinIfSurrounder() - } - } -} \ No newline at end of file
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidInspectionExtensionsFactory.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidInspectionExtensionsFactory.java deleted file mode 100644 index f7da140..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidInspectionExtensionsFactory.java +++ /dev/null
@@ -1,46 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.intellij.codeInspection.HTMLComposer; -import com.intellij.codeInspection.lang.GlobalInspectionContextExtension; -import com.intellij.codeInspection.lang.HTMLComposerExtension; -import com.intellij.codeInspection.lang.InspectionExtensionsFactory; -import com.intellij.codeInspection.lang.RefManagerExtension; -import com.intellij.codeInspection.reference.RefManager; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class AndroidInspectionExtensionsFactory extends InspectionExtensionsFactory { - @Override - public GlobalInspectionContextExtension createGlobalInspectionContextExtension() { - return new AndroidLintGlobalInspectionContext(); - } - - @Nullable - @Override - public RefManagerExtension createRefManagerExtension(RefManager refManager) { - return null; - } - - @Nullable - @Override - public HTMLComposerExtension createHTMLComposerExtension(HTMLComposer composer) { - return null; - } - - @Override - public boolean isToCheckMember(@NotNull PsiElement element, @NotNull String id) { - return true; - } - - @Override - public String getSuppressedInspectionIdsIn(@NotNull PsiElement element) { - return null; - } - - @Override - public boolean isProjectConfiguredToRunInspections(@NotNull Project project, boolean online) { - return true; - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintExternalAnnotator.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintExternalAnnotator.java deleted file mode 100644 index d1ebf96..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintExternalAnnotator.java +++ /dev/null
@@ -1,443 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.tools.klint.client.api.IssueRegistry; -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.client.api.LintRequest; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Scope; -import com.intellij.codeHighlighting.HighlightDisplayLevel; -import com.intellij.codeInsight.FileModificationService; -import com.intellij.codeInsight.daemon.DaemonBundle; -import com.intellij.codeInsight.daemon.HighlightDisplayKey; -import com.intellij.codeInsight.intention.HighPriorityAction; -import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.codeInspection.*; -import com.intellij.codeInspection.ex.CustomEditInspectionToolsSettingsAction; -import com.intellij.codeInspection.ex.DisableInspectionToolAction; -import com.intellij.lang.annotation.Annotation; -import com.intellij.lang.annotation.AnnotationHolder; -import com.intellij.lang.annotation.ExternalAnnotator; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypes; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.module.ModuleUtilCore; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.ProjectRootModificationTracker; -import com.intellij.openapi.util.*; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.profile.codeInspection.InspectionProjectProfileManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.psi.util.CachedValuesManager; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.ui.UIUtil; -import com.intellij.xml.util.XmlStringUtil; -import org.jetbrains.android.compiler.AndroidCompileUtil; -import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.util.AndroidBundle; -import org.jetbrains.android.util.AndroidCommonUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.idea.KotlinFileType; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; - -import static com.android.SdkConstants.*; -import static com.android.tools.klint.detector.api.TextFormat.HTML; -import static com.android.tools.klint.detector.api.TextFormat.RAW; - -public class AndroidLintExternalAnnotator extends ExternalAnnotator<State, State> { - static final boolean INCLUDE_IDEA_SUPPRESS_ACTIONS = false; - static final boolean IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode(); - - @Nullable - @Override - public State collectInformation(@NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors) { - return collectInformation(file); - } - - @Override - public State collectInformation(@NotNull PsiFile file) { - final Module module = ModuleUtilCore.findModuleForPsiElement(file); - if (module == null) { - return null; - } - - final AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet == null && !isDependencyOfAnyAndroidModule(module)) { - return null; - } - - final VirtualFile vFile = file.getVirtualFile(); - if (vFile == null) { - return null; - } - - final FileType fileType = file.getFileType(); - - if (fileType == FileTypes.PLAIN_TEXT) { - if (!AndroidCommonUtils.PROGUARD_CFG_FILE_NAME.equals(file.getName()) && - !AndroidCompileUtil.OLD_PROGUARD_CFG_FILE_NAME.equals(file.getName())) { - return null; - } - } - else if (fileType != KotlinFileType.INSTANCE && fileType != StdFileTypes.PROPERTIES) { - return null; - } - - final List<Issue> issues = getIssuesFromInspections(file.getProject(), file); - if (issues.size() == 0) { - return null; - } - return new State(module, vFile, file.getText(), issues); - } - - private static boolean isDependencyOfAnyAndroidModule(@NotNull Module module) { - return CachedValuesManager - .getManager(module.getProject()) - .getCachedValue(module, - () -> new CachedValueProvider.Result<>( - computeIsDependencyOfAnyAndroidModule(module), - ProjectRootModificationTracker.getInstance(module.getProject()))); - } - - private static boolean computeIsDependencyOfAnyAndroidModule(@NotNull Module dependency) { - Module[] allModules = ModuleManager.getInstance(dependency.getProject()).getModules(); - for (Module module : allModules) { - if (AndroidFacet.getInstance(module) == null) { - continue; - } - - if (ModuleRootManager.getInstance(module).isDependsOn(dependency)) { - return true; - } - } - - return false; - } - - @Override - public State doAnnotate(final State state) { - final IntellijLintClient client = IntellijLintClient.forEditor(state); - try { - final LintDriver lint = new LintDriver(new IntellijLintIssueRegistry(), client); - - EnumSet<Scope> scope; - VirtualFile mainFile = state.getMainFile(); - final FileType fileType = mainFile.getFileType(); - String name = mainFile.getName(); - if (fileType == StdFileTypes.XML) { - if (name.equals(ANDROID_MANIFEST_XML)) { - scope = Scope.MANIFEST_SCOPE; - } else { - scope = Scope.RESOURCE_FILE_SCOPE; - } - } else if (fileType == KotlinFileType.INSTANCE) { - scope = Scope.JAVA_FILE_SCOPE; - } else if (name.equals(OLD_PROGUARD_FILE) || name.equals(FN_PROJECT_PROGUARD_FILE)) { - scope = EnumSet.of(Scope.PROGUARD_FILE); - } else if (fileType == StdFileTypes.PROPERTIES) { - scope = Scope.PROPERTY_SCOPE; - } else { - // #collectionInformation above should have prevented this - assert false; - return state; - } - - Project project = state.getModule().getProject(); - if (project.isDisposed()) { - return state; - } - - List<VirtualFile> files = Collections.singletonList(mainFile); - final LintRequest request = new IntellijLintRequest( - client, project, files, Collections.singletonList(state.getModule()), true /* incremental */); - request.setScope(scope); - - if (!IS_UNIT_TEST_MODE) { - ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(new Runnable() { - @Override - public void run() { - lint.analyze(request); - } - }); - } - else { - lint.analyze(request); - } - } - finally { - Disposer.dispose(client); - } - return state; - } - - @NotNull - static List<Issue> getIssuesFromInspections(@NotNull Project project, @Nullable PsiElement context) { - final List<Issue> result = new ArrayList<Issue>(); - final IssueRegistry fullRegistry = new IntellijLintIssueRegistry(); - - for (Issue issue : fullRegistry.getIssues()) { - final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(project, issue); - if (inspectionShortName == null) { - continue; - } - - final HighlightDisplayKey key = HighlightDisplayKey.find(inspectionShortName); - if (key == null) { - continue; - } - - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - final boolean enabled = context != null ? profile.isToolEnabled(key, context) : profile.isToolEnabled(key); - - if (!enabled) { - continue; - } else if (!issue.isEnabledByDefault()) { - // If an issue is marked as not enabled by default, lint won't run it, even if it's in the set - // of issues provided by an issue registry. Since in the IDE we're enforcing the enabled-state via - // inspection profiles, mark the issue as enabled to allow users to turn on a lint check directly - // via the inspections UI. - issue.setEnabledByDefault(true); - } - result.add(issue); - } - return result; - } - - @Override - public void apply(@NotNull PsiFile file, State state, @NotNull AnnotationHolder holder) { - if (state.isDirty()) { - return; - } - final Project project = file.getProject(); - - for (ProblemData problemData : state.getProblems()) { - final Issue issue = problemData.getIssue(); - final String message = problemData.getMessage(); - final TextRange range = problemData.getTextRange(); - - if (range.getStartOffset() == range.getEndOffset()) { - continue; - } - - final Pair<AndroidLintInspectionBase, HighlightDisplayLevel> pair = - AndroidLintUtil.getHighlighLevelAndInspection(project, issue, file); - if (pair == null) { - continue; - } - final AndroidLintInspectionBase inspection = pair.getFirst(); - HighlightDisplayLevel displayLevel = pair.getSecond(); - - if (inspection != null) { - final HighlightDisplayKey key = HighlightDisplayKey.find(inspection.getShortName()); - - if (key != null) { - final PsiElement startElement = file.findElementAt(range.getStartOffset()); - final PsiElement endElement = file.findElementAt(range.getEndOffset() - 1); - - if (startElement != null && endElement != null && !inspection.isSuppressedFor(startElement)) { - if (problemData.getConfiguredSeverity() != null) { - HighlightDisplayLevel configuredLevel = - AndroidLintInspectionBase.toHighlightDisplayLevel(problemData.getConfiguredSeverity()); - if (configuredLevel != null) { - displayLevel = configuredLevel; - } - } - final Annotation annotation = createAnnotation(holder, message, range, displayLevel, issue); - - for (AndroidLintQuickFix fix : inspection.getQuickFixes(startElement, endElement, message)) { - if (fix.isApplicable(startElement, endElement, AndroidQuickfixContexts.EditorContext.TYPE)) { - annotation.registerFix(new MyFixingIntention(fix, startElement, endElement)); - } - } - - for (IntentionAction intention : inspection.getIntentions(startElement, endElement)) { - annotation.registerFix(intention); - } - - String id = key.getID(); - if (IntellijLintIssueRegistry.CUSTOM_ERROR == issue - || IntellijLintIssueRegistry.CUSTOM_WARNING == issue) { - Issue original = IntellijLintClient.findCustomIssue(message); - if (original != null) { - id = original.getId(); - } - } - - annotation.registerFix(new SuppressLintIntentionAction(id, startElement)); - annotation.registerFix(new MyDisableInspectionFix(key)); - annotation.registerFix(new MyEditInspectionToolsSettingsAction(key, inspection)); - - if (INCLUDE_IDEA_SUPPRESS_ACTIONS) { - final SuppressQuickFix[] suppressActions = inspection.getBatchSuppressActions(startElement); - for (SuppressQuickFix action : suppressActions) { - if (action.isAvailable(project, startElement)) { - ProblemHighlightType type = annotation.getHighlightType(); - annotation.registerFix(action, null, key, InspectionManager.getInstance(project).createProblemDescriptor( - startElement, endElement, message, type, true, LocalQuickFix.EMPTY_ARRAY)); - } - } - } - } - } - } - } - } - - @SuppressWarnings("deprecation") - @NotNull - private Annotation createAnnotation(@NotNull AnnotationHolder holder, - @NotNull String message, - @NotNull TextRange range, - @NotNull HighlightDisplayLevel displayLevel, - @NotNull Issue issue) { - // Convert from inspection severity to annotation severity - HighlightSeverity severity; - if (displayLevel == HighlightDisplayLevel.ERROR) { - severity = HighlightSeverity.ERROR; - } else if (displayLevel == HighlightDisplayLevel.WARNING) { - severity = HighlightSeverity.WARNING; - } else if (displayLevel == HighlightDisplayLevel.WEAK_WARNING) { - severity = HighlightSeverity.WEAK_WARNING; - } else if (displayLevel == HighlightDisplayLevel.INFO) { - severity = HighlightSeverity.INFO; - } else { - severity = HighlightSeverity.WARNING; - } - - String link = " <a " - +"href=\"#lint/" + issue.getId() + "\"" - + (UIUtil.isUnderDarcula() ? " color=\"7AB4C9\" " : "") - +">" + DaemonBundle.message("inspection.extended.description") - +"</a> " + getShowMoreShortCut(); - String tooltip = XmlStringUtil.wrapInHtml(RAW.convertTo(message, HTML) + link); - - return holder.createAnnotation(severity, range, message, tooltip); - } - - // Based on similar code in the LocalInspectionsPass constructor - private String myShortcutText; - private String getShowMoreShortCut() { - if (myShortcutText == null) { - final KeymapManager keymapManager = KeymapManager.getInstance(); - if (keymapManager != null) { - final Keymap keymap = keymapManager.getActiveKeymap(); - myShortcutText = - keymap == null ? "" : "(" + KeymapUtil.getShortcutsText(keymap.getShortcuts(IdeActions.ACTION_SHOW_ERROR_DESCRIPTION)) + ")"; - } - else { - myShortcutText = ""; - } - } - - return myShortcutText; - } - - private static class MyDisableInspectionFix implements IntentionAction, Iconable { - private final DisableInspectionToolAction myDisableInspectionToolAction; - - private MyDisableInspectionFix(@NotNull HighlightDisplayKey key) { - myDisableInspectionToolAction = new DisableInspectionToolAction(key); - } - - @NotNull - @Override - public String getText() { - return "Disable inspection"; - } - - @NotNull - @Override - public String getFamilyName() { - return getText(); - } - - @Override - public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return true; - } - - @Override - public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - myDisableInspectionToolAction.invoke(project, editor, file); - } - - @Override - public boolean startInWriteAction() { - return myDisableInspectionToolAction.startInWriteAction(); - } - - @Override - public Icon getIcon(@IconFlags int flags) { - return myDisableInspectionToolAction.getIcon(flags); - } - } - - public static class MyFixingIntention implements IntentionAction, HighPriorityAction { - private final AndroidLintQuickFix myQuickFix; - private final PsiElement myStartElement; - private final PsiElement myEndElement; - - public MyFixingIntention(@NotNull AndroidLintQuickFix quickFix, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { - myQuickFix = quickFix; - myStartElement = startElement; - myEndElement = endElement; - } - - @NotNull - @Override - public String getText() { - return myQuickFix.getName(); - } - - @NotNull - @Override - public String getFamilyName() { - return AndroidBundle.message("android.lint.quickfixes.family"); - } - - @Override - public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return true; - } - - @Override - public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - FileModificationService.getInstance().prepareFileForWrite(file); - myQuickFix.apply(myStartElement, myEndElement, AndroidQuickfixContexts.EditorContext.getInstance(editor)); - } - - @Override - public boolean startInWriteAction() { - return true; - } - } - - private static class MyEditInspectionToolsSettingsAction extends CustomEditInspectionToolsSettingsAction { - private MyEditInspectionToolsSettingsAction(@NotNull HighlightDisplayKey key, @NotNull final AndroidLintInspectionBase inspection) { - super(key, new Computable<String>() { - @Override - public String compute() { - return "Edit '" + inspection.getDisplayName() + "' inspection settings"; - } - }); - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintGlobalInspectionContext.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintGlobalInspectionContext.java deleted file mode 100644 index e213e54..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintGlobalInspectionContext.java +++ /dev/null
@@ -1,216 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.tools.klint.client.api.LintDriver; -import com.android.tools.klint.client.api.LintRequest; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Scope; -import com.google.common.collect.Lists; -import com.intellij.analysis.AnalysisScope; -import com.intellij.codeInspection.GlobalInspectionContext; -import com.intellij.codeInspection.ex.InspectionToolWrapper; -import com.intellij.codeInspection.ex.Tools; -import com.intellij.codeInspection.lang.GlobalInspectionContextExtension; -import com.intellij.facet.ProjectFacetManager; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.module.ModuleUtilCore; -import com.intellij.openapi.module.impl.scopes.ModuleWithDependenciesScope; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.util.ProgressWrapper; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiFile; -import com.intellij.psi.search.LocalSearchScope; -import com.intellij.psi.search.SearchScope; -import com.intellij.util.containers.HashMap; -import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; - -class AndroidLintGlobalInspectionContext implements GlobalInspectionContextExtension<AndroidLintGlobalInspectionContext> { - static final Key<AndroidLintGlobalInspectionContext> ID = Key.create("AndroidLintGlobalInspectionContext"); - private Map<Issue, Map<File, List<ProblemData>>> myResults; - - @NotNull - @Override - public Key<AndroidLintGlobalInspectionContext> getID() { - return ID; - } - - @Override - public void performPreRunActivities(@NotNull List<Tools> globalTools, @NotNull List<Tools> localTools, @NotNull final GlobalInspectionContext context) { - final Project project = context.getProject(); - - if (!ProjectFacetManager.getInstance(project).hasFacets(AndroidFacet.ID)) { - return; - } - - final List<Issue> issues = AndroidLintExternalAnnotator.getIssuesFromInspections(project, null); - if (issues.size() == 0) { - return; - } - - final Map<Issue, Map<File, List<ProblemData>>> problemMap = new HashMap<Issue, Map<File, List<ProblemData>>>(); - final AnalysisScope scope = context.getRefManager().getScope(); - if (scope == null) { - return; - } - - final IntellijLintClient client = IntellijLintClient.forBatch(project, problemMap, scope, issues); - final LintDriver lint = new LintDriver(new IntellijLintIssueRegistry(), client); - - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - ProgressWrapper.unwrap(indicator).setText("Running Android Lint"); - } - - EnumSet<Scope> lintScope; - //noinspection ConstantConditions - if (!IntellijLintProject.SUPPORT_CLASS_FILES) { - lintScope = EnumSet.copyOf(Scope.ALL); - // Can't run class file based checks - lintScope.remove(Scope.CLASS_FILE); - lintScope.remove(Scope.ALL_CLASS_FILES); - lintScope.remove(Scope.JAVA_LIBRARIES); - } else { - lintScope = Scope.ALL; - } - - List<VirtualFile> files = null; - final List<Module> modules = Lists.newArrayList(); - - int scopeType = scope.getScopeType(); - switch (scopeType) { - case AnalysisScope.MODULE: { - SearchScope searchScope = scope.toSearchScope(); - if (searchScope instanceof ModuleWithDependenciesScope) { - ModuleWithDependenciesScope s = (ModuleWithDependenciesScope)searchScope; - if (!s.isSearchInLibraries()) { - modules.add(s.getModule()); - } - } - break; - } - case AnalysisScope.FILE: - case AnalysisScope.VIRTUAL_FILES: - case AnalysisScope.UNCOMMITTED_FILES: { - files = Lists.newArrayList(); - SearchScope searchScope = ApplicationManager - .getApplication() - .runReadAction((Computable<SearchScope>) scope::toSearchScope); - if (searchScope instanceof LocalSearchScope) { - final LocalSearchScope localSearchScope = (LocalSearchScope)searchScope; - final PsiElement[] elements = localSearchScope.getScope(); - final List<VirtualFile> finalFiles = files; - - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - for (PsiElement element : elements) { - if (element instanceof PsiFile) { // should be the case since scope type is FILE - Module module = ModuleUtilCore.findModuleForPsiElement(element); - if (module != null && !modules.contains(module)) { - modules.add(module); - } - VirtualFile virtualFile = ((PsiFile)element).getVirtualFile(); - if (virtualFile != null) { - finalFiles.add(virtualFile); - } - } - } - } - }); - } else { - final List<VirtualFile> finalList = files; - scope.accept(new PsiElementVisitor() { - @Override - public void visitFile(PsiFile file) { - VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile != null) { - finalList.add(virtualFile); - } - } - }); - } - if (files.isEmpty()) { - files = null; - } else { - // Lint will compute it lazily based on actual files in the request - lintScope = null; - } - break; - } - case AnalysisScope.PROJECT: { - modules.addAll(Arrays.asList(ModuleManager.getInstance(project).getModules())); - break; - } - case AnalysisScope.CUSTOM: - case AnalysisScope.MODULES: - case AnalysisScope.DIRECTORY: { - // Handled by the getNarrowedComplementaryScope case below - break; - } - - case AnalysisScope.INVALID: - break; - default: - Logger.getInstance(this.getClass()).warn("Unexpected inspection scope " + scope + ", " + scopeType); - } - - if (modules.isEmpty()) { - for (Module module : ModuleManager.getInstance(project).getModules()) { - if (scope.containsModule(module)) { - modules.add(module); - } - } - - if (modules.isEmpty() && files != null) { - for (VirtualFile file : files) { - Module module = ModuleUtilCore.findModuleForFile(file, project); - if (module != null && !modules.contains(module)) { - modules.add(module); - } - } - } - - if (modules.isEmpty()) { - AnalysisScope narrowed = scope.getNarrowedComplementaryScope(project); - for (Module module : ModuleManager.getInstance(project).getModules()) { - if (narrowed.containsModule(module)) { - modules.add(module); - } - } - } - } - - LintRequest request = new IntellijLintRequest(client, project, files, modules, false); - request.setScope(lintScope); - - lint.analyze(request); - - myResults = problemMap; - } - - @Nullable - public Map<Issue, Map<File, List<ProblemData>>> getResults() { - return myResults; - } - - @Override - public void performPostRunActivities(@NotNull List<InspectionToolWrapper> inspections, @NotNull final GlobalInspectionContext context) { - } - - @Override - public void cleanup() { - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionBase.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionBase.java deleted file mode 100644 index 8a7424a..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionBase.java +++ /dev/null
@@ -1,492 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.annotations.concurrency.GuardedBy; -import com.android.tools.klint.detector.api.*; -import com.intellij.analysis.AnalysisScope; -import com.intellij.codeHighlighting.HighlightDisplayLevel; -import com.intellij.codeInsight.daemon.HighlightDisplayKey; -import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.codeInspection.*; -import com.intellij.codeInspection.ex.InspectionToolWrapper; -import com.intellij.lang.annotation.ProblemGroup; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.colors.TextAttributesKey; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.profile.codeInspection.InspectionProjectProfileManager; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.HashMap; -import org.jetbrains.android.util.AndroidBundle; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import java.io.File; -import java.util.*; - -import static com.android.tools.klint.detector.api.TextFormat.*; -import static com.intellij.xml.CommonXmlStrings.HTML_END; -import static com.intellij.xml.CommonXmlStrings.HTML_START; - -public abstract class AndroidLintInspectionBase extends GlobalInspectionTool { - private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.lint.AndroidLintInspectionBase"); - - private static final Object ISSUE_MAP_LOCK = new Object(); - - @GuardedBy("ISSUE_MAP_LOCK") - private static volatile Map<Issue, String> ourIssue2InspectionShortName; - - protected final Issue myIssue; - private final String[] myGroupPath; - private final String myDisplayName; - - protected AndroidLintInspectionBase(@NotNull String displayName, @NotNull Issue issue) { - myIssue = issue; - - final Category category = issue.getCategory(); - - myGroupPath = ArrayUtil.mergeArrays(new String[]{AndroidBundle.message("android.inspections.group.name"), - AndroidBundle.message("android.lint.inspections.subgroup.name")}, computeAllNames(category)); - myDisplayName = displayName; - } - - @NotNull - public AndroidLintQuickFix[] getQuickFixes(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull String message) { - return getQuickFixes(message); - } - - @NotNull - public AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { - return AndroidLintQuickFix.EMPTY_ARRAY; - } - - @NotNull - public IntentionAction[] getIntentions(@NotNull PsiElement startElement, @NotNull PsiElement endElement) { - return IntentionAction.EMPTY_ARRAY; - } - - @Override - public boolean isGraphNeeded() { - return false; - } - - @NotNull - private LocalQuickFix[] getLocalQuickFixes(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull String message) { - final AndroidLintQuickFix[] fixes = getQuickFixes(startElement, endElement, message); - final LocalQuickFix[] result = new LocalQuickFix[fixes.length]; - - for (int i = 0; i < fixes.length; i++) { - if (fixes[i].isApplicable(startElement, endElement, AndroidQuickfixContexts.BatchContext.TYPE)) { - result[i] = new MyLocalQuickFix(fixes[i]); - } - } - return result; - } - - @Override - public void runInspection(@NotNull AnalysisScope scope, - @NotNull final InspectionManager manager, - @NotNull final GlobalInspectionContext globalContext, - @NotNull final ProblemDescriptionsProcessor problemDescriptionsProcessor) { - final AndroidLintGlobalInspectionContext androidLintContext = globalContext.getExtension(AndroidLintGlobalInspectionContext.ID); - if (androidLintContext == null) { - return; - } - - final Map<Issue, Map<File, List<ProblemData>>> problemMap = androidLintContext.getResults(); - if (problemMap == null) { - return; - } - - final Map<File, List<ProblemData>> file2ProblemList = problemMap.get(myIssue); - if (file2ProblemList == null) { - return; - } - - for (final Map.Entry<File, List<ProblemData>> entry : file2ProblemList.entrySet()) { - final File file = entry.getKey(); - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file); - - if (vFile == null) { - continue; - } - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - final PsiManager psiManager = PsiManager.getInstance(globalContext.getProject()); - final PsiFile psiFile = psiManager.findFile(vFile); - - if (psiFile != null) { - final ProblemDescriptor[] descriptors = computeProblemDescriptors(psiFile, manager, entry.getValue()); - - if (descriptors.length > 0) { - problemDescriptionsProcessor.addProblemElement(globalContext.getRefManager().getReference(psiFile), descriptors); - } - } else if (vFile.isDirectory()) { - final PsiDirectory psiDirectory = psiManager.findDirectory(vFile); - - if (psiDirectory != null) { - final ProblemDescriptor[] descriptors = computeProblemDescriptors(psiDirectory, manager, entry.getValue()); - - if (descriptors.length > 0) { - problemDescriptionsProcessor.addProblemElement(globalContext.getRefManager().getReference(psiDirectory), descriptors); - } - } - } - } - }); - } - } - - @NotNull - private ProblemDescriptor[] computeProblemDescriptors(@NotNull PsiElement psiFile, - @NotNull InspectionManager manager, - @NotNull List<ProblemData> problems) { - final List<ProblemDescriptor> result = new ArrayList<ProblemDescriptor>(); - - for (ProblemData problemData : problems) { - final String originalMessage = problemData.getMessage(); - - // We need to have explicit <html> and </html> tags around the text; inspection infrastructure - // such as the {@link com.intellij.codeInspection.ex.DescriptorComposer} will call - // {@link com.intellij.xml.util.XmlStringUtil.isWrappedInHtml}. See issue 177283 for uses. - // Note that we also need to use HTML with unicode characters here, since the HTML display - // in the inspections view does not appear to support numeric code character entities. - String formattedMessage = HTML_START + RAW.convertTo(originalMessage, HTML_WITH_UNICODE) + HTML_END; - - // The inspections UI does not correctly handle - - final TextRange range = problemData.getTextRange(); - - if (range.getStartOffset() == range.getEndOffset()) { - - if (psiFile instanceof PsiBinaryFile || psiFile instanceof PsiDirectory) { - final LocalQuickFix[] fixes = getLocalQuickFixes(psiFile, psiFile, originalMessage); - result.add(new NonTextFileProblemDescriptor((PsiFileSystemItem)psiFile, formattedMessage, fixes)); - } else if (!isSuppressedFor(psiFile)) { - result.add(manager.createProblemDescriptor(psiFile, formattedMessage, false, - getLocalQuickFixes(psiFile, psiFile, originalMessage), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING)); - } - } - else { - final PsiElement startElement = psiFile.findElementAt(range.getStartOffset()); - final PsiElement endElement = psiFile.findElementAt(range.getEndOffset() - 1); - - if (startElement != null && endElement != null && !isSuppressedFor(startElement)) { - result.add(manager.createProblemDescriptor(startElement, endElement, formattedMessage, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false, - getLocalQuickFixes(startElement, endElement, originalMessage))); - } - } - } - return result.toArray(new ProblemDescriptor[result.size()]); - } - - @NotNull - @Override - public SuppressQuickFix[] getBatchSuppressActions(@Nullable PsiElement element) { - if (AndroidLintExternalAnnotator.INCLUDE_IDEA_SUPPRESS_ACTIONS) { - final List<SuppressQuickFix> result = new ArrayList<SuppressQuickFix>(); - result.addAll(Arrays.asList(BatchSuppressManager.SERVICE.getInstance().createBatchSuppressActions(HighlightDisplayKey.find(getShortName())))); - result.addAll(Arrays.asList(new XmlSuppressableInspectionTool.SuppressTagStatic(getShortName()), - new XmlSuppressableInspectionTool.SuppressForFile(getShortName()))); - return result.toArray(new SuppressQuickFix[result.size()]); - } else { - return new SuppressQuickFix[0]; - } - } - - @TestOnly - public static void invalidateInspectionShortName2IssueMap() { - ourIssue2InspectionShortName = null; - } - - public static String getInspectionShortNameByIssue(@NotNull Project project, @NotNull Issue issue) { - synchronized (ISSUE_MAP_LOCK) { - if (ourIssue2InspectionShortName == null) { - ourIssue2InspectionShortName = new HashMap<Issue, String>(); - - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - - for (InspectionToolWrapper e : profile.getInspectionTools(null)) { - final String shortName = e.getShortName(); - - if (shortName.startsWith("AndroidKLint")) { - final InspectionProfileEntry entry = e.getTool(); - if (entry instanceof AndroidLintInspectionBase) { - final Issue s = ((AndroidLintInspectionBase)entry).getIssue(); - ourIssue2InspectionShortName.put(s, shortName); - } - } - } - } - return ourIssue2InspectionShortName.get(issue); - } - } - - @NotNull - private static String[] computeAllNames(@NotNull Category category) { - final List<String> result = new ArrayList<String>(); - - Category c = category; - - while (c != null) { - final String name = c.getName(); - - if (name == null) { - return ArrayUtil.EMPTY_STRING_ARRAY; - } - result.add(name); - c = c.getParent(); - } - return ArrayUtil.reverseArray(ArrayUtil.toStringArray(result)); - } - - @Nls - @NotNull - @Override - public String getGroupDisplayName() { - return AndroidBundle.message("android.lint.inspections.group.name"); - } - - @NotNull - @Override - public String[] getGroupPath() { - return myGroupPath; - } - - @Nls - @NotNull - @Override - public String getDisplayName() { - return myDisplayName; - } - - @SuppressWarnings("deprecation") - @Override - public String getStaticDescription() { - StringBuilder sb = new StringBuilder(1000); - sb.append("<html><body>"); - sb.append(myIssue.getBriefDescription(HTML)); - sb.append("<br><br>"); - sb.append(myIssue.getExplanation(HTML)); - List<String> urls = myIssue.getMoreInfo(); - if (!urls.isEmpty()) { - boolean separated = false; - for (String url : urls) { - if (!myIssue.getExplanation(RAW).contains(url)) { - if (!separated) { - sb.append("<br><br>"); - separated = true; - } else { - sb.append("<br>"); - } - sb.append("<a href=\""); - sb.append(url); - sb.append("\">"); - sb.append(url); - sb.append("</a>"); - } - } - } - sb.append("</body></html>"); - - return sb.toString(); - } - - @Override - public boolean isEnabledByDefault() { - return myIssue.isEnabledByDefault(); - } - - @NotNull - @Override - public String getShortName() { - return InspectionProfileEntry.getShortName(getClass().getSimpleName()); - } - - @NotNull - @Override - public HighlightDisplayLevel getDefaultLevel() { - final Severity defaultSeverity = myIssue.getDefaultSeverity(); - if (defaultSeverity == null) { - return HighlightDisplayLevel.WARNING; - } - final HighlightDisplayLevel displayLevel = toHighlightDisplayLevel(defaultSeverity); - return displayLevel != null ? displayLevel : HighlightDisplayLevel.WARNING; - } - - @Nullable - static HighlightDisplayLevel toHighlightDisplayLevel(@NotNull Severity severity) { - switch (severity) { - case ERROR: - return HighlightDisplayLevel.ERROR; - case FATAL: - return HighlightDisplayLevel.ERROR; - case WARNING: - return HighlightDisplayLevel.WARNING; - case INFORMATIONAL: - return HighlightDisplayLevel.WEAK_WARNING; - case IGNORE: - return null; - default: - LOG.error("Unknown severity " + severity); - return null; - } - } - - /** Returns true if the given analysis scope is adequate for single-file analysis */ - private static boolean isSingleFileScope(EnumSet<Scope> scopes) { - if (scopes.size() != 1) { - return false; - } - final Scope scope = scopes.iterator().next(); - return scope == Scope.JAVA_FILE || scope == Scope.RESOURCE_FILE || scope == Scope.MANIFEST - || scope == Scope.PROGUARD_FILE || scope == Scope.OTHER; - } - - @Override - public boolean worksInBatchModeOnly() { - Implementation implementation = myIssue.getImplementation(); - if (isSingleFileScope(implementation.getScope())) { - return false; - } - for (EnumSet<Scope> scopes : implementation.getAnalysisScopes()) { - if (isSingleFileScope(scopes)) { - return false; - } - } - - return true; - } - - @NotNull - public Issue getIssue() { - return myIssue; - } - - static class MyLocalQuickFix implements LocalQuickFix { - private final AndroidLintQuickFix myLintQuickFix; - - MyLocalQuickFix(@NotNull AndroidLintQuickFix lintQuickFix) { - myLintQuickFix = lintQuickFix; - } - - @NotNull - @Override - public String getName() { - return myLintQuickFix.getName(); - } - - @NotNull - @Override - public String getFamilyName() { - return AndroidBundle.message("android.lint.quickfixes.family"); - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - myLintQuickFix.apply(descriptor.getStartElement(), descriptor.getEndElement(), AndroidQuickfixContexts.BatchContext.getInstance()); - } - } - - /** - * A {@link com.intellij.codeInspection.ProblemDescriptor} for image and directory files. This is - * necessary because the {@link InspectionManager}'s createProblemDescriptor methods - * all use {@link com.intellij.codeInspection.ProblemDescriptorBase} where in the constructor - * it insists that the start and end {@link PsiElement} instances must have a valid - * <b>text</b> range, which does not apply for images. - * <p> - * This custom descriptor allows the batch lint analysis to correctly handle lint errors - * associated with image files (such as the various {@link com.android.tools.lint.checks.IconDetector} - * warnings), as well as directory errors (such as incorrect locale folders), - * and clicking on them will navigate to the correct icon. - */ - private static class NonTextFileProblemDescriptor implements ProblemDescriptor { - private final PsiFileSystemItem myFile; - private final String myMessage; - private final LocalQuickFix[] myFixes; - private ProblemGroup myGroup; - - public NonTextFileProblemDescriptor(@NotNull PsiFileSystemItem file, @NotNull String message, @NotNull LocalQuickFix[] fixes) { - myFile = file; - myMessage = message; - myFixes = fixes; - } - - @Override - public PsiElement getPsiElement() { - return myFile; - } - - @Override - public PsiElement getStartElement() { - return myFile; - } - - @Override - public PsiElement getEndElement() { - return myFile; - } - - @Override - public TextRange getTextRangeInElement() { - return new TextRange(0, 0); - } - - @Override - public int getLineNumber() { - return 0; - } - - @NotNull - @Override - public ProblemHighlightType getHighlightType() { - return ProblemHighlightType.GENERIC_ERROR_OR_WARNING; - } - - @Override - public boolean isAfterEndOfLine() { - return false; - } - - @Override - public void setTextAttributes(TextAttributesKey key) { - } - - @Nullable - @Override - public ProblemGroup getProblemGroup() { - return myGroup; - } - - @Override - public void setProblemGroup(@Nullable ProblemGroup problemGroup) { - myGroup = problemGroup; - } - - @Override - public boolean showTooltip() { - return false; - } - - @NotNull - @Override - public String getDescriptionTemplate() { - return myMessage; - } - - @Nullable - @Override - public QuickFix[] getFixes() { - return myFixes; - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java deleted file mode 100644 index a74bd02..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java +++ /dev/null
@@ -1,757 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.tools.klint.checks.*; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.lint.detector.api.TextFormat; -import com.intellij.openapi.project.Project; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiElement; -import com.intellij.psi.search.GlobalSearchScope; -import org.jetbrains.android.util.AndroidBundle; -import org.jetbrains.annotations.NotNull; - -import java.util.regex.Pattern; - -import static com.android.tools.klint.checks.ApiDetector.REQUIRES_API_ANNOTATION; -import static com.android.tools.klint.checks.FragmentDetector.ISSUE; - -/** - * Registrations for all the various Lint rules as local IDE inspections, along with quickfixes for many of them - */ -public class AndroidLintInspectionToolProvider { - public static class AndroidKLintCustomErrorInspection extends AndroidLintInspectionBase { - public AndroidKLintCustomErrorInspection() { - super("Error from Custom Lint Check", IntellijLintIssueRegistry.CUSTOM_ERROR); - } - } - - public static class AndroidKLintCustomWarningInspection extends AndroidLintInspectionBase { - public AndroidKLintCustomWarningInspection() { - super("Warning from Custom Lint Check", IntellijLintIssueRegistry.CUSTOM_WARNING); - } - - } - - public static class AndroidKLintInconsistentLayoutInspection extends AndroidLintInspectionBase { - public AndroidKLintInconsistentLayoutInspection() { - super(AndroidBundle.message("android.lint.inspections.inconsistent.layout"), LayoutConsistencyDetector.INCONSISTENT_IDS); - } - } - - public static class AndroidKLintIconExpectedSizeInspection extends AndroidLintInspectionBase { - public AndroidKLintIconExpectedSizeInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.expected.size"), IconDetector.ICON_EXPECTED_SIZE); - } - } - - public static class AndroidKLintIconDipSizeInspection extends AndroidLintInspectionBase { - public AndroidKLintIconDipSizeInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.dip.size"), IconDetector.ICON_DIP_SIZE); - } - } - - public static class AndroidKLintIconLocationInspection extends AndroidLintInspectionBase { - public AndroidKLintIconLocationInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.location"), IconDetector.ICON_LOCATION); - } - } - - public static class AndroidKLintIconDensitiesInspection extends AndroidLintInspectionBase { - public AndroidKLintIconDensitiesInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.densities"), IconDetector.ICON_DENSITIES); - } - } - - public static class AndroidKLintIconMissingDensityFolderInspection extends AndroidLintInspectionBase { - public AndroidKLintIconMissingDensityFolderInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.missing.density.folder"), IconDetector.ICON_MISSING_FOLDER); - } - } - - public static class AndroidKLintIconMixedNinePatchInspection extends AndroidLintInspectionBase { - public AndroidKLintIconMixedNinePatchInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.mixed.nine.patch"), IconDetector.ICON_MIX_9PNG); - } - } - - public static class AndroidKLintFloatMathInspection extends AndroidLintInspectionBase { - public AndroidKLintFloatMathInspection() { - super("Using FloatMath instead of Math", MathDetector.ISSUE); - } - } - - public static class AndroidKLintGetInstanceInspection extends AndroidLintInspectionBase { - public AndroidKLintGetInstanceInspection() { - super(AndroidBundle.message("android.lint.inspections.get.instance"), CipherGetInstanceDetector.ISSUE); - } - } - - public static class AndroidKLintGifUsageInspection extends AndroidLintInspectionBase { - public AndroidKLintGifUsageInspection() { - super(AndroidBundle.message("android.lint.inspections.gif.usage"), IconDetector.GIF_USAGE); - } - } - - public static class AndroidKLintIconDuplicatesInspection extends AndroidLintInspectionBase { - public AndroidKLintIconDuplicatesInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.duplicates"), IconDetector.DUPLICATES_NAMES); - } - } - - public static class AndroidKLintIconDuplicatesConfigInspection extends AndroidLintInspectionBase { - public AndroidKLintIconDuplicatesConfigInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.duplicates.config"), IconDetector.DUPLICATES_CONFIGURATIONS); - } - } - - public static class AndroidKLintIconNoDpiInspection extends AndroidLintInspectionBase { - public AndroidKLintIconNoDpiInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.no.dpi"), IconDetector.ICON_NODPI); - } - } - - public static class AndroidKLintOverdrawInspection extends AndroidLintInspectionBase { - public AndroidKLintOverdrawInspection() { - super(AndroidBundle.message("android.lint.inspections.overdraw"), OverdrawDetector.ISSUE); - } - } - - public static class AndroidKLintMissingSuperCallInspection extends AndroidLintInspectionBase { - public AndroidKLintMissingSuperCallInspection() { - super(AndroidBundle.message("android.lint.inspections.missing.super.call"), CallSuperDetector.ISSUE); - } - } - - public static class AndroidKLintUnprotectedSMSBroadcastReceiverInspection extends AndroidLintInspectionBase { - public AndroidKLintUnprotectedSMSBroadcastReceiverInspection() { - super(AndroidBundle.message("android.lint.inspections.unprotected.smsbroadcast.receiver"), UnsafeBroadcastReceiverDetector.BROADCAST_SMS); - } - - } - - public static class AndroidKLintUnusedAttributeInspection extends AndroidLintInspectionBase { - public AndroidKLintUnusedAttributeInspection() { - super(AndroidBundle.message("android.lint.inspections.unused.attribute"), ApiDetector.UNUSED); - } - - } - - public static class AndroidKLintAlwaysShowActionInspection extends AndroidLintInspectionBase { - public AndroidKLintAlwaysShowActionInspection() { - super(AndroidBundle.message("android.lint.inspections.always.show.action"), AlwaysShowActionDetector.ISSUE); - } - - } - - public static class AndroidKLintAppCompatMethodInspection extends AndroidLintInspectionBase { - public AndroidKLintAppCompatMethodInspection() { - super(AndroidBundle.message("android.lint.inspections.app.compat.method"), AppCompatCallDetector.ISSUE); - } - - } - - public static class AndroidKLintGoogleAppIndexingUrlErrorInspection extends AndroidLintInspectionBase { - public AndroidKLintGoogleAppIndexingUrlErrorInspection() { - super("URL not supported by app for Google App Indexing", AppIndexingApiDetector.ISSUE_URL_ERROR); - } - - } - - public static class AndroidKLintGoogleAppIndexingWarningInspection extends AndroidLintInspectionBase { - public AndroidKLintGoogleAppIndexingWarningInspection() { - super("Missing support for Google App Indexing", AppIndexingApiDetector.ISSUE_APP_INDEXING); - } - - } - - public static class AndroidKLintGoogleAppIndexingApiWarningInspection extends AndroidLintInspectionBase { - public AndroidKLintGoogleAppIndexingApiWarningInspection() { - super("Missing support for Google App Indexing Api", AppIndexingApiDetector.ISSUE_APP_INDEXING_API); - } - - } - - public static class AndroidKLintStringFormatCountInspection extends AndroidLintInspectionBase { - public AndroidKLintStringFormatCountInspection() { - super(AndroidBundle.message("android.lint.inspections.string.format.count"), StringFormatDetector.ARG_COUNT); - } - } - - public static class AndroidKLintStringFormatMatchesInspection extends AndroidLintInspectionBase { - public AndroidKLintStringFormatMatchesInspection() { - super(AndroidBundle.message("android.lint.inspections.string.format.matches"), StringFormatDetector.ARG_TYPES); - } - } - - public static class AndroidKLintStringFormatInvalidInspection extends AndroidLintInspectionBase { - public AndroidKLintStringFormatInvalidInspection() { - super(AndroidBundle.message("android.lint.inspections.string.format.invalid"), StringFormatDetector.INVALID); - } - } - - public static class AndroidKLintWrongViewCastInspection extends AndroidLintInspectionBase { - public AndroidKLintWrongViewCastInspection() { - super(AndroidBundle.message("android.lint.inspections.wrong.view.cast"), ViewTypeDetector.ISSUE); - } - } - - public static class AndroidKLintCommitTransactionInspection extends AndroidLintInspectionBase { - public AndroidKLintCommitTransactionInspection() { - super(AndroidBundle.message("android.lint.inspections.commit.transaction"), CleanupDetector.COMMIT_FRAGMENT); - } - } - - public static class AndroidKLintBadHostnameVerifierInspection extends AndroidLintInspectionBase { - public AndroidKLintBadHostnameVerifierInspection() { - super("Insecure HostnameVerifier", BadHostnameVerifierDetector.ISSUE); - } - } - - public static class AndroidKLintBatteryLifeInspection extends AndroidLintInspectionBase { - public AndroidKLintBatteryLifeInspection() { - super("Battery Life Issues", BatteryDetector.ISSUE); - } - } - - public static class AndroidKLintHandlerLeakInspection extends AndroidLintInspectionBase { - public AndroidKLintHandlerLeakInspection() { - super(AndroidBundle.message("android.lint.inspections.handler.leak"), HandlerDetector.ISSUE); - } - } - - public static class AndroidKLintDrawAllocationInspection extends AndroidLintInspectionBase { - public AndroidKLintDrawAllocationInspection() { - super(AndroidBundle.message("android.lint.inspections.draw.allocation"), JavaPerformanceDetector.PAINT_ALLOC); - } - } - - public static class AndroidKLintUseSparseArraysInspection extends AndroidLintInspectionBase { - public AndroidKLintUseSparseArraysInspection() { - super(AndroidBundle.message("android.lint.inspections.use.sparse.arrays"), JavaPerformanceDetector.USE_SPARSE_ARRAY); - } - } - - public static class AndroidKLintUseValueOfInspection extends AndroidLintInspectionBase { - public AndroidKLintUseValueOfInspection() { - super(AndroidBundle.message("android.lint.inspections.use.value.of"), JavaPerformanceDetector.USE_VALUE_OF); - } - - } - - public static class AndroidKLintPackageManagerGetSignaturesInspection extends AndroidLintInspectionBase { - public AndroidKLintPackageManagerGetSignaturesInspection() { - super(AndroidBundle.message("android.lint.inspections.package.manager.get.signatures"), GetSignaturesDetector.ISSUE); - } - } - - public static class AndroidKLintParcelClassLoaderInspection extends AndroidLintInspectionBase { - public AndroidKLintParcelClassLoaderInspection() { - super("Default Parcel Class Loader", ReadParcelableDetector.ISSUE); - } - - } - - public static class AndroidKLintParcelCreatorInspection extends AndroidLintInspectionBase { - public AndroidKLintParcelCreatorInspection() { - super(AndroidBundle.message("android.lint.inspections.parcel.creator"), ParcelDetector.ISSUE); - } - @NotNull - @Override - public AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { - return new AndroidLintQuickFix[]{ new ParcelableQuickFix() }; - } - } - - public static class AndroidKLintPluralsCandidateInspection extends AndroidLintInspectionBase { - public AndroidKLintPluralsCandidateInspection() { - super(AndroidBundle.message("android.lint.inspections.plurals.candidate"), StringFormatDetector.POTENTIAL_PLURAL); - } - } - - public static class AndroidKLintPrivateResourceInspection extends AndroidLintInspectionBase { - public AndroidKLintPrivateResourceInspection() { - super(AndroidBundle.message("android.lint.inspections.private.resource"), PrivateResourceDetector.ISSUE); - } - } - - public static class AndroidKLintSdCardPathInspection extends AndroidLintInspectionBase { - public AndroidKLintSdCardPathInspection() { - super(AndroidBundle.message("android.lint.inspections.sd.card.path"), SdCardDetector.ISSUE); - } - } - - public static class AndroidKLintSuspiciousImportInspection extends AndroidLintInspectionBase { - public AndroidKLintSuspiciousImportInspection() { - super(AndroidBundle.message("android.lint.inspections.suspicious.import"), WrongImportDetector.ISSUE); - } - } - - public static class AndroidKLintSQLiteStringInspection extends AndroidLintInspectionBase { - public AndroidKLintSQLiteStringInspection() { - super(AndroidBundle.message("android.lint.inspections.sqlite.string"), SQLiteDetector.ISSUE); - } - } - - public static class AndroidKLintDefaultLocaleInspection extends AndroidLintInspectionBase { - public AndroidKLintDefaultLocaleInspection() { - super("Implied default locale in case conversion", LocaleDetector.STRING_LOCALE); - } - } - - public static class AndroidKLintValidFragmentInspection extends AndroidLintInspectionBase { - public AndroidKLintValidFragmentInspection() { - super(AndroidBundle.message("android.lint.inspections.valid.fragment"), ISSUE); - } - } - - public static class AndroidKLintViewConstructorInspection extends AndroidLintInspectionBase { - public AndroidKLintViewConstructorInspection() { - super(AndroidBundle.message("android.lint.inspections.view.constructor"), ViewConstructorDetector.ISSUE); - } - } - - public static class AndroidKLintViewHolderInspection extends AndroidLintInspectionBase { - public AndroidKLintViewHolderInspection() { - super(AndroidBundle.message("android.lint.inspections.view.holder"), ViewHolderDetector.ISSUE); - } - } - - public static class AndroidKLintViewTagInspection extends AndroidLintInspectionBase { - public AndroidKLintViewTagInspection() { - super("Tagged object leaks", ViewTagDetector.ISSUE); - } - } - - public static class AndroidKLintMergeRootFrameInspection extends AndroidLintInspectionBase { - public AndroidKLintMergeRootFrameInspection() { - super(AndroidBundle.message("android.lint.inspections.merge.root.frame"), MergeRootFrameLayoutDetector.ISSUE); - } - } - - public static class AndroidKLintExportedServiceInspection extends AndroidLintInspectionBase { - public AndroidKLintExportedServiceInspection() { - super(AndroidBundle.message("android.lint.inspections.exported.service"), SecurityDetector.EXPORTED_SERVICE); - } - - } - - public static class AndroidKLintGrantAllUrisInspection extends AndroidLintInspectionBase { - public AndroidKLintGrantAllUrisInspection() { - super(AndroidBundle.message("android.lint.inspections.grant.all.uris"), SecurityDetector.OPEN_PROVIDER); - } - } - - public static class AndroidKLintWorldWriteableFilesInspection extends AndroidLintInspectionBase { - public AndroidKLintWorldWriteableFilesInspection() { - super(AndroidBundle.message("android.lint.inspections.world.writeable.files"), SecurityDetector.WORLD_WRITEABLE); - } - } - - public static class AndroidKLintSSLCertificateSocketFactoryCreateSocketInspection extends AndroidLintInspectionBase { - public AndroidKLintSSLCertificateSocketFactoryCreateSocketInspection() { - super(AndroidBundle.message("android.lint.inspections.sslcertificate.socket.factory.create.socket"), SslCertificateSocketFactoryDetector.CREATE_SOCKET); - } - } - - public static class AndroidKLintSSLCertificateSocketFactoryGetInsecureInspection extends AndroidLintInspectionBase { - public AndroidKLintSSLCertificateSocketFactoryGetInsecureInspection() { - super(AndroidBundle.message("android.lint.inspections.sslcertificate.socket.factory.get.insecure"), SslCertificateSocketFactoryDetector.GET_INSECURE); - } - } - - public static class AndroidKLintSwitchIntDefInspection extends AndroidLintInspectionBase { - public AndroidKLintSwitchIntDefInspection() { - super("Missing @IntDef in Switch", AnnotationDetector.SWITCH_TYPE_DEF); - } - - } - - public static class AndroidKLintTrustAllX509TrustManagerInspection extends AndroidLintInspectionBase { - public AndroidKLintTrustAllX509TrustManagerInspection() { - super("Insecure TLS/SSL trust manager", TrustAllX509TrustManagerDetector.ISSUE); - } - } - - private abstract static class AndroidKLintTypographyInspectionBase extends AndroidLintInspectionBase { - public AndroidKLintTypographyInspectionBase(String displayName, Issue issue) { - super(displayName, issue); - } - - } - - public static class AndroidKLintNewApiInspection extends AndroidLintInspectionBase { - public AndroidKLintNewApiInspection() { - super(AndroidBundle.message("android.lint.inspections.new.api"), ApiDetector.UNSUPPORTED); - } - - @NotNull - @Override - public AndroidLintQuickFix[] getQuickFixes(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull String message) { - return getApiQuickFixes(startElement, endElement, message); - } - - @NotNull - public static AndroidLintQuickFix[] getApiQuickFixes(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull String message) { - int api = ApiDetector.getRequiredVersion(TextFormat.RAW.toText(message)); - if (api == -1) { - return AndroidLintQuickFix.EMPTY_ARRAY; - } - - Project project = startElement.getProject(); - if (JavaPsiFacade.getInstance(project).findClass(REQUIRES_API_ANNOTATION, GlobalSearchScope.allScope(project)) != null) { - return new AndroidLintQuickFix[] { - new AddTargetApiQuickFix(api, true), - new AddTargetApiQuickFix(api, false), - new AddTargetVersionCheckQuickFix(api) - }; - } - - return new AndroidLintQuickFix[] { - new AddTargetApiQuickFix(api, false), - new AddTargetVersionCheckQuickFix(api) - }; - } - } - - public static class AndroidKLintInlinedApiInspection extends AndroidLintInspectionBase { - public AndroidKLintInlinedApiInspection() { - super(AndroidBundle.message("android.lint.inspections.inlined.api"), ApiDetector.INLINED); - } - - @NotNull - @Override - public AndroidLintQuickFix[] getQuickFixes(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull String message) { - return AndroidKLintNewApiInspection.getApiQuickFixes(startElement, endElement, message); - } - } - - public static class AndroidKLintOverrideInspection extends AndroidLintInspectionBase { - public AndroidKLintOverrideInspection() { - super(AndroidBundle.message("android.lint.inspections.override"), ApiDetector.OVERRIDE); - } - - } - - private static final Pattern QUOTED_PARAMETER = Pattern.compile("`.+:(.+)=\"(.*)\"`"); - - public static class AndroidKLintRtlCompatInspection extends AndroidLintInspectionBase { - public AndroidKLintRtlCompatInspection() { - super(AndroidBundle.message("android.lint.inspections.rtl.compat"), RtlDetector.COMPAT); - } - - - } - public static class AndroidKLintRtlEnabledInspection extends AndroidLintInspectionBase { - public AndroidKLintRtlEnabledInspection() { - super(AndroidBundle.message("android.lint.inspections.rtl.enabled"), RtlDetector.ENABLED); - } - } - public static class AndroidKLintRtlHardcodedInspection extends AndroidLintInspectionBase { - public AndroidKLintRtlHardcodedInspection() { - super(AndroidBundle.message("android.lint.inspections.rtl.hardcoded"), RtlDetector.USE_START); - } - } - public static class AndroidKLintRtlSymmetryInspection extends AndroidLintInspectionBase { - public AndroidKLintRtlSymmetryInspection() { - super(AndroidBundle.message("android.lint.inspections.rtl.symmetry"), RtlDetector.SYMMETRY); - } - } - - // Missing the following issues, because they require classfile analysis: - // FloatMath, FieldGetter, Override, OnClick, ViewTag, DefaultLocale, SimpleDateFormat, - // Registered, MissingRegistered, Instantiatable, HandlerLeak, ValidFragment, SecureRandom, - // ViewConstructor, Wakelock, Recycle, CommitTransaction, WrongCall, DalvikOverride - - // I think DefaultLocale is already handled by a regular IDEA code check. - - public static class AndroidKLintAddJavascriptInterfaceInspection extends AndroidLintInspectionBase { - public AndroidKLintAddJavascriptInterfaceInspection() { - super(AndroidBundle.message("android.lint.inspections.add.javascript.interface"), AddJavascriptInterfaceDetector.ISSUE); - } - } - - public static class AndroidKLintAllowAllHostnameVerifierInspection extends AndroidLintInspectionBase { - public AndroidKLintAllowAllHostnameVerifierInspection() { - super("Insecure HostnameVerifier", AllowAllHostnameVerifierDetector.ISSUE); - } - } - - public static class AndroidKLintCommitPrefEditsInspection extends AndroidLintInspectionBase { - public AndroidKLintCommitPrefEditsInspection() { - super(AndroidBundle.message("android.lint.inspections.commit.pref.edits"), CleanupDetector.SHARED_PREF); - } - - } - - public static class AndroidKLintCustomViewStyleableInspection extends AndroidLintInspectionBase { - public AndroidKLintCustomViewStyleableInspection() { - super(AndroidBundle.message("android.lint.inspections.custom.view.styleable"), CustomViewDetector.ISSUE); - } - } - - public static class AndroidKLintCutPasteIdInspection extends AndroidLintInspectionBase { - public AndroidKLintCutPasteIdInspection() { - super(AndroidBundle.message("android.lint.inspections.cut.paste.id"), CutPasteDetector.ISSUE); - } - } - public static class AndroidKLintEasterEggInspection extends AndroidLintInspectionBase { - public AndroidKLintEasterEggInspection() { - super(AndroidBundle.message("android.lint.inspections.easter.egg"), CommentDetector.EASTER_EGG); - } - } - public static class AndroidKLintExportedContentProviderInspection extends AndroidLintInspectionBase { - public AndroidKLintExportedContentProviderInspection() { - super(AndroidBundle.message("android.lint.inspections.exported.content.provider"), SecurityDetector.EXPORTED_PROVIDER); - } - - } - public static class AndroidKLintExportedPreferenceActivityInspection extends AndroidLintInspectionBase { - public AndroidKLintExportedPreferenceActivityInspection() { - super(AndroidBundle.message("android.lint.inspections.exported.preference.activity"), PreferenceActivityDetector.ISSUE); - } - } - public static class AndroidKLintExportedReceiverInspection extends AndroidLintInspectionBase { - public AndroidKLintExportedReceiverInspection() { - super(AndroidBundle.message("android.lint.inspections.exported.receiver"), SecurityDetector.EXPORTED_RECEIVER); - } - - } - public static class AndroidKLintIconColorsInspection extends AndroidLintInspectionBase { - public AndroidKLintIconColorsInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.colors"), IconDetector.ICON_COLORS); - } - } - public static class AndroidKLintIconExtensionInspection extends AndroidLintInspectionBase { - public AndroidKLintIconExtensionInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.extension"), IconDetector.ICON_EXTENSION); - } - } - public static class AndroidKLintIconLauncherShapeInspection extends AndroidLintInspectionBase { - public AndroidKLintIconLauncherShapeInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.launcher.shape"), IconDetector.ICON_LAUNCHER_SHAPE); - } - } - public static class AndroidKLintIconXmlAndPngInspection extends AndroidLintInspectionBase { - public AndroidKLintIconXmlAndPngInspection() { - super(AndroidBundle.message("android.lint.inspections.icon.xml.and.png"), IconDetector.ICON_XML_AND_PNG); - } - } - - public static class AndroidKLintAuthLeakInspection extends AndroidLintInspectionBase { - public AndroidKLintAuthLeakInspection() { - super("Code could contain an credential leak", StringAuthLeakDetector.AUTH_LEAK); - } - } - - public static class AndroidKLintInflateParamsInspection extends AndroidLintInspectionBase { - public AndroidKLintInflateParamsInspection() { - super(AndroidBundle.message("android.lint.inspections.inflate.params"), LayoutInflationDetector.ISSUE); - } - } - - public static class AndroidKLintInvalidUsesTagAttributeInspection extends AndroidLintInspectionBase { - public AndroidKLintInvalidUsesTagAttributeInspection() { - super(AndroidBundle.message("android.lint.inspections.invalid.uses.tag.attribute"), AndroidAutoDetector.INVALID_USES_TAG_ISSUE); - } - } - - public static class AndroidKLintJavascriptInterfaceInspection extends AndroidLintInspectionBase { - public AndroidKLintJavascriptInterfaceInspection() { - super(AndroidBundle.message("android.lint.inspections.javascript.interface"), JavaScriptInterfaceDetector.ISSUE); - } - } - - public static class AndroidKLintLocalSuppressInspection extends AndroidLintInspectionBase { - public AndroidKLintLocalSuppressInspection() { - super(AndroidBundle.message("android.lint.inspections.local.suppress"), AnnotationDetector.INSIDE_METHOD); - } - } - - public static class AndroidKLintLogConditionalInspection extends AndroidLintInspectionBase { - public AndroidKLintLogConditionalInspection() { - super(AndroidBundle.message("android.lint.inspections.log.conditional"), LogDetector.CONDITIONAL); - } - } - - public static class AndroidKLintLogTagMismatchInspection extends AndroidLintInspectionBase { - public AndroidKLintLogTagMismatchInspection() { - super(AndroidBundle.message("android.lint.inspections.log.tag.mismatch"), LogDetector.WRONG_TAG); - } - } - - public static class AndroidKLintLongLogTagInspection extends AndroidLintInspectionBase { - public AndroidKLintLongLogTagInspection() { - super(AndroidBundle.message("android.lint.inspections.long.log.tag"), LogDetector.LONG_TAG); - } - } - - public static class AndroidKLintMissingIntentFilterForMediaSearchInspection extends AndroidLintInspectionBase { - public AndroidKLintMissingIntentFilterForMediaSearchInspection() { - super(AndroidBundle.message("android.lint.inspections.missing.intent.filter.for.media.search"), - AndroidAutoDetector.MISSING_INTENT_FILTER_FOR_MEDIA_SEARCH); - } - } - - public static class AndroidKLintMissingMediaBrowserServiceIntentFilterInspection extends AndroidLintInspectionBase { - public AndroidKLintMissingMediaBrowserServiceIntentFilterInspection() { - super(AndroidBundle.message("android.lint.inspections.missing.media.browser.service.intent.filter"), - AndroidAutoDetector.MISSING_MEDIA_BROWSER_SERVICE_ACTION_ISSUE); - } - } - - public static class AndroidKLintMissingOnPlayFromSearchInspection extends AndroidLintInspectionBase { - public AndroidKLintMissingOnPlayFromSearchInspection() { - super(AndroidBundle.message("android.lint.inspections.missing.on.play.from.search"), - AndroidAutoDetector.MISSING_ON_PLAY_FROM_SEARCH); - } - } - - public static class AndroidKLintOverrideAbstractInspection extends AndroidLintInspectionBase { - public AndroidKLintOverrideAbstractInspection() { - super(AndroidBundle.message("android.lint.inspections.override.abstract"), OverrideConcreteDetector.ISSUE); - } - } - - public static class AndroidKLintRecycleInspection extends AndroidLintInspectionBase { - public AndroidKLintRecycleInspection() { - super(AndroidBundle.message("android.lint.inspections.recycle"), CleanupDetector.RECYCLE_RESOURCE); - } - } - - public static class AndroidKLintRecyclerViewInspection extends AndroidLintInspectionBase { - public AndroidKLintRecyclerViewInspection() { - super("RecyclerView Problems", RecyclerViewDetector.FIXED_POSITION); - } - } - - public static class AndroidKLintRegisteredInspection extends AndroidLintInspectionBase { - public AndroidKLintRegisteredInspection() { - super(AndroidBundle.message("android.lint.inspections.registered"), RegistrationDetector.ISSUE); - } - } - - public static class AndroidKLintRequiredSizeInspection extends AndroidLintInspectionBase { - public AndroidKLintRequiredSizeInspection() { - super(AndroidBundle.message("android.lint.inspections.required.size"), RequiredAttributeDetector.ISSUE); - } - } - public static class AndroidKLintSecureRandomInspection extends AndroidLintInspectionBase { - public AndroidKLintSecureRandomInspection() { - super("Using a fixed seed with SecureRandom", SecureRandomDetector.ISSUE); - } - } - - public static class AndroidKLintServiceCastInspection extends AndroidLintInspectionBase { - public AndroidKLintServiceCastInspection() { - super(AndroidBundle.message("android.lint.inspections.service.cast"), ServiceCastDetector.ISSUE); - } - } - public static class AndroidKLintSetJavaScriptEnabledInspection extends AndroidLintInspectionBase { - public AndroidKLintSetJavaScriptEnabledInspection() { - super(AndroidBundle.message("android.lint.inspections.set.java.script.enabled"), SetJavaScriptEnabledDetector.ISSUE); - } - } - - public static class AndroidKLintSetTextI18nInspection extends AndroidLintInspectionBase { - public AndroidKLintSetTextI18nInspection() { - super(AndroidBundle.message("android.lint.inspections.set.text.i18n"), SetTextDetector.SET_TEXT_I18N); - } - } - - public static class AndroidKLintSetWorldReadableInspection extends AndroidLintInspectionBase { - public AndroidKLintSetWorldReadableInspection() { - super(AndroidBundle.message("android.lint.inspections.set.world.readable"), SecurityDetector.SET_READABLE); - } - } - - public static class AndroidKLintSetWorldWritableInspection extends AndroidLintInspectionBase { - public AndroidKLintSetWorldWritableInspection() { - super(AndroidBundle.message("android.lint.inspections.set.world.writable"), SecurityDetector.SET_WRITABLE); - } - } - - public static class AndroidKLintShiftFlagsInspection extends AndroidLintInspectionBase { - public AndroidKLintShiftFlagsInspection() { - super(AndroidBundle.message("android.lint.inspections.shift.flags"), AnnotationDetector.FLAG_STYLE); - } - } - - public static class AndroidKLintShortAlarmInspection extends AndroidLintInspectionBase { - public AndroidKLintShortAlarmInspection() { - super(AndroidBundle.message("android.lint.inspections.short.alarm"), AlarmDetector.ISSUE); - } - } - - public static class AndroidKLintShowToastInspection extends AndroidLintInspectionBase { - public AndroidKLintShowToastInspection() { - super(AndroidBundle.message("android.lint.inspections.show.toast"), ToastDetector.ISSUE); - } - } - - public static class AndroidKLintSimpleDateFormatInspection extends AndroidLintInspectionBase { - public AndroidKLintSimpleDateFormatInspection() { - super(AndroidBundle.message("android.lint.inspections.simple.date.format"), DateFormatDetector.DATE_FORMAT); - } - } - - // Maybe not relevant - public static class AndroidKLintStopShipInspection extends AndroidLintInspectionBase { - public AndroidKLintStopShipInspection() { - super(AndroidBundle.message("android.lint.inspections.stop.ship"), CommentDetector.STOP_SHIP); - } - - } - - public static class AndroidKLintSupportAnnotationUsageInspection extends AndroidLintInspectionBase { - public AndroidKLintSupportAnnotationUsageInspection() { - super("Incorrect support annotation usage", AnnotationDetector.ANNOTATION_USAGE); - } - } - - public static class AndroidKLintUniqueConstantsInspection extends AndroidLintInspectionBase { - public AndroidKLintUniqueConstantsInspection() { - super(AndroidBundle.message("android.lint.inspections.unique.constants"), AnnotationDetector.UNIQUE); - } - } - - public static class AndroidKLintUnlocalizedSmsInspection extends AndroidLintInspectionBase { - public AndroidKLintUnlocalizedSmsInspection() { - super(AndroidBundle.message("android.lint.inspections.unlocalized.sms"), NonInternationalizedSmsDetector.ISSUE); - } - } - public static class AndroidKLintWorldReadableFilesInspection extends AndroidLintInspectionBase { - public AndroidKLintWorldReadableFilesInspection() { - super(AndroidBundle.message("android.lint.inspections.world.readable.files"), SecurityDetector.WORLD_READABLE); - } - } - public static class AndroidKLintWrongCallInspection extends AndroidLintInspectionBase { - public AndroidKLintWrongCallInspection() { - super(AndroidBundle.message("android.lint.inspections.wrong.call"), WrongCallDetector.ISSUE); - } - - } - - public static class AndroidKLintPendingBindingsInspection extends AndroidLintInspectionBase { - public AndroidKLintPendingBindingsInspection() { - super("Missing Pending Bindings", RecyclerViewDetector.DATA_BINDER); - } - } - - public static class AndroidKLintUnsafeDynamicallyLoadedCodeInspection extends AndroidLintInspectionBase { - public AndroidKLintUnsafeDynamicallyLoadedCodeInspection() { - super("load used to dynamically load code", UnsafeNativeCodeDetector.LOAD); - } - } - - public static class AndroidKLintUnsafeNativeCodeLocationInspection extends AndroidLintInspectionBase { - public AndroidKLintUnsafeNativeCodeLocationInspection() { - super("Native code outside library directory", UnsafeNativeCodeDetector.UNSAFE_NATIVE_CODE_LOCATION); - } - } - - public static class AndroidKLintUnsafeProtectedBroadcastReceiverInspection extends AndroidLintInspectionBase { - public AndroidKLintUnsafeProtectedBroadcastReceiverInspection() { - super("Unsafe Protected BroadcastReceiver", UnsafeBroadcastReceiverDetector.ACTION_STRING); - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintQuickFix.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintQuickFix.java deleted file mode 100644 index 967631a..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintQuickFix.java +++ /dev/null
@@ -1,16 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.intellij.psi.PsiElement; -import org.jetbrains.annotations.NotNull; - -public interface AndroidLintQuickFix { - AndroidLintQuickFix[] EMPTY_ARRAY = new AndroidLintQuickFix[0]; - - void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull AndroidQuickfixContexts.Context context); - - boolean isApplicable(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @NotNull AndroidQuickfixContexts.ContextType contextType); - - @NotNull - String getName(); -} -
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintUtil.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintUtil.java deleted file mode 100644 index 9c727a9..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintUtil.java +++ /dev/null
@@ -1,57 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.tools.klint.detector.api.Issue; -import com.intellij.codeHighlighting.HighlightDisplayLevel; -import com.intellij.codeInsight.daemon.HighlightDisplayKey; -import com.intellij.codeInspection.InspectionProfile; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.profile.codeInspection.InspectionProjectProfileManager; -import com.intellij.psi.PsiElement; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class AndroidLintUtil { - @NonNls static final String ATTR_VALUE_VERTICAL = "vertical"; - @NonNls static final String ATTR_VALUE_WRAP_CONTENT = "wrap_content"; - @NonNls static final String ATTR_LAYOUT_HEIGHT = "layout_height"; - @NonNls static final String ATTR_LAYOUT_WIDTH = "layout_width"; - @NonNls static final String ATTR_ORIENTATION = "orientation"; - - private AndroidLintUtil() { - } - - @Nullable - public static Pair<AndroidLintInspectionBase, HighlightDisplayLevel> getHighlighLevelAndInspection(@NotNull Project project, - @NotNull Issue issue, - @NotNull PsiElement context) { - final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(project, issue); - if (inspectionShortName == null) { - return null; - } - - final HighlightDisplayKey key = HighlightDisplayKey.find(inspectionShortName); - if (key == null) { - return null; - } - - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(context.getProject()).getInspectionProfile(); - if (!profile.isToolEnabled(key, context)) { - if (!issue.isEnabledByDefault()) { - // Lint will skip issues (and not report them) for issues that have been disabled, - // except for those issues that are explicitly enabled via Gradle. Therefore, if - // we get this far, lint has found this issue to be explicitly enabled, so we let - // that setting override a local enabled/disabled state in the IDE profile. - } else { - return null; - } - } - - final AndroidLintInspectionBase inspection = (AndroidLintInspectionBase)profile.getUnwrappedTool(inspectionShortName, context); - if (inspection == null) return null; - final HighlightDisplayLevel errorLevel = profile.getErrorLevel(key, context); - return Pair.create(inspection, - errorLevel != null ? errorLevel : HighlightDisplayLevel.WARNING); - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidQuickfixContexts.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidQuickfixContexts.java deleted file mode 100644 index f4c9238..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidQuickfixContexts.java +++ /dev/null
@@ -1,72 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.intellij.openapi.editor.Editor; -import org.jetbrains.annotations.NotNull; - -public class AndroidQuickfixContexts { - public static abstract class Context { - private final ContextType myType; - - private Context(@NotNull ContextType type) { - myType = type; - } - - @NotNull - public ContextType getType() { - return myType; - } - } - - public static class ContextType { - private ContextType() { - } - } - - public static class BatchContext extends Context { - public static final ContextType TYPE = new ContextType(); - private static final BatchContext INSTANCE = new BatchContext(); - - private BatchContext() { - super(TYPE); - } - - @NotNull - public static BatchContext getInstance() { - return INSTANCE; - } - } - - public static class EditorContext extends Context { - public static final ContextType TYPE = new ContextType(); - private final Editor myEditor; - - private EditorContext(@NotNull Editor editor) { - super(TYPE); - myEditor = editor; - } - - @NotNull - public Editor getEditor() { - return myEditor; - } - - @NotNull - public static EditorContext getInstance(@NotNull Editor editor) { - return new EditorContext(editor); - } - } - - public static class DesignerContext extends Context { - public static final ContextType TYPE = new ContextType(); - private static final DesignerContext INSTANCE = new DesignerContext(); - - private DesignerContext() { - super(TYPE); - } - - @NotNull - public static DesignerContext getInstance() { - return INSTANCE; - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt deleted file mode 100644 index e3509d2..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt +++ /dev/null
@@ -1,24 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * 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.klint - -import com.android.sdklib.SdkVersionInfo - - -fun getVersionField(api: Int, fullyQualified: Boolean): String = SdkVersionInfo.getBuildCode(api)?.let { - if (fullyQualified) "android.os.Build.VERSION_CODES.$it" else it -} ?: api.toString() \ No newline at end of file
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiConverter.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiConverter.java deleted file mode 100644 index d0b29f0..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiConverter.java +++ /dev/null
@@ -1,1302 +0,0 @@ -/* - * 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 org.jetbrains.android.inspections.klint; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.xml.*; -import com.intellij.util.containers.HashMap; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.w3c.dom.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Converter which takes a PSI hierarchy for an XML file or document, and - * creates a corresponding W3C DOM tree. It attempts to delegate as much - * as possible to the original PSI tree. Note also that the {@link #getTextRange(Node)}} - * method allows us to look up source offsets for the DOM nodes (which plain XML - * DOM parsers do not). - * <p> - * NOTE: The tree may not be semantically equivalent to the XML PSI structure; this - * converter only attempts to make the DOM correct as far as Lint cares (meaning that it - * only worries about the details Lint cares about; currently this means it only wraps elements, - * text and comment nodes.) - */ -class DomPsiConverter { - private DomPsiConverter() { - } - - /** - * Convert the given {@link XmlFile} to a DOM tree - * - * @param xmlFile the file to be converted - * @return a corresponding W3C DOM tree - */ - @Nullable - public static Document convert(@NotNull XmlFile xmlFile) { - try { - XmlDocument xmlDocument = xmlFile.getDocument(); - if (xmlDocument == null) { - return null; - } - - return convert(xmlDocument); - } - catch (ProcessCanceledException e) { - // Ignore: common occurrence, e.g. we're running lint as part of an editor background - // and while lint is running the user switches files: the inspections framework will - // then cancel the process from within the PSI machinery (which asks the progress manager - // periodically whether the operation is cancelled) and we find ourselves here - return null; - } - catch (Exception e) { - String path = xmlFile.getName(); - VirtualFile virtualFile = xmlFile.getVirtualFile(); - if (virtualFile != null) { - path = virtualFile.getPath(); - } - throw new RuntimeException("Could not convert file " + path, e); - } - } - - /** - * Convert the given {@link XmlDocument} to a DOM tree - * - * @param document the document to be converted - * @return a corresponding W3C DOM tree - */ - @Nullable - private static Document convert(@NotNull XmlDocument document) { - return new DomDocument(document); - } - - /** Gets the {@link TextRange} for a {@link Node} created with this converter */ - @NotNull - public static TextRange getTextRange(@NotNull Node node) { - assert node instanceof DomNode; - DomNode domNode = (DomNode)node; - XmlElement element = domNode.myElement; - - // For elements, don't highlight the entire element range; instead, just - // highlight the element name - if (node.getNodeType() == Node.ELEMENT_NODE) { - return getTextNameRange(node); - } - - return element.getTextRange(); - } - - /** Gets the {@link TextRange} for a {@link Node} created with this converter */ - @NotNull - public static TextRange getTextNameRange(@NotNull Node node) { - assert node instanceof DomNode; - DomNode domNode = (DomNode)node; - XmlElement element = domNode.myElement; - - // For elements and attributes, don't highlight the entire element range; instead, just - // highlight the element name - if (node.getNodeType() == Node.ELEMENT_NODE && element instanceof XmlTag) { - String tag = node.getNodeName(); - int index = element.getText().indexOf(tag); - if (index != -1) { - TextRange textRange = element.getTextRange(); - int start = textRange.getStartOffset() + index; - return new TextRange(start, start + tag.length()); - } - } else if (node.getNodeType() == Node.ATTRIBUTE_NODE && element instanceof XmlAttribute) { - XmlElement nameElement = ((XmlAttribute)element).getNameElement(); - if (nameElement != null) { - return nameElement.getTextRange(); - } - } - - return element.getTextRange(); - } - - /** Gets the {@link TextRange} for the value region of a {@link Node} created with this converter */ - @NotNull - public static TextRange getTextValueRange(@NotNull Node node) { - assert node instanceof DomNode; - DomNode domNode = (DomNode)node; - XmlElement element = domNode.myElement; - TextRange textRange = element.getTextRange(); - - // For attributes, don't highlight the entire element range; instead, just - // highlight the value range - if (node.getNodeType() == Node.ATTRIBUTE_NODE && element instanceof XmlAttribute) { - XmlAttributeValue valueElement = ((XmlAttribute)element).getValueElement(); - if (valueElement != null) { - return valueElement.getValueTextRange(); - } - } - - return textRange; - } - - private static final NodeList EMPTY = new NodeList() { - @NotNull - @Override - public Node item(int i) { - throw new IllegalArgumentException(); - } - - @Override - public int getLength() { - return 0; - } - }; - - @Nullable - private static final NamedNodeMap EMPTY_ATTRIBUTES = new NamedNodeMap() { - @Override - public int getLength() { - return 0; - } - - @Nullable - @Override - public Node getNamedItem(String s) { - return null; - } - - @Nullable - @Override - public Node getNamedItemNS(String s, String s2) throws DOMException { - return null; - } - - @NotNull - @Override - public Node setNamedItem(Node node) throws DOMException { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Node removeNamedItem(String s) throws DOMException { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Node item(int i) { - throw new UnsupportedOperationException(); // Not supported - } - - @Nullable - @Override - public Node setNamedItemNS(Node node) throws DOMException { - return null; - } - - @NotNull - @Override - public Node removeNamedItemNS(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Not supported - } - }; - - private static class DomNodeList implements NodeList { - protected final List<DomNode> myChildren = new ArrayList<DomNode>(); - - @NotNull - @Override - public Node item(int i) { - return myChildren.get(i); - } - - @Override - public int getLength() { - return myChildren.size(); - } - - void add(@NotNull DomNode node) { - int size = myChildren.size(); - if (size > 0) { - DomNode last = myChildren.get(size - 1); - node.myPrevious = last; - last.myNext = node; - } - myChildren.add(node); - } - } - - private static class DomNamedNodeMap implements NamedNodeMap { - @NotNull protected final Map<String, DomNode> myMap; - @NotNull protected final Map<String, Map<String, DomNode>> myNsMap; - @NotNull protected final List<DomNode> mItems; - - private DomNamedNodeMap(@NotNull DomElement element, @NotNull XmlAttribute[] attributes) { - int count = attributes.length; - int namespaceCount = 0; - for (XmlAttribute attribute : attributes) { - if (!attribute.getNamespace().isEmpty()) { - namespaceCount++; - } - } - myMap = new HashMap<String, DomNode>(count - namespaceCount); - myNsMap = new HashMap<String, Map<String, DomNode>>(namespaceCount); - mItems = new ArrayList<DomNode>(count); - - assert element.myOwner != null; // True for elements, not true for non-Element nodes - for (XmlAttribute attribute : attributes) { - DomAttr attr = new DomAttr(element.myOwner, element, attribute); - mItems.add(attr); - String namespace = attribute.getNamespace(); - if (!namespace.isEmpty()) { - Map<String, DomNode> map = myNsMap.get(namespace); - if (map == null) { - map = new HashMap<String, DomNode>(); - myNsMap.put(namespace, map); - } - map.put(attribute.getLocalName(), attr); - } else { - myMap.put(attribute.getName(), attr); - } - } - } - - @Override - public Node item(int i) { - return mItems.get(i); - } - - @Override - public int getLength() { - return mItems.size(); - } - - @Override - public Node getNamedItem(@NotNull String s) { - return myMap.get(s); - } - - @Nullable - @Override - public Node getNamedItemNS(@NotNull String namespace, @NotNull String name) throws DOMException { - Map<String, DomNode> map = myNsMap.get(namespace); - if (map != null) { - return map.get(name); - } - return null; - } - - @NotNull - @Override - public Node setNamedItem(Node node) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node removeNamedItem(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node setNamedItemNS(Node node) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node removeNamedItemNS(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - } - - @SuppressWarnings({"UnusedParameters", "UnusedDeclaration"}) // Specifies methods shared by children - private static abstract class DomNode implements Node { - @Nullable protected final Document myOwner; - @Nullable protected final DomNode myParent; - @NotNull protected final XmlElement myElement; - @Nullable protected NodeList myChildren; - @Nullable protected DomNode myNext; - @Nullable protected DomNode myPrevious; - - protected DomNode(@Nullable Document owner, @Nullable DomNode parent, @NotNull XmlElement element) { - myOwner = owner; - myParent = parent; - myElement = element; - } - - @Nullable - @Override - public Node getParentNode() { - return myParent; - } - - @NotNull - @Override - public NodeList getChildNodes() { - if (myChildren == null) { - PsiElement[] children = myElement.getChildren(); - if (children.length > 0) { - DomNodeList list = new DomNodeList(); - myChildren = list; - // True except for in DomDocument, which has custom getChildNodes - assert myOwner != null; - - for (PsiElement child : children) { - if (child instanceof XmlTag) { - list.add(new DomElement(myOwner, this, (XmlTag) child)); - } else if (child instanceof XmlText) { - list.add(new DomText(myOwner, this, (XmlText) child)); - } else if (child instanceof XmlComment) { - list.add(new DomComment(myOwner, this, (XmlComment) child)); - } else { - // Skipping other types for now; lint doesn't care about them. - // TODO: Consider whether we need CDATA. - } - } - } else { - myChildren = EMPTY; - } - } - return myChildren; - } - - @Nullable - @Override - public Node getFirstChild() { - NodeList childNodes = getChildNodes(); - if (childNodes.getLength() > 0) { - return childNodes.item(0); - } - return null; - } - - @Nullable - @Override - public Node getLastChild() { - NodeList childNodes = getChildNodes(); - if (childNodes.getLength() > 0) { - return childNodes.item(0); - } - return null; - } - - @Nullable - @Override - public Node getPreviousSibling() { - return myPrevious; - } - - @Nullable - @Override - public Node getNextSibling() { - return myNext; - } - - @Nullable - @Override - public NamedNodeMap getAttributes() { - throw new UnsupportedOperationException(); // Only supported on elements - } - - @Nullable - @Override - public Document getOwnerDocument() { - return myOwner; - } - - @Override - public void setNodeValue(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node insertBefore(Node node, Node node2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node replaceChild(Node node, Node node2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node removeChild(Node node) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node appendChild(Node node) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public boolean hasChildNodes() { - return getChildNodes().getLength() > 0; - } - - @NotNull - @Override - public Node cloneNode(boolean b) { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public void normalize() { - } - - @Override - public boolean isSupported(String s, String s2) { - return false; - } - - @NotNull - @Override - public String getNamespaceURI() { - throw new UnsupportedOperationException(); // Only supported on elements in lint - } - - @NotNull - @Override - public String getPrefix() { - throw new UnsupportedOperationException(); // Only supported on elements in lint - } - - @Override - public void setPrefix(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Nullable - @Override - public String getLocalName() { - return null; - } - - @Override - public boolean hasAttributes() { - return false; - } - - @Nullable - @Override - public String getBaseURI() { - return null; - } - - @Override - public short compareDocumentPosition(Node node) throws DOMException { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public String getTextContent() throws DOMException { - return myElement.getText(); - } - - @Override - public void setTextContent(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public boolean isSameNode(Node node) { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public String lookupPrefix(String s) { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public boolean isDefaultNamespace(String s) { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public String lookupNamespaceURI(String s) { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public boolean isEqualNode(Node node) { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Object getFeature(String s, String s2) { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Object setUserData(String s, Object o, UserDataHandler userDataHandler) { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Object getUserData(String s) { - throw new UnsupportedOperationException(); // Not supported - } - - // From CharacterData - - @NotNull - public String getData() throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - public void setData(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - public int getLength() { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - public String substringData(int i, int i2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - public void appendData(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - public void insertData(int i, String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - public void deleteData(int i, int i2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - public void replaceData(int i, int i2, String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - } - - private static class DomDocument extends DomNode implements Document { - @NotNull private final XmlDocument myPsiDocument; - @Nullable private DomElement myRoot; - - private DomDocument(@NotNull XmlDocument document) { - super(null, null, document); - myPsiDocument = document; - } - - // From org.w3c.dom.Node: - - @Nullable - @Override - public String getNodeName() { - return null; - } - - @Nullable - @Override - public String getNodeValue() throws DOMException { - return null; - } - - @Override - public short getNodeType() { - return Node.DOCUMENT_NODE; - } - - @NotNull - @Override - public NodeList getChildNodes() { - if (myChildren == null) { - DomNodeList list = new DomNodeList(); - myChildren = list; - DomNode documentElement = (DomNode)getDocumentElement(); - if (documentElement != null) { - list.add(documentElement); - } - } - - return myChildren; - } - - // From org.w3c.dom.Document: - - @NotNull - @Override - public DocumentType getDoctype() { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public DOMImplementation getImplementation() { - throw new UnsupportedOperationException(); // Not supported - } - - @Nullable - @Override - public Element getDocumentElement() { - if (myRoot == null) { - XmlTag rootTag = myPsiDocument.getRootTag(); - if (rootTag == null) { - return null; - } - myRoot = new DomElement(this, this, rootTag); - } - - return myRoot; - } - - @NotNull - @Override - public NodeList getElementsByTagName(String s) { - Element root = getDocumentElement(); - if (root != null) { - return root.getElementsByTagName(s); - } - return EMPTY; - } - - @NotNull - @Override - public NodeList getElementsByTagNameNS(String s, String s2) { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Element createElement(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public DocumentFragment createDocumentFragment() { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Text createTextNode(String s) { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Comment createComment(String s) { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public CDATASection createCDATASection(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public ProcessingInstruction createProcessingInstruction(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Attr createAttribute(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public EntityReference createEntityReference(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Node importNode(Node node, boolean b) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Element createElementNS(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Attr createAttributeNS(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Element getElementById(String s) { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public String getInputEncoding() { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public String getXmlEncoding() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public boolean getXmlStandalone() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public void setXmlStandalone(boolean b) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public String getXmlVersion() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public void setXmlVersion(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public boolean getStrictErrorChecking() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public void setStrictErrorChecking(boolean b) { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public String getDocumentURI() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public void setDocumentURI(String s) { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Node adoptNode(Node node) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public DOMConfiguration getDomConfig() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public void normalizeDocument() { - } - - @NotNull - @Override - public Node renameNode(Node node, String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Not supported - } - } - - private static class DomElement extends DomNode implements Element { - private final XmlTag myTag; - @Nullable private NamedNodeMap myAttributes; - - private DomElement(@NotNull Document owner, @NotNull DomNode parent, @NotNull XmlTag tag) { - super(owner, parent, tag); - myTag = tag; - } - - // From org.w3c.dom.Node: - - @NotNull - @Override - public String getNodeName() { - return getTagName(); - } - - @Nullable - @Override - public String getNodeValue() throws DOMException { - return null; - } - - @Override - public short getNodeType() { - return Node.ELEMENT_NODE; - } - - @NotNull - @Override - public NamedNodeMap getAttributes() { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<NamedNodeMap>() { - @Override - public NamedNodeMap compute() { - return getAttributes(); - } - }); - } - - if (myAttributes == null) { - XmlAttribute[] attributes = myTag.getAttributes(); - if (attributes.length == 0) { - myAttributes = EMPTY_ATTRIBUTES; - } else { - myAttributes = new DomNamedNodeMap(this, attributes); - } - } - - return myAttributes; - } - - // From org.w3c.dom.Element: - - @NotNull - @Override - public String getTagName() { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getTagName(); - } - }); - } - - return myTag.getName(); - } - - @NotNull - @Override - public String getAttribute(@NotNull String name) { - Node node = getAttributes().getNamedItem(name); - if (node != null) { - return node.getNodeValue(); - } - return ""; - } - - @NotNull - @Override - public String getAttributeNS(@NotNull String namespace, @NotNull String name) throws DOMException { - Node node = getAttributes().getNamedItemNS(namespace, name); - if (node != null) { - return node.getNodeValue(); - } - return ""; - } - - @Nullable - @Override - public Attr getAttributeNodeNS(@NotNull String namespace, @NotNull String name) throws DOMException { - Node node = getAttributes().getNamedItemNS(namespace, name); - if (node != null) { - return (Attr)node; - } - return null; - } - - @Nullable - @Override - public Attr getAttributeNode(@NotNull String name) { - Node node = getAttributes().getNamedItem(name); - if (node != null) { - return (Attr)node; - } - return null; - } - - @Override - public boolean hasAttribute(@NotNull String name) { - return getAttributes().getNamedItem(name) != null; - } - - @Override - public boolean hasAttributeNS(@NotNull String namespace, @NotNull String name) throws DOMException { - return getAttributes().getNamedItemNS(namespace, name) != null; - } - - @NotNull - @Override - public NodeList getElementsByTagName(@NotNull String s) { - NodeList childNodes = getChildNodes(); - if (childNodes == EMPTY) { - return EMPTY; - } - DomNodeList matches = new DomNodeList(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node node = childNodes.item(i); - if (s.equals(node.getNodeName())) { - matches.add((DomNode)node); - } - } - - return matches; - } - - @NotNull - @Override - public NodeList getElementsByTagNameNS(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Attr setAttributeNode(Attr attr) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Attr removeAttributeNode(Attr attr) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public void setAttributeNS(String s, String s2, String s3) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public void removeAttributeNS(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public void setAttribute(String s, String s2) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public void removeAttribute(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Attr setAttributeNodeNS(Attr attr) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public TypeInfo getSchemaTypeInfo() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public void setIdAttribute(String s, boolean b) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public void setIdAttributeNS(String s, String s2, boolean b) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public void setIdAttributeNode(Attr attr, boolean b) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - } - - private static class DomText extends DomNode implements Text { - @NotNull private final XmlText myText; - - private DomText(@NotNull Document owner, @NotNull DomNode parent, @NotNull XmlText text) { - super(owner, parent, text); - myText = text; - } - - // From org.w3c.dom.Node: - - @Nullable - @Override - public String getNodeName() { - return null; - } - - @NotNull - @Override - public String getNodeValue() throws DOMException { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getNodeValue(); - } - }); - } - - return myText.getText(); - } - - @Override - public short getNodeType() { - return Node.TEXT_NODE; - } - - // From org.w3c.dom.Text: - - @NotNull - @Override - public Text splitText(int i) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @Override - public boolean isElementContentWhitespace() { - String s = myText.getText(); - for (int i = 0, n = s.length(); i < n; i++) { - if (!Character.isWhitespace(s.charAt(i))) { - return false; - } - } - - return true; - } - - @NotNull - @Override - public String getWholeText() { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public Text replaceWholeText(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - } - - private static class DomComment extends DomNode implements Comment { - @NotNull private final XmlComment myComment; - - private DomComment(@NotNull Document owner, @NotNull DomNode parent, @NotNull XmlComment comment) { - super(owner, parent, comment); - myComment = comment; - } - - // From org.w3c.dom.Node: - - @Nullable - @Override - public String getNodeName() { - return null; - } - - @NotNull - @Override - public String getNodeValue() throws DOMException { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getNodeValue(); - } - }); - } - - return myComment.getText(); - } - - @Override - public short getNodeType() { - return Node.COMMENT_NODE; - } - - @NotNull - @Override - public String getTextContent() throws DOMException { - return getNodeValue(); - } - } - - private static class DomAttr extends DomNode implements Attr { - @NotNull private final DomElement myOwner; - @NotNull private final XmlAttribute myAttribute; - - private DomAttr(@NotNull Document document, @NotNull DomElement owner, @NotNull XmlAttribute attribute) { - super(document, null, attribute); - myOwner = owner; - myAttribute = attribute; - } - - // From org.w3c.dom.Node: - - @NotNull - @Override - public String getNodeName() { - return getName(); - } - - @NotNull - @Override - public String getNodeValue() throws DOMException { - return getValue(); - } - - @Override - public short getNodeType() { - return Node.ATTRIBUTE_NODE; - } - - // From org.w3c.dom.Attr: - - @NotNull - @Override - public String getName() { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getName(); - } - }); - } - return myAttribute.getName(); - } - - @Override - public boolean getSpecified() { - throw new UnsupportedOperationException(); // Not supported - } - - @NotNull - @Override - public String getValue() { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getValue(); - } - }); - } - - String value = myAttribute.getValue(); - if (value == null) { - value = ""; - } - return value; - } - - @NotNull - @Override - public String getLocalName() { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getLocalName(); - } - }); - } - - return myAttribute.getLocalName(); - } - - @NotNull - @Override - public String getPrefix() { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getPrefix(); - } - }); - } - - return myAttribute.getNamespacePrefix(); - } - - @NotNull - @Override - public String getNamespaceURI() { - Application application = ApplicationManager.getApplication(); - if (!application.isReadAccessAllowed()) { - return application.runReadAction(new Computable<String>() { - @Override - public String compute() { - return getNamespaceURI(); - } - }); - } - - return myAttribute.getNamespace(); - } - - @Override - public void setValue(String s) throws DOMException { - throw new UnsupportedOperationException(); // Read-only bridge - } - - @NotNull - @Override - public Element getOwnerElement() { - return myOwner; - } - - @NotNull - @Override - public TypeInfo getSchemaTypeInfo() { - throw new UnsupportedOperationException(); // Not supported - } - - @Override - public boolean isId() { - throw new UnsupportedOperationException(); // Not supported - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiParser.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiParser.java deleted file mode 100644 index 1c0ca05..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiParser.java +++ /dev/null
@@ -1,183 +0,0 @@ -/* - * 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 org.jetbrains.android.inspections.klint; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.client.api.XmlParser; -import com.android.tools.klint.detector.api.DefaultPosition; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Position; -import com.android.tools.klint.detector.api.XmlContext; -import com.intellij.openapi.application.AccessToken; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiFile; -import com.intellij.psi.xml.XmlFile; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import java.io.File; - -/** - * Lint parser which reads in a DOM from a given file, by mapping to the underlying XML PSI structure - */ -class DomPsiParser extends XmlParser { - private final LintClient myClient; - private AccessToken myReadLock; - - public DomPsiParser(LintClient client) { - myClient = client; - } - - @Override - public void dispose(@NonNull XmlContext context, @NonNull Document document) { - if (context.document != null) { - myReadLock.finish(); - myReadLock = null; - context.document = null; - } - } - - @Override - public int getNodeStartOffset(@NonNull XmlContext context, @NonNull Node node) { - TextRange textRange = DomPsiConverter.getTextRange(node); - return textRange.getStartOffset(); - } - - @Override - public int getNodeEndOffset(@NonNull XmlContext context, @NonNull Node node) { - TextRange textRange = DomPsiConverter.getTextRange(node); - return textRange.getEndOffset(); - } - - @Nullable - @Override - public Document parseXml(@NonNull final XmlContext context) { - assert myReadLock == null; - myReadLock = ApplicationManager.getApplication().acquireReadActionLock(); - Document document = parse(context); - if (document == null) { - myReadLock.finish(); - myReadLock = null; - } - return document; - } - - @Nullable - private Document parse(XmlContext context) { - // Should only be called from read thread - assert ApplicationManager.getApplication().isReadAccessAllowed(); - - final PsiFile psiFile = IntellijLintUtils.getPsiFile(context); - if (!(psiFile instanceof XmlFile)) { - return null; - } - XmlFile xmlFile = (XmlFile)psiFile; - - try { - return DomPsiConverter.convert(xmlFile); - } catch (Throwable t) { - myClient.log(t, "Failed converting PSI parse tree to DOM for file %1$s", - context.file.getPath()); - return null; - } - } - - @NonNull - @Override - public Location getLocation(@NonNull XmlContext context, @NonNull Node node) { - TextRange textRange = DomPsiConverter.getTextRange(node); - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); - Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); - return Location.create(context.file, start, end); - } - - @NonNull - @Override - public Location getLocation(@NonNull XmlContext context, @NonNull Node node, int startDelta, int endDelta) { - TextRange textRange = DomPsiConverter.getTextRange(node); - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset() + startDelta); - Position end = new DefaultPosition(-1, -1, textRange.getStartOffset() + endDelta); - return Location.create(context.file, start, end); - } - - @NonNull - @Override - public Location getNameLocation(@NonNull XmlContext context, @NonNull Node node) { - TextRange textRange = DomPsiConverter.getTextNameRange(node); - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); - Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); - return Location.create(context.file, start, end); - } - - @NonNull - @Override - public Location getValueLocation(@NonNull XmlContext context, @NonNull Attr node) { - TextRange textRange = DomPsiConverter.getTextValueRange(node); - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); - Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); - return Location.create(context.file, start, end); - } - - @NonNull - @Override - public Location.Handle createLocationHandle(@NonNull XmlContext context, @NonNull Node node) { - return new LocationHandle(context.file, node); - } - - private static class LocationHandle implements Location.Handle { - private final File myFile; - private final Node myNode; - private Object myClientData; - - public LocationHandle(File file, Node node) { - myFile = file; - myNode = node; - } - - @NonNull - @Override - public Location resolve() { - if (!ApplicationManager.getApplication().isReadAccessAllowed()) { - return ApplicationManager.getApplication().runReadAction(new Computable<Location>() { - @Override - public Location compute() { - return resolve(); - } - }); - } - TextRange textRange = DomPsiConverter.getTextRange(myNode); - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); - Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); - return Location.create(myFile, start, end); - } - - @Override - public void setClientData(@Nullable Object clientData) { - myClientData = clientData; - } - - @Override - @Nullable - public Object getClientData() { - return myClientData; - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IdeaJavaParser.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IdeaJavaParser.java deleted file mode 100644 index 5f468cc1..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IdeaJavaParser.java +++ /dev/null
@@ -1,192 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * 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.klint; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.JavaEvaluator; -import com.android.tools.klint.client.api.JavaParser; -import com.android.tools.klint.detector.api.JavaContext; -import com.android.tools.klint.detector.api.Location; -import com.android.tools.klint.detector.api.Severity; -import com.google.common.collect.Sets; -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.InheritanceUtil; -import lombok.ast.Node; -import lombok.ast.Position; -import org.jetbrains.uast.UastContext; - -import java.io.File; -import java.util.List; - -public class IdeaJavaParser extends JavaParser { - private final IntellijLintClient myClient; - private final Project myProject; - private final UastContext myContext; - private final JavaEvaluator myEvaluator; - - public IdeaJavaParser(IntellijLintClient client, Project myProject) { - this.myClient = client; - this.myProject = myProject; - this.myEvaluator = new MyJavaEvaluator(myProject); - - myContext = ServiceManager.getService(myProject, UastContext.class); - } - - @Override - public UastContext getUastContext() { - return myContext; - } - - @Override - public void prepareJavaParse(@NonNull List<JavaContext> contexts) { - - } - - @Override - public PsiJavaFile parseJavaToPsi(@NonNull JavaContext context) { - PsiFile psiFile = IntellijLintUtils.getPsiFile(context); - if (!(psiFile instanceof PsiJavaFile)) { - return null; - } - return (PsiJavaFile)psiFile; - } - - @Override - public JavaEvaluator getEvaluator() { - return myEvaluator; - } - - @Override - public Project getIdeaProject() { - return myProject; - } - - @Override - public Location getRangeLocation( - @NonNull JavaContext context, @NonNull Node from, int fromDelta, @NonNull Node to, int toDelta - ) { - Position position1 = from.getPosition(); - Position position2 = to.getPosition(); - if (position1 == null) { - return getLocation(context, to); - } - else if (position2 == null) { - return getLocation(context, from); - } - - int start = Math.max(0, from.getPosition().getStart() + fromDelta); - int end = to.getPosition().getEnd() + toDelta; - return Location.create(context.file, null, start, end); - } - - @Override - public Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node) { - return new LocationHandle(context.file, node); - } - - @Override - public void runReadAction(@NonNull Runnable runnable) { - ApplicationManager.getApplication().runReadAction(runnable); - } - - /* Handle for creating positions cheaply and returning full fledged locations later */ - private class LocationHandle implements Location.Handle { - private final File myFile; - private final Node myNode; - private Object mClientData; - - public LocationHandle(File file, Node node) { - myFile = file; - myNode = node; - } - - @NonNull - @Override - public Location resolve() { - Position pos = myNode.getPosition(); - if (pos == null) { - myClient.log(Severity.WARNING, null, "No position data found for node %1$s", myNode); - return Location.create(myFile); - } - return Location.create(myFile, null /*contents*/, pos.getStart(), pos.getEnd()); - } - - @Override - public void setClientData(@Nullable Object clientData) { - mClientData = clientData; - } - - @Override - @Nullable - public Object getClientData() { - return mClientData; - } - } - - private static class MyJavaEvaluator extends JavaEvaluator { - private final Project myProject; - - public MyJavaEvaluator(Project project) { - myProject = project; - } - - @Nullable - @Override - public PsiClass findClass(@NonNull String qualifiedName) { - return JavaPsiFacade.getInstance(myProject).findClass(qualifiedName, GlobalSearchScope.allScope(myProject)); - } - - @Nullable - @Override - public PsiClassType getClassType(@Nullable PsiClass cls) { - return cls != null ? JavaPsiFacade.getElementFactory(myProject).createType(cls) : null; - } - - @NonNull - @Override - public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner) { - return AnnotationUtil.getAllAnnotations(owner, true, null, true); - } - - @Nullable - @Override - public PsiAnnotation findAnnotationInHierarchy(@NonNull PsiModifierListOwner listOwner, @NonNull String... annotationNames) { - return AnnotationUtil.findAnnotationInHierarchy(listOwner, Sets.newHashSet(annotationNames)); - } - - @Nullable - @Override - public PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, @NonNull String... annotationNames) { - return AnnotationUtil.findAnnotation(listOwner, false, annotationNames); - } - - @Nullable - @Override - public File getFile(@NonNull PsiFile file) { - VirtualFile virtualFile = file.getVirtualFile(); - return virtualFile != null ? VfsUtilCore.virtualToIoFile(virtualFile) : null; - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java deleted file mode 100644 index 61ed0c9..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java +++ /dev/null
@@ -1,862 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.annotations.NonNull; -import com.android.builder.model.AndroidProject; -import com.android.builder.model.LintOptions; -import com.android.ide.common.repository.ResourceVisibilityLookup; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceFile; -import com.android.ide.common.res2.ResourceItem; -import com.android.sdklib.repository.AndroidSdkHandler; -import com.android.tools.idea.gradle.util.Projects; -import com.android.tools.idea.project.AndroidProjectInfo; -import com.android.tools.idea.res.AppResourceRepository; -import com.android.tools.idea.res.LocalResourceRepository; -import com.android.tools.idea.sdk.IdeSdks; -import com.android.tools.idea.welcome.install.AndroidSdk; -import com.android.tools.klint.checks.ApiLookup; -import com.android.tools.klint.client.api.*; -import com.android.tools.klint.detector.api.*; -import com.google.common.base.Charsets; -import com.google.common.collect.Lists; -import com.google.common.io.Files; -import com.intellij.analysis.AnalysisScope; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.editor.event.DocumentListener; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.module.ModuleUtilCore; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.psi.xml.XmlElement; -import com.intellij.psi.xml.XmlTag; -import com.intellij.util.PathUtil; -import com.intellij.util.containers.HashMap; -import com.intellij.util.lang.UrlClassLoader; -import com.intellij.util.net.HttpConfigurable; -import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.facet.AndroidRootUtil; -import org.jetbrains.android.sdk.AndroidSdkData; -import org.jetbrains.android.sdk.AndroidSdkType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; -import java.util.*; - -import static com.android.tools.klint.detector.api.TextFormat.RAW; -import static org.jetbrains.android.inspections.klint.IntellijLintIssueRegistry.CUSTOM_ERROR; -import static org.jetbrains.android.inspections.klint.IntellijLintIssueRegistry.CUSTOM_WARNING; - -/** - * Implementation of the {@linkplain LintClient} API for executing lint within the IDE: - * reading files, reporting issues, logging errors, etc. - */ -public class IntellijLintClient extends LintClient implements Disposable { - protected static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.IntellijLintClient"); - - @NonNull protected Project myProject; - @Nullable protected Map<com.android.tools.klint.detector.api.Project, Module> myModuleMap; - - public IntellijLintClient(@NonNull Project project) { - super(CLIENT_STUDIO); - myProject = project; - } - - /** Creates a lint client for batch inspections */ - public static IntellijLintClient forBatch(@NotNull Project project, - @NotNull Map<Issue, Map<File, List<ProblemData>>> problemMap, - @NotNull AnalysisScope scope, - @NotNull List<Issue> issues) { - return new BatchLintClient(project, problemMap, scope, issues); - } - - /** - * Returns an {@link ApiLookup} service. - * - * @param project the project to use for locating the Android SDK - * @return an API lookup if one can be found - */ - @Nullable - public static ApiLookup getApiLookup(@NotNull Project project) { - return ApiLookup.get(new IntellijLintClient(project)); - } - - /** - * Creates a lint client used for in-editor single file lint analysis (e.g. background checking while user is editing.) - */ - public static IntellijLintClient forEditor(@NotNull State state) { - return new EditorLintClient(state); - } - - @Nullable - protected Module findModuleForLintProject(@NotNull Project project, - @NotNull com.android.tools.klint.detector.api.Project lintProject) { - if (myModuleMap != null) { - Module module = myModuleMap.get(lintProject); - if (module != null) { - return module; - } - } - final File dir = lintProject.getDir(); - final VirtualFile vDir = LocalFileSystem.getInstance().findFileByIoFile(dir); - return vDir != null ? ModuleUtilCore.findModuleForFile(vDir, project) : null; - } - - void setModuleMap(@Nullable Map<com.android.tools.klint.detector.api.Project, Module> moduleMap) { - myModuleMap = moduleMap; - } - - @NonNull - @Override - public Configuration getConfiguration(@NonNull com.android.tools.klint.detector.api.Project project, @Nullable final LintDriver driver) { - if (project.isGradleProject() && project.isAndroidProject() && !project.isLibrary()) { - AndroidProject model = project.getGradleProjectModel(); - if (model != null) { - try { - LintOptions lintOptions = model.getLintOptions(); - final Map<String, Integer> overrides = lintOptions.getSeverityOverrides(); - if (overrides != null && !overrides.isEmpty()) { - return new DefaultConfiguration(this, project, null) { - @NonNull - @Override - public Severity getSeverity(@NonNull Issue issue) { - Integer severity = overrides.get(issue.getId()); - if (severity != null) { - switch (severity.intValue()) { - case LintOptions.SEVERITY_FATAL: - return Severity.FATAL; - case LintOptions.SEVERITY_ERROR: - return Severity.ERROR; - case LintOptions.SEVERITY_WARNING: - return Severity.WARNING; - case LintOptions.SEVERITY_INFORMATIONAL: - return Severity.INFORMATIONAL; - case LintOptions.SEVERITY_IGNORE: - default: - return Severity.IGNORE; - } - } - - // This is a LIST lookup. I should make this faster! - if (!getIssues().contains(issue) && (driver == null || !driver.isCustomIssue(issue))) { - return Severity.IGNORE; - } - - return super.getSeverity(issue); - } - }; - } - } catch (Exception e) { - LOG.error(e); - } - } - } - return new DefaultConfiguration(this, project, null) { - @Override - public boolean isEnabled(@NonNull Issue issue) { - if (getIssues().contains(issue) && super.isEnabled(issue)) { - return true; - } - - return driver != null && driver.isCustomIssue(issue); - } - }; - } - - @Override - public void report(@NonNull Context context, - @NonNull Issue issue, - @NonNull Severity severity, - @NonNull Location location, - @NonNull String message, - @NonNull TextFormat format) { - assert false : message; - } - - @NonNull protected List<Issue> getIssues() { - return Collections.emptyList(); - } - - @Nullable - protected Module getModule() { - return null; - } - - /** - * Recursively calls {@link #report} on the secondary location of this error, if any, which in turn may call it on a third - * linked location, and so on.This is necessary since IntelliJ problems don't have secondary locations; instead, we create one - * problem for each location associated with the lint error. - */ - protected void reportSecondary(@NonNull Context context, @NonNull Issue issue, @NonNull Severity severity, @NonNull Location location, - @NonNull String message, @NonNull TextFormat format) { - Location secondary = location.getSecondary(); - if (secondary != null) { - if (secondary.getMessage() != null) { - message = message + " (" + secondary.getMessage() + ")"; - } - report(context, issue, severity, secondary, message, format); - } - } - - @Override - public void log(@NonNull Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) { - if (severity == Severity.ERROR || severity == Severity.FATAL) { - if (format != null) { - LOG.error(String.format(format, args), exception); - } else if (exception != null) { - LOG.error(exception); - } - } else if (severity == Severity.WARNING) { - if (format != null) { - LOG.warn(String.format(format, args), exception); - } else if (exception != null) { - LOG.warn(exception); - } - } else { - if (format != null) { - LOG.info(String.format(format, args), exception); - } else if (exception != null) { - LOG.info(exception); - } - } - } - - @Override - public XmlParser getXmlParser() { - return new DomPsiParser(this); - } - - @Nullable - @Override - public JavaParser getJavaParser(@Nullable com.android.tools.klint.detector.api.Project project) { - return new IdeaJavaParser(this, myProject); - } - - @NonNull - @Override - public List<File> getJavaClassFolders(@NonNull com.android.tools.klint.detector.api.Project project) { - // todo: implement when class files checking detectors will be available - return Collections.emptyList(); - } - - @NonNull - @Override - public List<File> getJavaLibraries(@NonNull com.android.tools.klint.detector.api.Project project, boolean includeProvided) { - // todo: implement - return Collections.emptyList(); - } - - @Override - @NonNull - public String readFile(@NonNull final File file) { - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file); - if (vFile == null) { - LOG.debug("Cannot find file " + file.getPath() + " in the VFS"); - return ""; - } - - return ApplicationManager.getApplication().runReadAction(new Computable<String>() { - @Nullable - @Override - public String compute() { - final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile); - if (psiFile == null) { - LOG.info("Cannot find file " + file.getPath() + " in the PSI"); - return null; - } - else { - return psiFile.getText(); - } - } - }); - } - - @Override - public void dispose() { - } - - @Nullable - @Override - public File getSdkHome() { - Module module = getModule(); - if (module != null) { - Sdk moduleSdk = ModuleRootManager.getInstance(module).getSdk(); - if (moduleSdk != null && moduleSdk.getSdkType() instanceof AndroidSdkType) { - String path = moduleSdk.getHomePath(); - if (path != null) { - File home = new File(path); - if (home.exists()) { - return home; - } - } - } - } - - File sdkHome = super.getSdkHome(); - if (sdkHome != null) { - return sdkHome; - } - - for (Module m : ModuleManager.getInstance(myProject).getModules()) { - Sdk moduleSdk = ModuleRootManager.getInstance(m).getSdk(); - if (moduleSdk != null) { - if (moduleSdk.getSdkType() instanceof AndroidSdkType) { - String path = moduleSdk.getHomePath(); - if (path != null) { - File home = new File(path); - if (home.exists()) { - return home; - } - } - } - } - } - - return IdeSdks.getInstance().getAndroidSdkPath(); - } - - @Nullable - @Override - public AndroidSdkHandler getSdk() { - if (mSdk == null) { - Module module = getModule(); - AndroidSdkHandler sdk = getLocalSdk(module); - if (sdk != null) { - mSdk = sdk; - } else { - for (Module m : ModuleManager.getInstance(myProject).getModules()) { - sdk = getLocalSdk(m); - if (sdk != null) { - mSdk = sdk; - break; - } - } - - if (mSdk == null) { - mSdk = super.getSdk(); - } - } - } - - return mSdk; - } - - @Nullable - private static AndroidSdkHandler getLocalSdk(@Nullable Module module) { - if (module != null) { - AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet != null) { - AndroidSdkData sdkData = facet.getSdkData(); - if (sdkData != null) { - return sdkData.getSdkHandler(); - } - } - } - - return null; - } - - @Override - public boolean isGradleProject(com.android.tools.klint.detector.api.Project project) { - Module module = getModule(); - if (module != null) { - AndroidFacet facet = AndroidFacet.getInstance(module); - return facet != null && facet.requiresAndroidModel(); - } - return AndroidProjectInfo.getInstance(this.myProject).requiresAndroidModel(); - } - - // Overridden such that lint doesn't complain about missing a bin dir property in the event - // that no SDK is configured - @Override - @Nullable - public File findResource(@NonNull String relativePath) { - File top = getSdkHome(); - if (top != null) { - File file = new File(top, relativePath); - if (file.exists()) { - return file; - } - } - - return null; - } - - @Nullable private static volatile String ourSystemPath; - - @Override - @Nullable - public File getCacheDir(boolean create) { - final String path = ourSystemPath != null ? ourSystemPath : (ourSystemPath = PathUtil.getCanonicalPath(PathManager.getSystemPath())); - File lint = new File(path, "lint"); - if (create && !lint.exists()) { - lint.mkdirs(); - } - return lint; - } - - @Override - public boolean isProjectDirectory(@NonNull File dir) { - return new File(dir, Project.DIRECTORY_STORE_FOLDER).exists(); - } - - private static List<Issue> ourReportedCustomIssues; - - private static void recordCustomIssue(@NonNull Issue issue) { - if (ourReportedCustomIssues == null) { - ourReportedCustomIssues = Lists.newArrayList(); - } else if (ourReportedCustomIssues.contains(issue)) { - return; - } - ourReportedCustomIssues.add(issue); - } - - @Nullable - public static Issue findCustomIssue(@NonNull String errorMessage) { - if (ourReportedCustomIssues != null) { - // We stash the original id into the error message such that we can - // find it later - int begin = errorMessage.lastIndexOf('['); - int end = errorMessage.lastIndexOf(']'); - if (begin < end && begin != -1) { - String id = errorMessage.substring(begin + 1, end); - for (Issue issue : ourReportedCustomIssues) { - if (id.equals(issue.getId())) { - return issue; - } - } - } - } - - return null; - } - - /** - * A lint client used for in-editor single file lint analysis (e.g. background checking while user is editing.) - * <p> - * Since this applies only to a given file and module, it can take some shortcuts over what the general - * {@link BatchLintClient} has to do. - * */ - private static class EditorLintClient extends IntellijLintClient { - private final State myState; - - public EditorLintClient(@NotNull State state) { - super(state.getModule().getProject()); - myState = state; - } - - @Nullable - @Override - protected Module getModule() { - return myState.getModule(); - } - - @NonNull - @Override - protected List<Issue> getIssues() { - return myState.getIssues(); - } - - @Override - public void report(@NonNull Context context, - @NonNull Issue issue, - @NonNull Severity severity, - @NonNull Location location, - @NonNull String message, - @NonNull TextFormat format) { - if (location != null) { - final File file = location.getFile(); - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file); - - if (context.getDriver().isCustomIssue(issue)) { - // Record original issue id in the message (such that we can find - // it later, in #findCustomIssue) - message += " [" + issue.getId() + "]"; - recordCustomIssue(issue); - issue = Severity.WARNING.compareTo(severity) <= 0 ? CUSTOM_WARNING : CUSTOM_ERROR; - } - - if (myState.getMainFile().equals(vFile)) { - final Position start = location.getStart(); - final Position end = location.getEnd(); - - final TextRange textRange = start != null && end != null && start.getOffset() <= end.getOffset() - ? new TextRange(start.getOffset(), end.getOffset()) - : TextRange.EMPTY_RANGE; - - Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null; - message = format.convertTo(message, RAW); - myState.getProblems().add(new ProblemData(issue, message, textRange, configuredSeverity)); - } - - Location secondary = location.getSecondary(); - if (secondary != null && myState.getMainFile().equals(LocalFileSystem.getInstance().findFileByIoFile(secondary.getFile()))) { - reportSecondary(context, issue, severity, location, message, format); - } - } - } - - @Override - @NotNull - public String readFile(@NonNull File file) { - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file); - - if (vFile == null) { - try { - return Files.toString(file, Charsets.UTF_8); - } catch (IOException ioe) { - LOG.debug("Cannot find file " + file.getPath() + " in the VFS"); - return ""; - } - } - final String content = getFileContent(vFile); - - if (content == null) { - LOG.info("Cannot find file " + file.getPath() + " in the PSI"); - return ""; - } - return content; - } - - @Nullable - private String getFileContent(final VirtualFile vFile) { - if (Comparing.equal(myState.getMainFile(), vFile)) { - return myState.getMainFileContent(); - } - - return ApplicationManager.getApplication().runReadAction(new Computable<String>() { - @Nullable - @Override - public String compute() { - final Module module = myState.getModule(); - final Project project = module.getProject(); - if (project.isDisposed()) { - return null; - } - - final PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile); - - if (psiFile == null) { - return null; - } - final Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile); - - if (document != null) { - final DocumentListener listener = new DocumentListener() { - @Override - public void beforeDocumentChange(DocumentEvent event) { - } - - @Override - public void documentChanged(DocumentEvent event) { - myState.markDirty(); - } - }; - document.addDocumentListener(listener, EditorLintClient.this); - } - return psiFile.getText(); - } - }); - } - - @NonNull - @Override - public List<File> getJavaSourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) { - final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(myState.getModule()).getSourceRoots(false); - final List<File> result = new ArrayList<File>(sourceRoots.length); - - for (VirtualFile root : sourceRoots) { - result.add(new File(root.getPath())); - } - return result; - } - - @NonNull - @Override - public List<File> getResourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) { - AndroidFacet facet = AndroidFacet.getInstance(myState.getModule()); - if (facet != null) { - return IntellijLintUtils.getResourceDirectories(facet); - } - return super.getResourceFolders(project); - } - } - - /** Lint client used for batch operations */ - private static class BatchLintClient extends IntellijLintClient { - private final Map<Issue, Map<File, List<ProblemData>>> myProblemMap; - private final AnalysisScope myScope; - private final List<Issue> myIssues; - - public BatchLintClient(@NotNull Project project, - @NotNull Map<Issue, Map<File, List<ProblemData>>> problemMap, - @NotNull AnalysisScope scope, - @NotNull List<Issue> issues) { - super(project); - myProblemMap = problemMap; - myScope = scope; - myIssues = issues; - } - - @Nullable - @Override - protected Module getModule() { - // No default module - return null; - } - - @NonNull - @Override - protected List<Issue> getIssues() { - return myIssues; - } - - @Override - public void report(@NonNull Context context, - @NonNull Issue issue, - @NonNull Severity severity, - @NonNull Location location, - @NonNull String message, - @NonNull TextFormat format) { - VirtualFile vFile = null; - File file = null; - - if (location != null) { - file = location.getFile(); - vFile = LocalFileSystem.getInstance().findFileByIoFile(file); - } - else if (context.getProject() != null) { - final Module module = findModuleForLintProject(myProject, context.getProject()); - - if (module != null) { - final AndroidFacet facet = AndroidFacet.getInstance(module); - vFile = facet != null ? AndroidRootUtil.getPrimaryManifestFile(facet) : null; - - if (vFile != null) { - file = new File(vFile.getPath()); - } - } - } - - boolean inScope = vFile != null && myScope.contains(vFile); - // In analysis batch mode, the AnalysisScope contains a specific set of virtual - // files, not directories, so any errors reported against a directory will not - // be considered part of the scope and therefore won't be reported. Correct - // for this. - if (!inScope && vFile != null && vFile.isDirectory()) { - if (myScope.getScopeType() == AnalysisScope.PROJECT) { - inScope = true; - } else if (myScope.getScopeType() == AnalysisScope.MODULE || - myScope.getScopeType() == AnalysisScope.MODULES) { - final Module module = findModuleForLintProject(myProject, context.getProject()); - if (module != null && myScope.containsModule(module)) { - inScope = true; - } - } - } - - if (inScope) { - if (context.getDriver().isCustomIssue(issue)) { - // Record original issue id in the message (such that we can find - // it later, in #findCustomIssue) - message += " [" + issue.getId() + "]"; - recordCustomIssue(issue); - issue = Severity.WARNING.compareTo(severity) <= 0 ? CUSTOM_WARNING : CUSTOM_ERROR; - } - - file = new File(PathUtil.getCanonicalPath(file.getPath())); - - Map<File, List<ProblemData>> file2ProblemList = myProblemMap.get(issue); - if (file2ProblemList == null) { - file2ProblemList = new HashMap<File, List<ProblemData>>(); - myProblemMap.put(issue, file2ProblemList); - } - - List<ProblemData> problemList = file2ProblemList.get(file); - if (problemList == null) { - problemList = new ArrayList<ProblemData>(); - file2ProblemList.put(file, problemList); - } - - TextRange textRange = TextRange.EMPTY_RANGE; - - if (location != null) { - final Position start = location.getStart(); - final Position end = location.getEnd(); - - if (start != null && end != null && start.getOffset() <= end.getOffset()) { - textRange = new TextRange(start.getOffset(), end.getOffset()); - } - } - Severity configuredSeverity = severity != issue.getDefaultSeverity() ? severity : null; - message = format.convertTo(message, RAW); - problemList.add(new ProblemData(issue, message, textRange, configuredSeverity)); - - if (location != null && location.getSecondary() != null) { - reportSecondary(context, issue, severity, location, message, format); - } - } - } - - @NonNull - @Override - public List<File> getJavaSourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) { - final Module module = findModuleForLintProject(myProject, project); - if (module == null) { - return Collections.emptyList(); - } - final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(false); - final List<File> result = new ArrayList<File>(sourceRoots.length); - - for (VirtualFile root : sourceRoots) { - result.add(new File(root.getPath())); - } - return result; - } - - @NonNull - @Override - public List<File> getResourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) { - final Module module = findModuleForLintProject(myProject, project); - if (module != null) { - AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet != null) { - return IntellijLintUtils.getResourceDirectories(facet); - } - } - return super.getResourceFolders(project); - } - } - - @Override - public boolean checkForSuppressComments() { - return false; - } - - @Override - public boolean supportsProjectResources() { - return true; - } - - @Nullable - @Override - public AbstractResourceRepository getProjectResources(com.android.tools.klint.detector.api.Project project, boolean includeDependencies) { - final Module module = findModuleForLintProject(myProject, project); - if (module != null) { - AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet != null) { - return includeDependencies ? facet.getProjectResources(true) : facet.getModuleResources(true); - } - } - - return null; - } - - @Nullable - @Override - public URLConnection openConnection(@NonNull URL url) throws IOException { - return HttpConfigurable.getInstance().openConnection(url.toExternalForm()); - } - - @Override - public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) { - return UrlClassLoader.build().parent(parent).urls(urls).get(); - } - - @NonNull - @Override - public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) { - XmlTag tag = LocalResourceRepository.getItemTag(myProject, item); - if (tag != null) { - ResourceFile source = item.getSource(); - assert source != null : item; - return new LocationHandle(source.getFile(), tag); - } - return super.createResourceItemHandle(item); - } - - @NonNull - @Override - public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() { - Module module = getModule(); - if (module != null) { - AppResourceRepository appResources = AppResourceRepository.getAppResources(module, true); - if (appResources != null) { - ResourceVisibilityLookup.Provider provider = appResources.getResourceVisibilityProvider(); - if (provider != null) { - return provider; - } - } - } - return super.getResourceVisibilityProvider(); - } - - private static class LocationHandle implements Location.Handle, Computable<Location> { - private final File myFile; - private final XmlElement myNode; - private Object myClientData; - - public LocationHandle(File file, XmlElement node) { - myFile = file; - myNode = node; - } - - @NonNull - @Override - public Location resolve() { - if (!ApplicationManager.getApplication().isReadAccessAllowed()) { - return ApplicationManager.getApplication().runReadAction(this); - } - TextRange textRange = myNode.getTextRange(); - - // For elements, don't highlight the entire element range; instead, just - // highlight the element name - if (myNode instanceof XmlTag) { - String tag = ((XmlTag)myNode).getName(); - int index = myNode.getText().indexOf(tag); - if (index != -1) { - int start = textRange.getStartOffset() + index; - textRange = new TextRange(start, start + tag.length()); - } - } - - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); - Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); - return Location.create(myFile, start, end); - } - - @Override - public Location compute() { - return resolve(); - } - - @Override - public void setClientData(@Nullable Object clientData) { - myClientData = clientData; - } - - @Override - @Nullable - public Object getClientData() { - return myClientData; - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java deleted file mode 100644 index 2a47da6..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java +++ /dev/null
@@ -1,107 +0,0 @@ -/* - * 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 org.jetbrains.android.inspections.klint; - -import com.android.annotations.NonNull; -import com.android.tools.klint.checks.*; -import com.android.tools.klint.detector.api.*; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; - -import static org.jetbrains.android.inspections.klint.IntellijLintProject.*; - -/** - * Custom version of the {@link BuiltinIssueRegistry}. This - * variation will filter the default issues and remove - * any issues that aren't usable inside IDEA (e.g. they - * rely on class files), and it will also replace the implementation - * of some issues with IDEA specific ones. - */ -public class IntellijLintIssueRegistry extends BuiltinIssueRegistry { - private static final Implementation DUMMY_IMPLEMENTATION = new Implementation(Detector.class, - EnumSet.noneOf(Scope.class)); - - private static final String CUSTOM_EXPLANATION = - "When custom (third-party) lint rules are integrated in the IDE, they are not available as native IDE inspections, " + - "so the explanation text (which must be statically registered by a plugin) is not available. As a workaround, run the " + - "lint target in Gradle instead; the HTML report will include full explanations."; - - /** - * Issue reported by a custom rule (3rd party detector). We need a placeholder issue to reference for inspections, which - * have to be registered statically (can't load these on the fly from custom jars the way lint does) - */ - @NonNull - public static final Issue CUSTOM_WARNING = Issue.create( - "CustomWarning", "Warning from Custom Rule", CUSTOM_EXPLANATION, Category.CORRECTNESS, 5, Severity.WARNING, DUMMY_IMPLEMENTATION); - - @NonNull - public static final Issue CUSTOM_ERROR = Issue.create( - "CustomError", "Error from Custom Rule", CUSTOM_EXPLANATION, Category.CORRECTNESS, 5, Severity.ERROR, DUMMY_IMPLEMENTATION); - - private static List<Issue> ourFilteredIssues; - - public IntellijLintIssueRegistry() { - } - - @NonNull - @Override - public List<Issue> getIssues() { - if (ourFilteredIssues == null) { - List<Issue> sIssues = super.getIssues(); - List<Issue> result = new ArrayList<Issue>(sIssues.size()); - for (Issue issue : sIssues) { - Implementation implementation = issue.getImplementation(); - EnumSet<Scope> scope = implementation.getScope(); - Class<? extends Detector> detectorClass = implementation.getDetectorClass(); - if (detectorClass == ApiDetector.class) { - //issue.setImplementation(IntellijApiDetector.IMPLEMENTATION); - } else if (detectorClass == ViewTypeDetector.class) { - issue.setImplementation(IntellijViewTypeDetector.IMPLEMENTATION); - } - if (detectorClass == SupportAnnotationDetector.class) { - // Handled by the ResourceTypeInspection - continue; - } else if (scope.contains(Scope.CLASS_FILE) || - scope.contains(Scope.ALL_CLASS_FILES) || - scope.contains(Scope.JAVA_LIBRARIES)) { - //noinspection ConstantConditions - assert !SUPPORT_CLASS_FILES; // When enabled, adjust this to include class detector based issues - - boolean isOk = false; - for (EnumSet<Scope> analysisScope : implementation.getAnalysisScopes()) { - if (!analysisScope.contains(Scope.CLASS_FILE) && - !analysisScope.contains(Scope.ALL_CLASS_FILES) && - !analysisScope.contains(Scope.JAVA_LIBRARIES)) { - isOk = true; - break; - } - } - if (!isOk) { - // Skip issue: not included inside the IDE - continue; - } - } - result.add(issue); - } - //noinspection AssignmentToStaticFieldFromInstanceMethod - ourFilteredIssues = result; - } - - return ourFilteredIssues; - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java deleted file mode 100644 index a10e5a1..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java +++ /dev/null
@@ -1,1129 +0,0 @@ -/* - * 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 org.jetbrains.android.inspections.klint; - -import com.android.annotations.NonNull; -import com.android.builder.model.*; -import com.android.sdklib.AndroidTargetHash; -import com.android.sdklib.AndroidVersion; -import com.android.tools.idea.gradle.project.model.AndroidModuleModel; -import com.android.tools.idea.gradle.util.GradleUtil; -import com.android.tools.idea.model.AndroidModel; -import com.android.tools.idea.model.AndroidModuleInfo; -import com.android.tools.klint.client.api.LintClient; -import com.android.tools.klint.detector.api.Project; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ArrayUtil; -import com.intellij.util.graph.Graph; -import org.jetbrains.android.compiler.AndroidDexCompiler; -import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.android.facet.AndroidRootUtil; -import org.jetbrains.android.facet.IdeaSourceProvider; -import org.jetbrains.android.sdk.AndroidPlatform; -import org.jetbrains.android.util.AndroidCommonUtils; -import org.jetbrains.android.util.AndroidUtils; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.android.model.impl.JpsAndroidModuleProperties; - -import java.io.File; -import java.util.*; - -import static com.android.SdkConstants.APPCOMPAT_LIB_ARTIFACT; -import static com.android.SdkConstants.SUPPORT_LIB_ARTIFACT; - -/** - * An {@linkplain IntellijLintProject} represents a lint project, which typically corresponds to a {@link Module}, - * but can also correspond to a library "project" such as an {@link AndroidLibrary}. - */ -class IntellijLintProject extends Project { - /** - * Whether we support running .class file checks. No class file checks are currently registered as inspections. - * Since IntelliJ doesn't perform background compilation (e.g. only parsing, so there are no bytecode checks) - * this might need some work before we enable it. - */ - public static final boolean SUPPORT_CLASS_FILES = false; - - protected AndroidVersion mMinSdkVersion; - protected AndroidVersion mTargetSdkVersion; - - IntellijLintProject(@NonNull LintClient client, - @NonNull File dir, - @NonNull File referenceDir) { - super(client, dir, referenceDir); - } - - /** Creates a set of projects for the given IntelliJ modules */ - @NonNull - public static List<Project> create(@NonNull IntellijLintClient client, @Nullable List<VirtualFile> files, @NonNull Module... modules) { - List<Project> projects = Lists.newArrayList(); - - Map<Project,Module> projectMap = Maps.newHashMap(); - Map<Module,Project> moduleMap = Maps.newHashMap(); - Map<AndroidLibrary,Project> libraryMap = Maps.newHashMap(); - if (files != null && !files.isEmpty()) { - // Wrap list with a mutable list since we'll be removing the files as we see them - files = Lists.newArrayList(files); - } - for (Module module : modules) { - addProjects(client, module, files, moduleMap, libraryMap, projectMap, projects); - } - - client.setModuleMap(projectMap); - - if (projects.size() > 1) { - // Partition the projects up such that we only return projects that aren't - // included by other projects (e.g. because they are library projects) - Set<Project> roots = new HashSet<Project>(projects); - for (Project project : projects) { - roots.removeAll(project.getAllLibraries()); - } - return Lists.newArrayList(roots); - } else { - return projects; - } - } - - /** - * Creates a project for a single file. Also optionally creates a main project for the file, if applicable. - * - * @param client the lint client - * @param file the file to create a project for - * @param module the module to create a project for - * @return a project for the file, as well as a project (or null) for the main Android module - */ - @NonNull - public static Pair<Project,Project> createForSingleFile(@NonNull IntellijLintClient client, @Nullable VirtualFile file, @NonNull Module module) { - // TODO: Can make this method even more lightweight: we don't need to initialize anything in the project (source paths etc) - // other than the metadata necessary for this file's type - LintModuleProject project = createModuleProject(client, module); - LintModuleProject main = null; - Map<Project,Module> projectMap = Maps.newHashMap(); - if (project != null) { - project.setDirectLibraries(Collections.<Project>emptyList()); - if (file != null) { - project.addFile(VfsUtilCore.virtualToIoFile(file)); - } - projectMap.put(project, module); - - // Supply a main project too, such that when you for example edit a file in a Java library, - // and lint asks for getMainProject().getMinSdk(), we return the min SDK of an application - // using the library, not "1" (the default for a module without a manifest) - if (!project.isAndroidProject()) { - Module androidModule = findAndroidModule(module); - if (androidModule != null) { - main = createModuleProject(client, androidModule); - if (main != null) { - projectMap.put(main, androidModule); - main.setDirectLibraries(Collections.<Project>singletonList(project)); - } - } - } - } - client.setModuleMap(projectMap); - - //noinspection ConstantConditions - return Pair.<Project,Project>create(project,main); - } - - /** Find an Android module that depends on this module; prefer app modules over library modules */ - @Nullable - private static Module findAndroidModule(@NonNull final Module module) { - // Search for dependencies of this module - Graph<Module> graph = ApplicationManager.getApplication().runReadAction(new Computable<Graph<Module>>() { - @Override - public Graph<Module> compute() { - com.intellij.openapi.project.Project project = module.getProject(); - if (project.isDisposed()) { - return null; - } - return ModuleManager.getInstance(project).moduleGraph(); - } - }); - - if (graph == null) { - return null; - } - - Set<AndroidFacet> facets = Sets.newHashSet(); - HashSet<Module> seen = Sets.newHashSet(); - seen.add(module); - addAndroidModules(facets, seen, graph, module); - - // Prefer Android app modules - for (AndroidFacet facet : facets) { - if (!facet.isLibraryProject()) { - return facet.getModule(); - } - } - - // Resort to library modules if no app module depends directly on it - if (!facets.isEmpty()) { - return facets.iterator().next().getModule(); - } - - return null; - } - - private static void addAndroidModules(Set<AndroidFacet> androidFacets, Set<Module> seen, Graph<Module> graph, Module module) { - Iterator<Module> iterator = graph.getOut(module); - while (iterator.hasNext()) { - Module dep = iterator.next(); - AndroidFacet facet = AndroidFacet.getInstance(dep); - if (facet != null) { - androidFacets.add(facet); - } - - if (!seen.contains(dep)) { - seen.add(dep); - addAndroidModules(androidFacets, seen, graph, dep); - } - } - } - - /** - * Recursively add lint projects for the given module, and any other module or library it depends on, and also - * populate the reverse maps so we can quickly map from a lint project to a corresponding module/library (used - * by the lint client - */ - private static void addProjects(@NonNull LintClient client, - @NonNull Module module, - @Nullable List<VirtualFile> files, - @NonNull Map<Module,Project> moduleMap, - @NonNull Map<AndroidLibrary, Project> libraryMap, - @NonNull Map<Project,Module> projectMap, - @NonNull List<Project> projects) { - if (moduleMap.containsKey(module)) { - return; - } - - LintModuleProject project = createModuleProject(client, module); - - if (project == null) { - // It's possible for the module to *depend* on Android code, e.g. in a Gradle - // project there will be a top-level non-Android module - List<AndroidFacet> dependentFacets = AndroidUtils.getAllAndroidDependencies(module, false); - for (AndroidFacet dependentFacet : dependentFacets) { - addProjects(client, dependentFacet.getModule(), files, moduleMap, libraryMap, projectMap, projects); - } - return; - } - - projects.add(project); - moduleMap.put(module, project); - projectMap.put(project, module); - - if (processFileFilter(module, files, project)) { - // No need to process dependencies when doing single file analysis - return; - } - - List<Project> dependencies = Lists.newArrayList(); - // No, this shouldn't use getAllAndroidDependencies; we may have non-Android dependencies that this won't include - // (e.g. Java-only modules) - List<AndroidFacet> dependentFacets = AndroidUtils.getAllAndroidDependencies(module, true); - for (AndroidFacet dependentFacet : dependentFacets) { - Project p = moduleMap.get(dependentFacet.getModule()); - if (p != null) { - dependencies.add(p); - } else { - addProjects(client, dependentFacet.getModule(), files, moduleMap, libraryMap, projectMap, dependencies); - } - } - - AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet != null) { - AndroidModuleModel androidModuleModel = AndroidModuleModel.get(facet); - if (androidModuleModel != null) { - addGradleLibraryProjects(client, files, libraryMap, projects, facet, androidModuleModel, project, projectMap, dependencies); - } - } - - project.setDirectLibraries(dependencies); - } - - /** - * Checks whether we have a file filter (e.g. a set of specific files to check in the module rather than all files, - * and if so, and if all the files have been found, returns true) - */ - private static boolean processFileFilter(@NonNull Module module, @Nullable List<VirtualFile> files, @NonNull LintModuleProject project) { - if (files != null && !files.isEmpty()) { - ListIterator<VirtualFile> iterator = files.listIterator(); - while (iterator.hasNext()) { - VirtualFile file = iterator.next(); - if (module.getModuleContentScope().accept(file)) { - project.addFile(VfsUtilCore.virtualToIoFile(file)); - iterator.remove(); - } - } - if (files.isEmpty()) { - // We're only scanning a subset of files (typically the current file in the editor); - // in that case, don't initialize all the libraries etc - project.setDirectLibraries(Collections.<Project>emptyList()); - return true; - } - } - return false; - } - - /** Creates a new module project */ - @Nullable - private static LintModuleProject createModuleProject(@NonNull LintClient client, @NonNull Module module) { - AndroidFacet facet = AndroidFacet.getInstance(module); - File dir; - - if (facet != null) { - final VirtualFile mainContentRoot = AndroidRootUtil.getMainContentRoot(facet); - - if (mainContentRoot == null) { - return null; - } - dir = new File(FileUtil.toSystemDependentName(mainContentRoot.getPath())); - } else { - String moduleDirPath = AndroidRootUtil.getModuleDirPath(module); - if (moduleDirPath == null) { - return null; - } - dir = new File(FileUtil.toSystemDependentName(moduleDirPath)); - } - LintModuleProject project = null; - if (facet == null) { - project = new LintModuleProject(client, dir, dir, module); - AndroidFacet f = findAndroidFacetInProject(module.getProject()); - if (f != null) { - project.mGradleProject = f.requiresAndroidModel(); - } - } - else if (facet.requiresAndroidModel()) { - AndroidModel androidModel = facet.getAndroidModel(); - if (androidModel instanceof AndroidModuleModel) { - project = new LintGradleProject(client, dir, dir, facet, (AndroidModuleModel)androidModel); - } else { - project = new LintAndroidModelProject(client, dir, dir, facet, androidModel); - } - } - else { - project = new LintAndroidProject(client, dir, dir, facet); - } - if (project != null) { - client.registerProject(dir, project); - } - return project; - } - - public static boolean hasAndroidModule(@NonNull com.intellij.openapi.project.Project project) { - return findAndroidFacetInProject(project) != null; - } - - @Nullable - private static AndroidFacet findAndroidFacetInProject(@NonNull com.intellij.openapi.project.Project project) { - ModuleManager moduleManager = ModuleManager.getInstance(project); - for (Module module : moduleManager.getModules()) { - AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet != null) { - return facet; - } - } - - return null; - } - - /** Adds any gradle library projects to the dependency list */ - private static void addGradleLibraryProjects(@NonNull LintClient client, - @Nullable List<VirtualFile> files, - @NonNull Map<AndroidLibrary, Project> libraryMap, - @NonNull List<Project> projects, - @NonNull AndroidFacet facet, - @NonNull AndroidModuleModel AndroidModuleModel, - @NonNull LintModuleProject project, - @NonNull Map<Project,Module> projectMap, - @NonNull List<Project> dependencies) { - Collection<AndroidLibrary> libraries = AndroidModuleModel.getMainArtifact().getDependencies().getLibraries(); - for (AndroidLibrary library : libraries) { - Project p = libraryMap.get(library); - if (p == null) { - File dir = library.getFolder(); - p = new LintGradleLibraryProject(client, dir, dir, library); - libraryMap.put(library, p); - projectMap.put(p, facet.getModule()); - projects.add(p); - - if (files != null) { - VirtualFile libraryDir = LocalFileSystem.getInstance().findFileByIoFile(dir); - if (libraryDir != null) { - ListIterator<VirtualFile> iterator = files.listIterator(); - while (iterator.hasNext()) { - VirtualFile file = iterator.next(); - if (VfsUtilCore.isAncestor(libraryDir, file, false)) { - project.addFile(VfsUtilCore.virtualToIoFile(file)); - iterator.remove(); - } - } - } - if (files.isEmpty()) { - files = null; // No more work in other modules - } - } - } - dependencies.add(p); - } - } - - @Override - protected void initialize() { - // NOT calling super: super performs ADT/ant initialization. Here we want to use - // the gradle data instead - } - - protected static boolean depsDependsOn(@NonNull Project project, @NonNull String artifact) { - // Checks project dependencies only; used when there is no model - for (Project dependency : project.getDirectLibraries()) { - Boolean b = dependency.dependsOn(artifact); - if (b != null && b) { - return true; - } - } - - return false; - } - - private static class LintModuleProject extends IntellijLintProject { - private Module myModule; - - public void setDirectLibraries(List<Project> libraries) { - mDirectLibraries = libraries; - } - - private LintModuleProject(@NonNull LintClient client, @NonNull File dir, @NonNull File referenceDir, Module module) { - super(client, dir, referenceDir); - myModule = module; - } - - @Override - public boolean isAndroidProject() { - return false; - } - - @NonNull - @Override - public List<File> getJavaSourceFolders() { - if (mJavaSourceFolders == null) { - VirtualFile[] sourceRoots = ModuleRootManager.getInstance(myModule).getSourceRoots(false); - List<File> dirs = new ArrayList<File>(sourceRoots.length); - for (VirtualFile root : sourceRoots) { - dirs.add(new File(root.getPath())); - } - mJavaSourceFolders = dirs; - } - - return mJavaSourceFolders; - } - - @NonNull - @Override - public List<File> getTestSourceFolders() { - if (mTestSourceFolders == null) { - ModuleRootManager manager = ModuleRootManager.getInstance(myModule); - VirtualFile[] sourceRoots = manager.getSourceRoots(false); - VirtualFile[] sourceAndTestRoots = manager.getSourceRoots(true); - List<File> dirs = new ArrayList<File>(sourceAndTestRoots.length); - for (VirtualFile root : sourceAndTestRoots) { - if (!ArrayUtil.contains(root, sourceRoots)) { - dirs.add(new File(root.getPath())); - } - } - mTestSourceFolders = dirs; - } - return mTestSourceFolders; - } - - @NonNull - @Override - public List<File> getJavaClassFolders() { - if (SUPPORT_CLASS_FILES) { - if (mJavaClassFolders == null) { - VirtualFile folder = AndroidDexCompiler.getOutputDirectoryForDex(myModule); - if (folder != null) { - mJavaClassFolders = Collections.singletonList(VfsUtilCore.virtualToIoFile(folder)); - } else { - mJavaClassFolders = Collections.emptyList(); - } - } - - return mJavaClassFolders; - } - - return Collections.emptyList(); - } - - @NonNull - @Override - public List<File> getJavaLibraries(boolean includeProvided) { - if (SUPPORT_CLASS_FILES) { - if (mJavaLibraries == null) { - mJavaLibraries = Lists.newArrayList(); - - final OrderEntry[] entries = ModuleRootManager.getInstance(myModule).getOrderEntries(); - // loop in the inverse order to resolve dependencies on the libraries, so that if a library - // is required by two higher level libraries it can be inserted in the correct place - - for (int i = entries.length - 1; i >= 0; i--) { - final OrderEntry orderEntry = entries[i]; - if (orderEntry instanceof LibraryOrderEntry) { - LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)orderEntry; - VirtualFile[] classes = libraryOrderEntry.getRootFiles(OrderRootType.CLASSES); - if (classes != null) { - for (VirtualFile file : classes) { - mJavaLibraries.add(VfsUtilCore.virtualToIoFile(file)); - } - } - } - } - } - - return mJavaLibraries; - } - - return Collections.emptyList(); - } - } - - /** Wraps an Android module */ - private static class LintAndroidProject extends LintModuleProject { - protected final AndroidFacet myFacet; - - private LintAndroidProject(@NonNull LintClient client, @NonNull File dir, @NonNull File referenceDir, @NonNull AndroidFacet facet) { - super(client, dir, referenceDir, facet.getModule()); - myFacet = facet; - - mGradleProject = false; - mLibrary = myFacet.isLibraryProject(); - - AndroidPlatform platform = AndroidPlatform.getInstance(myFacet.getModule()); - if (platform != null) { - mBuildSdk = platform.getApiLevel(); - } - } - - @Override - public boolean isAndroidProject() { - return true; - } - - @NonNull - @Override - public String getName() { - return myFacet.getModule().getName(); - } - - @Override - @NonNull - public List<File> getManifestFiles() { - if (mManifestFiles == null) { - VirtualFile manifestFile = AndroidRootUtil.getPrimaryManifestFile(myFacet); - if (manifestFile != null) { - mManifestFiles = Collections.singletonList(VfsUtilCore.virtualToIoFile(manifestFile)); - } else { - mManifestFiles = Collections.emptyList(); - } - } - - return mManifestFiles; - } - - @NonNull - @Override - public List<File> getProguardFiles() { - if (mProguardFiles == null) { - final JpsAndroidModuleProperties properties = myFacet.getProperties(); - - if (properties.RUN_PROGUARD) { - final List<String> urls = properties.myProGuardCfgFiles; - - if (!urls.isEmpty()) { - mProguardFiles = new ArrayList<File>(); - - for (String osPath : AndroidUtils.urlsToOsPaths(urls, null)) { - if (!osPath.contains(AndroidCommonUtils.SDK_HOME_MACRO)) { - mProguardFiles.add(new File(osPath)); - } - } - } - } - - if (mProguardFiles == null) { - mProguardFiles = Collections.emptyList(); - } - } - - return mProguardFiles; - } - - @NonNull - @Override - public List<File> getResourceFolders() { - if (mResourceFolders == null) { - List<VirtualFile> folders = myFacet.getResourceFolderManager().getFolders(); - List<File> dirs = Lists.newArrayListWithExpectedSize(folders.size()); - for (VirtualFile folder : folders) { - dirs.add(VfsUtilCore.virtualToIoFile(folder)); - } - mResourceFolders = dirs; - } - - return mResourceFolders; - } - - @Nullable - @Override - public Boolean dependsOn(@NonNull String artifact) { - if (SUPPORT_LIB_ARTIFACT.equals(artifact)) { - if (mSupportLib == null) { - final OrderEntry[] entries = ModuleRootManager.getInstance(myFacet.getModule()).getOrderEntries(); - libraries: - for (int i = entries.length - 1; i >= 0; i--) { - final OrderEntry orderEntry = entries[i]; - if (orderEntry instanceof LibraryOrderEntry) { - LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)orderEntry; - VirtualFile[] classes = libraryOrderEntry.getRootFiles(OrderRootType.CLASSES); - if (classes != null) { - for (VirtualFile file : classes) { - if (file.getName().equals("android-support-v4.jar")) { - mSupportLib = true; - break libraries; - - } - } - } - } - } - if (mSupportLib == null) { - mSupportLib = depsDependsOn(this, artifact); - } - } - return mSupportLib; - } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) { - if (mSupportLib == null) { - final OrderEntry[] entries = ModuleRootManager.getInstance(myFacet.getModule()).getOrderEntries(); - libraries: - for (int i = entries.length - 1; i >= 0; i--) { - final OrderEntry orderEntry = entries[i]; - if (orderEntry instanceof LibraryOrderEntry) { - LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)orderEntry; - VirtualFile[] classes = libraryOrderEntry.getRootFiles(OrderRootType.CLASSES); - if (classes != null) { - for (VirtualFile file : classes) { - if (file.getName().equals("appcompat-v7.jar")) { - mSupportLib = true; - break libraries; - - } - } - } - } - } - if (mSupportLib == null) { - mSupportLib = depsDependsOn(this, artifact); - } - } - return mSupportLib; - } else { - return super.dependsOn(artifact); - } - } - } - - private static class LintAndroidModelProject extends LintAndroidProject { - private final AndroidModel myAndroidModel; - - private LintAndroidModelProject( - @NonNull LintClient client, - @NonNull File dir, - @NonNull File referenceDir, - @NonNull AndroidFacet facet, - @NonNull AndroidModel androidModel) { - super(client, dir, referenceDir, facet); - myAndroidModel = androidModel; - } - - @Nullable - @Override - public String getPackage() { - String manifestPackage = super.getPackage(); - // For now, lint only needs the manifest package; not the potentially variant specific - // package. As part of the Gradle work on the Lint API we should make two separate - // package lookup methods -- one for the manifest package, one for the build package - if (manifestPackage != null) { - return manifestPackage; - } - - return myAndroidModel.getApplicationId(); - } - - @NonNull - @Override - public AndroidVersion getMinSdkVersion() { - if (mMinSdkVersion == null) { - mMinSdkVersion = AndroidModuleInfo.get(myFacet).getMinSdkVersion(); - } - return mMinSdkVersion; - } - - @NonNull - @Override - public AndroidVersion getTargetSdkVersion() { - if (mTargetSdkVersion == null) { - mTargetSdkVersion = AndroidModuleInfo.get(myFacet).getTargetSdkVersion(); - } - - return mTargetSdkVersion; - } - } - - private static class LintGradleProject extends LintAndroidModelProject { - private final AndroidModuleModel myAndroidModuleModel; - - /** - * Creates a new Project. Use one of the factory methods to create. - */ - private LintGradleProject( - @NonNull LintClient client, - @NonNull File dir, - @NonNull File referenceDir, - @NonNull AndroidFacet facet, - @NonNull AndroidModuleModel AndroidModuleModel) { - super(client, dir, referenceDir, facet, AndroidModuleModel); - mGradleProject = true; - mMergeManifests = true; - myAndroidModuleModel = AndroidModuleModel; - } - - @NonNull - @Override - public List<File> getManifestFiles() { - if (mManifestFiles == null) { - mManifestFiles = Lists.newArrayList(); - File mainManifest = myFacet.getMainSourceProvider().getManifestFile(); - if (mainManifest.exists()) { - mManifestFiles.add(mainManifest); - } - - List<SourceProvider> flavorSourceProviders = myAndroidModuleModel.getFlavorSourceProviders(); - if (flavorSourceProviders != null) { - for (SourceProvider provider : flavorSourceProviders) { - File manifestFile = provider.getManifestFile(); - if (manifestFile.exists()) { - mManifestFiles.add(manifestFile); - } - } - } - - SourceProvider multiProvider = myAndroidModuleModel.getMultiFlavorSourceProvider(); - if (multiProvider != null) { - File manifestFile = multiProvider.getManifestFile(); - if (manifestFile.exists()) { - mManifestFiles.add(manifestFile); - } - } - - SourceProvider buildTypeSourceProvider = myAndroidModuleModel.getBuildTypeSourceProvider(); - if (buildTypeSourceProvider != null) { - File manifestFile = buildTypeSourceProvider.getManifestFile(); - if (manifestFile.exists()) { - mManifestFiles.add(manifestFile); - } - } - - SourceProvider variantProvider = myAndroidModuleModel.getVariantSourceProvider(); - if (variantProvider != null) { - File manifestFile = variantProvider.getManifestFile(); - if (manifestFile.exists()) { - mManifestFiles.add(manifestFile); - } - } - } - - return mManifestFiles; - } - - @NonNull - @Override - public List<File> getAssetFolders() { - if (mAssetFolders == null) { - mAssetFolders = Lists.newArrayList(); - for (SourceProvider provider : IdeaSourceProvider.getAllSourceProviders(myFacet)) { - Collection<File> dirs = provider.getAssetsDirectories(); - for (File dir : dirs) { - if (dir.exists()) { // model returns path whether or not it exists - mAssetFolders.add(dir); - } - } - } - } - - return mAssetFolders; - } - - @NonNull - @Override - public List<File> getProguardFiles() { - if (mProguardFiles == null) { - if (myFacet.requiresAndroidModel()) { - // TODO: b/22928250 - AndroidModuleModel androidModel = AndroidModuleModel.get(myFacet); - if (androidModel != null) { - ProductFlavor flavor = androidModel.getAndroidProject().getDefaultConfig().getProductFlavor(); - mProguardFiles = Lists.newArrayList(); - for (File file : flavor.getProguardFiles()) { - if (file.exists()) { - mProguardFiles.add(file); - } - } - try { - for (File file : flavor.getConsumerProguardFiles()) { - if (file.exists()) { - mProguardFiles.add(file); - } - } - } catch (Throwable t) { - // On some models, this threw - // org.gradle.tooling.model.UnsupportedMethodException: Unsupported method: BaseConfig.getConsumerProguardFiles(). - // Playing it safe for a while. - } - } - } - - if (mProguardFiles == null) { - mProguardFiles = Collections.emptyList(); - } - } - - return mProguardFiles; - } - - @NonNull - @Override - public List<File> getJavaClassFolders() { - if (SUPPORT_CLASS_FILES) { - if (mJavaClassFolders == null) { - // Overridden because we don't synchronize the gradle output directory to - // the AndroidDexCompiler settings the way java source roots are mapped into - // the module content root settings - File dir = myAndroidModuleModel.getMainArtifact().getClassesFolder(); - if (dir != null) { - mJavaClassFolders = Collections.singletonList(dir); - } else { - mJavaClassFolders = Collections.emptyList(); - } - } - - return mJavaClassFolders; - } - - return Collections.emptyList(); - } - - private static boolean sProvidedAvailable = true; - - @NonNull - @Override - public List<File> getJavaLibraries(boolean includeProvided) { - if (SUPPORT_CLASS_FILES) { - if (mJavaLibraries == null) { - if (myFacet.requiresAndroidModel() && myFacet.getAndroidModel() != null) { - Collection<JavaLibrary> libs = myAndroidModuleModel.getMainArtifact().getDependencies().getJavaLibraries(); - mJavaLibraries = Lists.newArrayListWithExpectedSize(libs.size()); - for (JavaLibrary lib : libs) { - if (!includeProvided) { - if (sProvidedAvailable) { - // Method added in 1.4-rc1; gracefully handle running with - // older plugins - try { - if (lib.isProvided()) { - continue; - } - } - catch (Throwable t) { - //noinspection AssignmentToStaticFieldFromInstanceMethod - sProvidedAvailable = false; // don't try again - } - } - } - - File jar = lib.getJarFile(); - if (jar.exists()) { - mJavaLibraries.add(jar); - } - } - } else { - mJavaLibraries = super.getJavaLibraries(includeProvided); - } - } - return mJavaLibraries; - } - - return Collections.emptyList(); - } - - @Override - public int getBuildSdk() { - // TODO: b/22928250 - AndroidModuleModel androidModel = AndroidModuleModel.get(myFacet); - if (androidModel != null) { - String compileTarget = androidModel.getAndroidProject().getCompileTarget(); - AndroidVersion version = AndroidTargetHash.getPlatformVersion(compileTarget); - if (version != null) { - return version.getFeatureLevel(); - } - } - - AndroidPlatform platform = AndroidPlatform.getInstance(myFacet.getModule()); - if (platform != null) { - return platform.getApiVersion().getFeatureLevel(); - } - - return super.getBuildSdk(); - } - - @Nullable - @Override - public AndroidProject getGradleProjectModel() { - // TODO: b/22928250 - AndroidModuleModel androidModel = AndroidModuleModel.get(myFacet); - if (androidModel != null) { - return androidModel.getAndroidProject(); - } - - return null; - } - - @Nullable - @Override - public Variant getCurrentVariant() { - // TODO: b/22928250 - AndroidModuleModel androidModel = AndroidModuleModel.get(myFacet); - if (androidModel != null) { - return androidModel.getSelectedVariant(); - } - - return null; - } - - @Nullable - @Override - public AndroidLibrary getGradleLibraryModel() { - return null; - } - - @Nullable - @Override - public Boolean dependsOn(@NonNull String artifact) { - // TODO: b/22928250 - AndroidModuleModel androidModel = AndroidModuleModel.get(myFacet); - - if (SUPPORT_LIB_ARTIFACT.equals(artifact)) { - if (mSupportLib == null) { - if (myFacet.requiresAndroidModel() && myFacet.getAndroidModel() != null) { - mSupportLib = GradleUtil.dependsOn(androidModel, artifact); - } else { - mSupportLib = depsDependsOn(this, artifact); - } - } - return mSupportLib; - } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) { - if (mAppCompat == null) { - if (myFacet.requiresAndroidModel() && myFacet.getAndroidModel() != null) { - mAppCompat = GradleUtil.dependsOn(androidModel, artifact); - } else { - mAppCompat = depsDependsOn(this, artifact); - } - } - return mAppCompat; - } else { - // Some other (not yet directly cached result) - if (myFacet.requiresAndroidModel() && myFacet.getAndroidModel() != null - && GradleUtil.dependsOn(androidModel, artifact)) { - return true; - } - - return super.dependsOn(artifact); - } - } - } - - private static class LintGradleLibraryProject extends IntellijLintProject { - private final AndroidLibrary myLibrary; - - private LintGradleLibraryProject(@NonNull LintClient client, - @NonNull File dir, - @NonNull File referenceDir, - @NonNull AndroidLibrary library) { - super(client, dir, referenceDir); - myLibrary = library; - - mLibrary = true; - mMergeManifests = true; - mReportIssues = false; - mGradleProject = true; - mDirectLibraries = Collections.emptyList(); - } - - @NonNull - @Override - public List<File> getManifestFiles() { - if (mManifestFiles == null) { - File manifest = myLibrary.getManifest(); - if (manifest.exists()) { - mManifestFiles = Collections.singletonList(manifest); - } else { - mManifestFiles = Collections.emptyList(); - } - } - - return mManifestFiles; - } - - @NonNull - @Override - public List<File> getProguardFiles() { - if (mProguardFiles == null) { - File proguardRules = myLibrary.getProguardRules(); - if (proguardRules.exists()) { - mProguardFiles = Collections.singletonList(proguardRules); - } else { - mProguardFiles = Collections.emptyList(); - } - } - - return mProguardFiles; - } - - @NonNull - @Override - public List<File> getResourceFolders() { - if (mResourceFolders == null) { - File folder = myLibrary.getResFolder(); - if (folder.exists()) { - mResourceFolders = Collections.singletonList(folder); - } else { - mResourceFolders = Collections.emptyList(); - } - } - - return mResourceFolders; - } - - @NonNull - @Override - public List<File> getJavaSourceFolders() { - return Collections.emptyList(); - } - - @NonNull - @Override - public List<File> getJavaClassFolders() { - return Collections.emptyList(); - } - - private static boolean sOptionalAvailable = true; - - @NonNull - @Override - public List<File> getJavaLibraries(boolean includeProvided) { - if (SUPPORT_CLASS_FILES) { - if (!includeProvided) { - if (sOptionalAvailable) { - // Method added in 1.4-rc1; gracefully handle running with - // older plugins - try { - if (myLibrary.isOptional()) { - return Collections.emptyList(); - } - } - catch (Throwable t) { - //noinspection AssignmentToStaticFieldFromInstanceMethod - sOptionalAvailable = false; // don't try again - } - } - } - - if (mJavaLibraries == null) { - mJavaLibraries = Lists.newArrayList(); - File jarFile = myLibrary.getJarFile(); - if (jarFile.exists()) { - mJavaLibraries.add(jarFile); - } - - for (File local : myLibrary.getLocalJars()) { - if (local.exists()) { - mJavaLibraries.add(local); - } - } - } - - return mJavaLibraries; - } - - return Collections.emptyList(); - } - - @Nullable - @Override - public AndroidProject getGradleProjectModel() { - return null; - } - - @Nullable - @Override - public AndroidLibrary getGradleLibraryModel() { - return myLibrary; - } - - @Nullable - @Override - public Boolean dependsOn(@NonNull String artifact) { - if (SUPPORT_LIB_ARTIFACT.equals(artifact)) { - if (mSupportLib == null) { - mSupportLib = GradleUtil.dependsOn(myLibrary, artifact, true); - } - return mSupportLib; - } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) { - if (mAppCompat == null) { - mAppCompat = GradleUtil.dependsOn(myLibrary, artifact, true); - } - return mAppCompat; - } else { - // Some other (not yet directly cached result) - if (GradleUtil.dependsOn(myLibrary, artifact, true)) { - return true; - } - - return super.dependsOn(artifact); - } - } - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintRequest.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintRequest.java deleted file mode 100644 index 13b340d..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintRequest.java +++ /dev/null
@@ -1,122 +0,0 @@ -/* - * 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 org.jetbrains.android.inspections.klint; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.tools.klint.client.api.LintRequest; -import com.android.tools.klint.detector.api.Scope; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vfs.VirtualFile; - -import java.io.File; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; - -public class IntellijLintRequest extends LintRequest { - @NonNull private final Project myProject; - @NonNull private final List<Module> myModules; - @NonNull private final IntellijLintClient mLintClient; - @Nullable private final List<VirtualFile> myFileList; - @Nullable private com.android.tools.klint.detector.api.Project myMainProject; - private final boolean myIncremental; - - /** - * Creates a new {@linkplain IntellijLintRequest}. - * @param client the client - * @param project the project where lint is run - * @param fileList an optional list of specific files to check, normally null - * @param modules the set of modules to be checked (or containing the files) - * @param incremental true if this is an incremental (current editor) analysis - */ - public IntellijLintRequest(@NonNull IntellijLintClient client, - @NonNull Project project, - @Nullable List<VirtualFile> fileList, - @NonNull List<Module> modules, - boolean incremental) { - super(client, Collections.<File>emptyList()); - mLintClient = client; - myProject = project; - myModules = modules; - myFileList = fileList; - myIncremental = incremental; - } - - @NonNull - Project getProject() { - return myProject; - } - - @Nullable - @Override - public EnumSet<Scope> getScope() { - if (mScope == null) { - Collection<com.android.tools.klint.detector.api.Project> projects = getProjects(); - if (projects != null) { - mScope = Scope.infer(projects); - - //noinspection ConstantConditions - if (!IntellijLintProject.SUPPORT_CLASS_FILES && (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.ALL_CLASS_FILES) - || mScope.contains(Scope.JAVA_LIBRARIES))) { - mScope = EnumSet.copyOf(mScope); // make mutable - // Can't run class file based checks - mScope.remove(Scope.CLASS_FILE); - mScope.remove(Scope.ALL_CLASS_FILES); - mScope.remove(Scope.JAVA_LIBRARIES); - } - } - } - - return mScope; - } - - @Nullable - @Override - public Collection<com.android.tools.klint.detector.api.Project> getProjects() { - if (mProjects == null) { - if (myIncremental && myFileList != null && myFileList.size() == 1 && myModules.size() == 1) { - Pair<com.android.tools.klint.detector.api.Project,com.android.tools.klint.detector.api.Project> pair = - IntellijLintProject.createForSingleFile(mLintClient, myFileList.get(0), myModules.get(0)); - mProjects = pair.first != null ? Collections.singletonList(pair.first) - : Collections.<com.android.tools.klint.detector.api.Project>emptyList(); - myMainProject = pair.second; - } else if (!myModules.isEmpty()) { - // Make one project for each module, mark each one as a library, - // and add projects for the gradle libraries and set error reporting to - // false on those - //mProjects = computeProjects() - mProjects = IntellijLintProject.create(mLintClient, myFileList, myModules.toArray(new Module[myModules.size()])); - } else { - mProjects = super.getProjects(); - } - } - - return mProjects; - } - - @NonNull - @Override - public com.android.tools.klint.detector.api.Project getMainProject(@NonNull com.android.tools.klint.detector.api.Project project) { - if (myMainProject != null) { - return myMainProject; - } - return super.getMainProject(project); - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java deleted file mode 100644 index 891a047..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java +++ /dev/null
@@ -1,485 +0,0 @@ -/* - * 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 org.jetbrains.android.inspections.klint; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.builder.model.SourceProvider; -import com.android.tools.idea.AndroidPsiUtils; -import com.android.tools.idea.model.AndroidModel; -import com.android.tools.klint.client.api.LintRequest; -import com.android.tools.klint.detector.api.*; -import com.google.common.base.Splitter; -import com.intellij.debugger.engine.JVMNameUtil; -import com.intellij.ide.util.JavaAnonymousClassesHelper; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; -import com.intellij.psi.util.ClassUtil; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.TypeConversionUtil; -import org.jetbrains.android.facet.AndroidFacet; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.uast.*; -import org.jetbrains.uast.psi.UElementWithLocation; -import org.jetbrains.uast.util.UastExpressionUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import static com.android.SdkConstants.CONSTRUCTOR_NAME; -import static com.android.SdkConstants.SUPPRESS_ALL; - -/** - * Common utilities for handling lint within IntelliJ - * TODO: Merge with {@link AndroidLintUtil} - */ -public class IntellijLintUtils { - private IntellijLintUtils() { - } - - @NonNls - public static final String SUPPRESS_LINT_FQCN = "android.annotation.SuppressLint"; - @NonNls - public static final String SUPPRESS_WARNINGS_FQCN = "java.lang.SuppressWarnings"; - - /** - * Gets the location of the given element - * - * @param file the file containing the location - * @param element the element to look up the location for - * @return the location of the given element - */ - @NonNull - public static Location getLocation(@NonNull File file, @NonNull PsiElement element) { - //noinspection ConstantConditions - assert element.getContainingFile().getVirtualFile() == null - || FileUtil.filesEqual(VfsUtilCore.virtualToIoFile(element.getContainingFile().getVirtualFile()), file); - - if (element instanceof PsiClass) { - // Point to the name rather than the beginning of the javadoc - PsiClass clz = (PsiClass)element; - PsiIdentifier nameIdentifier = clz.getNameIdentifier(); - if (nameIdentifier != null) { - element = nameIdentifier; - } - } - - TextRange textRange = element.getTextRange(); - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); - Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); - return Location.create(file, start, end); - } - - /** - * Gets the location of the given element - * - * @param file the file containing the location - * @param element the element to look up the location for - * @return the location of the given element - */ - @NonNull - public static Location getUastLocation(@NonNull File file, @NonNull UElement element) { - //noinspection ConstantConditions - PsiFile containingPsiFile = UastUtils.getContainingFile(element).getPsi(); - assert containingPsiFile.getVirtualFile() == null - || FileUtil.filesEqual(VfsUtilCore.virtualToIoFile(containingPsiFile.getVirtualFile()), file); - - if (element instanceof UClass) { - // Point to the name rather than the beginning of the javadoc - UClass clz = (UClass)element; - UElement nameIdentifier = clz.getUastAnchor(); - if (nameIdentifier != null) { - element = nameIdentifier; - } - } - - TextRange textRange = null; - PsiElement psi = element.getPsi(); - if (psi != null) { - textRange = psi.getTextRange(); - } else if (element instanceof UElementWithLocation) { - UElementWithLocation elementWithLocation = (UElementWithLocation) element; - textRange = new TextRange( - elementWithLocation.getStartOffset(), - elementWithLocation.getEndOffset()); - } - - if (textRange == null) { - return Location.NONE; - } - - Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); - Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); - return Location.create(file, start, end); - } - - /** - * Returns the {@link PsiFile} associated with a given lint {@link Context} - * - * @param context the context to look up the file for - * @return the corresponding {@link PsiFile}, or null - */ - @Nullable - public static PsiFile getPsiFile(@NonNull Context context) { - VirtualFile file = VfsUtil.findFileByIoFile(context.file, false); - if (file == null) { - return null; - } - LintRequest request = context.getDriver().getRequest(); - Project project = ((IntellijLintRequest)request).getProject(); - if (project.isDisposed()) { - return null; - } - return AndroidPsiUtils.getPsiFileSafely(project, file); - } - - /** - * Returns true if the given issue is suppressed at the given element within the given file - * - * @param element the element to check - * @param file the file containing the element - * @param issue the issue to check - * @return true if the given issue is suppressed - */ - public static boolean isSuppressed(@NonNull PsiElement element, @NonNull PsiFile file, @NonNull Issue issue) { - // Search upwards for suppress lint and suppress warnings annotations - //noinspection ConstantConditions - while (element != null && element != file) { // otherwise it will keep going into directories! - if (element instanceof PsiModifierListOwner) { - PsiModifierListOwner owner = (PsiModifierListOwner)element; - PsiModifierList modifierList = owner.getModifierList(); - if (modifierList != null) { - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - String fqcn = annotation.getQualifiedName(); - if (fqcn != null && (fqcn.equals(SUPPRESS_LINT_FQCN) || fqcn.equals(SUPPRESS_WARNINGS_FQCN))) { - PsiAnnotationParameterList parameterList = annotation.getParameterList(); - for (PsiNameValuePair pair : parameterList.getAttributes()) { - PsiAnnotationMemberValue v = pair.getValue(); - if (v instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral)v; - Object value = literal.getValue(); - if (value instanceof String) { - if (isSuppressed(issue, (String) value)) { - return true; - } - } - } else if (v instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue mv = (PsiArrayInitializerMemberValue)v; - for (PsiAnnotationMemberValue mmv : mv.getInitializers()) { - if (mmv instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral) mmv; - Object value = literal.getValue(); - if (value instanceof String) { - if (isSuppressed(issue, (String) value)) { - return true; - } - } - } - } - } else if (v != null) { - // This shouldn't be necessary - String text = v.getText().trim(); // UGH! Find better way to access value! - if (!text.isEmpty() && isSuppressed(issue, text)) { - return true; - } - } - } - } - } - } - } - element = element.getParent(); - } - - return false; - } - - /** - * Returns true if the given issue is suppressed at the given element within the given file - * - * @param element the element to check - * @param file the file containing the element - * @param issue the issue to check - * @return true if the given issue is suppressed - */ - public static boolean isSuppressed(@NonNull UElement element, @NonNull UFile file, @NonNull Issue issue) { - // Search upwards for suppress lint and suppress warnings annotations - //noinspection ConstantConditions - while (element != null && element != file) { // otherwise it will keep going into directories! - if (element instanceof UAnnotated) { - UAnnotated annotated = (UAnnotated)element; - for (UAnnotation annotation : annotated.getAnnotations()) { - String fqcn = annotation.getQualifiedName(); - if (fqcn != null && (fqcn.equals(SUPPRESS_LINT_FQCN) || fqcn.equals(SUPPRESS_WARNINGS_FQCN))) { - List<UNamedExpression> parameterList = annotation.getAttributeValues(); - for (UNamedExpression pair : parameterList) { - UExpression v = pair.getExpression(); - if (v instanceof ULiteralExpression) { - ULiteralExpression literal = (ULiteralExpression)v; - Object value = literal.getValue(); - if (value instanceof String) { - if (isSuppressed(issue, (String) value)) { - return true; - } - } - } else if (UastExpressionUtils.isArrayInitializer(v)) { - UCallExpression mv = (UCallExpression)v; - for (UExpression mmv : mv.getValueArguments()) { - if (mmv instanceof ULiteralExpression) { - ULiteralExpression literal = (ULiteralExpression) mmv; - Object value = literal.getValue(); - if (value instanceof String) { - if (isSuppressed(issue, (String) value)) { - return true; - } - } - } - } - } - } - } - } - } - element = element.getUastParent(); - } - - return false; - } - - /** - * Returns true if the given issue is suppressed by the given suppress string; this - * is typically the same as the issue id, but is allowed to not match case sensitively, - * and is allowed to be a comma separated list, and can be the string "all" - * - * @param issue the issue id to match - * @param string the suppress string -- typically the id, or "all", or a comma separated list of ids - * @return true if the issue is suppressed by the given string - */ - private static boolean isSuppressed(@NonNull Issue issue, @NonNull String string) { - for (String id : Splitter.on(',').trimResults().split(string)) { - if (id.equals(issue.getId()) || id.equals(SUPPRESS_ALL)) { - return true; - } - } - - return false; - } - - /** Returns the internal method name */ - @NonNull - public static String getInternalMethodName(@NonNull PsiMethod method) { - if (method.isConstructor()) { - return SdkConstants.CONSTRUCTOR_NAME; - } - else { - return method.getName(); - } - } - - @Nullable - public static PsiElement getCallName(@NonNull PsiCallExpression expression) { - PsiElement firstChild = expression.getFirstChild(); - if (firstChild != null) { - PsiElement lastChild = firstChild.getLastChild(); - if (lastChild instanceof PsiIdentifier) { - return lastChild; - } - } - return null; - } - - - /** - * Computes the internal class name of the given class. - * For example, for PsiClass foo.bar.Foo.Bar it returns foo/bar/Foo$Bar. - * - * @param psiClass the class to look up the internal name for - * @return the internal class name - * @see ClassContext#getInternalName(String) - */ - @Nullable - public static String getInternalName(@NonNull PsiClass psiClass) { - if (psiClass instanceof PsiAnonymousClass) { - PsiClass parent = PsiTreeUtil.getParentOfType(psiClass, PsiClass.class); - if (parent != null) { - String internalName = getInternalName(parent); - if (internalName == null) { - return null; - } - return internalName + JavaAnonymousClassesHelper.getName((PsiAnonymousClass)psiClass); - } - } - String sig = ClassUtil.getJVMClassName(psiClass); - if (sig == null) { - String qualifiedName = psiClass.getQualifiedName(); - if (qualifiedName != null) { - return ClassContext.getInternalName(qualifiedName); - } - return null; - } else if (sig.indexOf('.') != -1) { - // Workaround -- ClassUtil doesn't treat this correctly! - // .replace('.', '/'); - sig = ClassContext.getInternalName(sig); - } - return sig; - } - - /** - * Computes the internal class name of the given class type. - * For example, for PsiClassType foo.bar.Foo.Bar it returns foo/bar/Foo$Bar. - * - * @param psiClassType the class type to look up the internal name for - * @return the internal class name - * @see ClassContext#getInternalName(String) - */ - @Nullable - public static String getInternalName(@NonNull PsiClassType psiClassType) { - PsiClass resolved = psiClassType.resolve(); - if (resolved != null) { - return getInternalName(resolved); - } - - String className = psiClassType.getClassName(); - if (className != null) { - return ClassContext.getInternalName(className); - } - - return null; - } - - /** - * Computes the internal JVM description of the given method. This is in the same - * format as the ASM desc fields for methods; meaning that a method named foo which for example takes an - * int and a String and returns a void will have description {@code foo(ILjava/lang/String;):V}. - * - * @param method the method to look up the description for - * @param includeName whether the name should be included - * @param includeReturn whether the return type should be included - * @return the internal JVM description for this method - */ - @Nullable - public static String getInternalDescription(@NonNull PsiMethod method, boolean includeName, boolean includeReturn) { - assert !includeName; // not yet tested - assert !includeReturn; // not yet tested - - StringBuilder signature = new StringBuilder(); - - if (includeName) { - if (method.isConstructor()) { - final PsiClass declaringClass = method.getContainingClass(); - if (declaringClass != null) { - final PsiClass outerClass = declaringClass.getContainingClass(); - if (outerClass != null) { - // declaring class is an inner class - if (!declaringClass.hasModifierProperty(PsiModifier.STATIC)) { - if (!appendJvmTypeName(signature, outerClass)) { - return null; - } - } - } - } - signature.append(CONSTRUCTOR_NAME); - } else { - signature.append(method.getName()); - } - } - - signature.append('('); - - for (PsiParameter psiParameter : method.getParameterList().getParameters()) { - if (!appendJvmSignature(signature, psiParameter.getType())) { - return null; - } - } - signature.append(')'); - if (includeReturn) { - if (!method.isConstructor()) { - if (!appendJvmSignature(signature, method.getReturnType())) { - return null; - } - } - else { - signature.append('V'); - } - } - return signature.toString(); - } - - private static boolean appendJvmTypeName(@NonNull StringBuilder signature, @NonNull PsiClass outerClass) { - String className = getInternalName(outerClass); - if (className == null) { - return false; - } - signature.append('L').append(className.replace('.', '/')).append(';'); - return true; - } - - private static boolean appendJvmSignature(@NonNull StringBuilder buffer, @Nullable PsiType type) { - if (type == null) { - return false; - } - final PsiType psiType = TypeConversionUtil.erasure(type); - if (psiType instanceof PsiArrayType) { - buffer.append('['); - appendJvmSignature(buffer, ((PsiArrayType)psiType).getComponentType()); - } - else if (psiType instanceof PsiClassType) { - PsiClass resolved = ((PsiClassType)psiType).resolve(); - if (resolved == null) { - return false; - } - if (!appendJvmTypeName(buffer, resolved)) { - return false; - } - } - else if (psiType instanceof PsiPrimitiveType) { - buffer.append(JVMNameUtil.getPrimitiveSignature(psiType.getCanonicalText())); - } - else { - return false; - } - return true; - } - - /** Returns the resource directories to use for the given module */ - @NotNull - public static List<File> getResourceDirectories(@NotNull AndroidFacet facet) { - if (facet.requiresAndroidModel()) { - AndroidModel androidModel = facet.getAndroidModel(); - if (androidModel != null) { - List<File> resDirectories = new ArrayList<File>(); - List<SourceProvider> sourceProviders = androidModel.getActiveSourceProviders(); - for (SourceProvider provider : sourceProviders) { - for (File file : provider.getResDirectories()) { - if (file.isDirectory()) { - resDirectories.add(file); - } - } - } - return resDirectories; - } - } - return new ArrayList<File>(facet.getMainSourceProvider().getResDirectories()); - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijViewTypeDetector.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijViewTypeDetector.java deleted file mode 100644 index 287fe0e..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijViewTypeDetector.java +++ /dev/null
@@ -1,49 +0,0 @@ -/* - * 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.klint; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.res2.AbstractResourceRepository; -import com.android.ide.common.res2.ResourceItem; -import com.android.tools.idea.res.LocalResourceRepository; -import com.android.tools.klint.checks.ViewTypeDetector; -import com.android.tools.klint.detector.api.Context; -import com.android.tools.klint.detector.api.Implementation; -import com.android.tools.klint.detector.api.Scope; - -import java.util.Collection; -import java.util.Collections; - -public class IntellijViewTypeDetector extends ViewTypeDetector { - static final Implementation IMPLEMENTATION = new Implementation( - IntellijViewTypeDetector.class, - Scope.JAVA_FILE_SCOPE); - - @Nullable - @Override - protected Collection<String> getViewTags(@NonNull Context context, @NonNull ResourceItem item) { - AbstractResourceRepository projectResources = context.getClient().getProjectResources(context.getMainProject(), true); - assert projectResources instanceof LocalResourceRepository : projectResources; - LocalResourceRepository repository = (LocalResourceRepository)projectResources; - String viewTag = repository.getViewTag(item); - if (viewTag != null) { - return Collections.singleton(viewTag); - } - - return super.getViewTags(context, item); - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/LintInspectionDescriptionLinkHandler.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/LintInspectionDescriptionLinkHandler.java deleted file mode 100644 index dd910a3..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/LintInspectionDescriptionLinkHandler.java +++ /dev/null
@@ -1,64 +0,0 @@ -/* - * 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.klint; - -import com.android.tools.klint.checks.BuiltinIssueRegistry; -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.TextFormat; -import com.intellij.codeInsight.highlighting.TooltipLinkHandler; -import com.intellij.codeInspection.InspectionProfile; -import com.intellij.codeInspection.InspectionsBundle; -import com.intellij.codeInspection.ex.InspectionToolWrapper; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.profile.codeInspection.InspectionProfileManager; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.NotNull; - -public class LintInspectionDescriptionLinkHandler extends TooltipLinkHandler { - private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.inspections.lint.LintInspectionDescriptionLinkHandler"); - - @Override - public String getDescription(@NotNull final String refSuffix, @NotNull final Editor editor) { - final Project project = editor.getProject(); - if (project == null) { - LOG.error(editor); - return null; - } - - final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); - if (file == null) { - return null; - } - - Issue issue = new BuiltinIssueRegistry().getIssue(refSuffix); - if (issue != null) { - String html = issue.getExplanation(TextFormat.HTML); - // IntelliJ seems to treat newlines in the HTML as needing to also be converted to <br> (whereas - // Lint includes these for HTML readability but they shouldn't add additional lines since it has - // already added <br> as well) so strip these out - html = html.replace("\n", ""); - return html; - } - - // TODO: What about custom registries for custom rules, AARs etc? - - LOG.warn("No description for inspection '" + refSuffix + "'"); - return InspectionsBundle.message("inspection.tool.description.under.construction.text"); - } -} \ No newline at end of file
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt deleted file mode 100644 index e938134..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt +++ /dev/null
@@ -1,41 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * 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.klint - -import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.android.util.AndroidBundle -import org.jetbrains.kotlin.android.canAddParcelable -import org.jetbrains.kotlin.android.implementParcelable -import org.jetbrains.kotlin.android.isParcelize -import org.jetbrains.kotlin.psi.KtClass - - -class ParcelableQuickFix : AndroidLintQuickFix { - override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) { - startElement.getTargetClass()?.implementParcelable() - } - - override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean { - val targetClass = startElement.getTargetClass() ?: return false - return targetClass.canAddParcelable() && !targetClass.isParcelize() - } - - override fun getName(): String = AndroidBundle.message("implement.parcelable.intention.text") - - private fun PsiElement.getTargetClass(): KtClass? = PsiTreeUtil.getParentOfType(this, KtClass::class.java, false) -} \ No newline at end of file
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ProblemData.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ProblemData.java deleted file mode 100644 index 33abc54..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ProblemData.java +++ /dev/null
@@ -1,41 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.tools.klint.detector.api.Issue; -import com.android.tools.klint.detector.api.Severity; -import com.intellij.openapi.util.TextRange; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class ProblemData { - private final Issue myIssue; - private final String myMessage; - private final TextRange myTextRange; - private final Severity myConfiguredSeverity; - - ProblemData(@NotNull Issue issue, @NotNull String message, @NotNull TextRange textRange, @Nullable Severity configuredSeverity) { - myIssue = issue; - myTextRange = textRange; - myMessage = message; - myConfiguredSeverity = configuredSeverity; - } - - @NotNull - public Issue getIssue() { - return myIssue; - } - - @NotNull - public TextRange getTextRange() { - return myTextRange; - } - - @NotNull - public String getMessage() { - return myMessage; - } - - @Nullable - public Severity getConfiguredSeverity() { - return myConfiguredSeverity; - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/State.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/State.java deleted file mode 100644 index b122b49..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/State.java +++ /dev/null
@@ -1,63 +0,0 @@ -package org.jetbrains.android.inspections.klint; - -import com.android.tools.klint.detector.api.Issue; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class State { - private final Module myModule; - private final VirtualFile myMainFile; - - private final String myMainFileContent; - private final List<ProblemData> myProblems = new ArrayList<ProblemData>(); - private final List<Issue> myIssues; - - private volatile boolean myDirty; - - State(@NotNull Module module, - @NotNull VirtualFile mainFile, - @NotNull String mainFileContent, - @NotNull List<Issue> issues) { - myModule = module; - myMainFile = mainFile; - myMainFileContent = mainFileContent; - myIssues = issues; - } - - @NotNull - public VirtualFile getMainFile() { - return myMainFile; - } - - @NotNull - public String getMainFileContent() { - return myMainFileContent; - } - - public void markDirty() { - myDirty = true; - } - - public boolean isDirty() { - return myDirty; - } - - @NotNull - public Module getModule() { - return myModule; - } - - @NotNull - public List<ProblemData> getProblems() { - return myProblems; - } - - @NotNull - public List<Issue> getIssues() { - return myIssues; - } -}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt deleted file mode 100644 index 0d8d750..0000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt +++ /dev/null
@@ -1,110 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * 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.klint - -import com.android.SdkConstants.FQCN_SUPPRESS_LINT -import com.intellij.codeInsight.FileModificationService -import com.intellij.codeInsight.intention.IntentionAction -import com.intellij.icons.AllIcons -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Iconable -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.android.util.AndroidBundle -import org.jetbrains.kotlin.android.hasBackingField -import org.jetbrains.kotlin.idea.util.addAnnotation -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.* -import javax.swing.Icon - - -class SuppressLintIntentionAction(val id: String, val element: PsiElement) : IntentionAction, Iconable { - - private companion object { - val INTENTION_NAME_PREFIX = "AndroidKLint" - val SUPPRESS_LINT_MESSAGE = "android.lint.fix.suppress.lint.api.annotation" - val FQNAME_SUPPRESS_LINT = FqName(FQCN_SUPPRESS_LINT) - } - - private val lintId = getLintId(id) - - override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?) = true - - override fun getText(): String = AndroidBundle.message(SUPPRESS_LINT_MESSAGE, lintId) - - override fun getFamilyName() = text - - override fun getIcon(flags: Int): Icon? = AllIcons.Actions.Cancel - - override fun startInWriteAction() = true - - override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { - if (file !is KtFile) { - return - } - - val annotationContainer = PsiTreeUtil.findFirstParent(element, true) { it.isSuppressLintTarget() } ?: return - if (!FileModificationService.getInstance().preparePsiElementForWrite(annotationContainer)) { - return - } - - val argument = "\"$lintId\"" - - when (annotationContainer) { - is KtModifierListOwner -> annotationContainer.addAnnotation( - FQNAME_SUPPRESS_LINT, - argument, - whiteSpaceText = if (annotationContainer.isNewLineNeededForAnnotation()) "\n" else " ", - addToExistingAnnotation = { entry -> addArgumentToAnnotation(entry, argument) }) - } - } - - private fun addArgumentToAnnotation(entry: KtAnnotationEntry, argument: String): Boolean { - // add new arguments to an existing entry - val args = entry.valueArgumentList - val psiFactory = KtPsiFactory(entry) - val newArgList = psiFactory.createCallArguments("($argument)") - when { - args == null -> // new argument list - entry.addAfter(newArgList, entry.lastChild) - args.arguments.isEmpty() -> // replace '()' with a new argument list - args.replace(newArgList) - args.arguments.none { it.textMatches(argument) } -> - args.addArgument(newArgList.arguments[0]) - } - - return true - } - - private fun getLintId(intentionId: String) = - if (intentionId.startsWith(INTENTION_NAME_PREFIX)) intentionId.substring(INTENTION_NAME_PREFIX.length) else intentionId - - private fun KtElement.isNewLineNeededForAnnotation(): Boolean { - return !(this is KtParameter || - this is KtTypeParameter || - this is KtPropertyAccessor) - } - - private fun PsiElement.isSuppressLintTarget(): Boolean { - return this is KtDeclaration && - (this as? KtProperty)?.hasBackingField() ?: true && - this !is KtFunctionLiteral && - this !is KtDestructuringDeclaration - } -} \ No newline at end of file