Drop custom Kotlin Android lint, register quickfixes with extension point
diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
index 46a00cf..60606fc 100755
--- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
+++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
@@ -27,7 +27,6 @@
import org.jetbrains.kotlin.android.folding.AbstractAndroidResourceFoldingTest
import org.jetbrains.kotlin.android.intention.AbstractAndroidIntentionTest
import org.jetbrains.kotlin.android.intention.AbstractAndroidResourceIntentionTest
-import org.jetbrains.kotlin.android.lint.AbstractKotlinLintTest
import org.jetbrains.kotlin.android.quickfix.AbstractAndroidLintQuickfixTest
import org.jetbrains.kotlin.android.quickfix.AbstractAndroidQuickFixMultiFileTest
import org.jetbrains.kotlin.annotation.AbstractAnnotationProcessorBoxTest
@@ -1216,10 +1215,6 @@
model("android/quickfix", pattern = """^(\w+)\.((before\.Main\.\w+)|(test))$""", testMethod = "doTestWithExtraFile")
}
- testClass<AbstractKotlinLintTest> {
- model("android/lint", excludeParentDirs = true)
- }
-
testClass<AbstractAndroidLintQuickfixTest> {
model("android/lintQuickfix", pattern = "^([\\w\\-_]+)\\.kt$")
}
diff --git a/idea/idea-android/idea-android.iml b/idea/idea-android/idea-android.iml
index e656260..fa123b9 100644
--- a/idea/idea-android/idea-android.iml
+++ b/idea/idea-android/idea-android.iml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
@@ -20,7 +20,6 @@
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="tests-common" scope="TEST" />
<orderEntry type="module" module-name="idea-core" />
- <orderEntry type="module" module-name="lint-idea" scope="TEST" />
<orderEntry type="library" name="uast-java" level="project" />
<orderEntry type="library" name="junit-plugin" level="project" />
<orderEntry type="library" name="kotlin-reflect" level="project" />
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetApiQuickFix.kt
similarity index 88%
rename from plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt
rename to idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetApiQuickFix.kt
index 16618a4..4b462b7 100644
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetApiQuickFix.kt
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package org.jetbrains.android.inspections.klint
+package org.jetbrains.kotlin.android.quickfix
import com.android.SdkConstants
-import com.android.tools.klint.checks.ApiDetector.REQUIRES_API_ANNOTATION
+import com.android.tools.lint.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.inspections.lint.AndroidLintQuickFix
+import org.jetbrains.android.inspections.lint.AndroidQuickfixContexts
import org.jetbrains.android.util.AndroidBundle
import org.jetbrains.kotlin.idea.util.addAnnotation
import org.jetbrains.kotlin.name.FqName
@@ -58,9 +60,9 @@
if (annotationContainer is KtModifierListOwner) {
annotationContainer.addAnnotation(
- if (useRequiresApi) FQNAME_REQUIRES_API else FQNAME_TARGET_API,
- getAnnotationValue(true),
- whiteSpaceText = "\n")
+ if (useRequiresApi) FQNAME_REQUIRES_API else FQNAME_TARGET_API,
+ getAnnotationValue(true),
+ whiteSpaceText = "\n")
}
}
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetVersionCheckQuickFix.kt
similarity index 94%
rename from plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt
rename to idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetVersionCheckQuickFix.kt
index e38cfcb..9710636 100644
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetVersionCheckQuickFix.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package org.jetbrains.android.inspections.klint
+package org.jetbrains.kotlin.android.quickfix
import com.intellij.codeInsight.FileModificationService
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.android.inspections.lint.AndroidLintQuickFix
+import org.jetbrains.android.inspections.lint.AndroidQuickfixContexts
import org.jetbrains.kotlin.idea.codeInsight.surroundWith.statement.KotlinIfSurrounder
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.inspections.findExistingEditor
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ApiUtils.kt
similarity index 84%
rename from plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt
rename to idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ApiUtils.kt
index e3509d2..3a52671 100644
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ApiUtils.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package org.jetbrains.android.inspections.klint
+package org.jetbrains.kotlin.android.quickfix
-import com.android.sdklib.SdkVersionInfo
+import com.android.sdklib.SdkVersionInfo.*
-fun getVersionField(api: Int, fullyQualified: Boolean): String = SdkVersionInfo.getBuildCode(api)?.let {
+fun getVersionField(api: Int, fullyQualified: Boolean): String = getBuildCode(api)?.let {
if (fullyQualified) "android.os.Build.VERSION_CODES.$it" else it
} ?: api.toString()
\ No newline at end of file
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/KotlinAndroidQuickFixProvider.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/KotlinAndroidQuickFixProvider.kt
new file mode 100644
index 0000000..050b103
--- /dev/null
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/KotlinAndroidQuickFixProvider.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.kotlin.android.quickfix
+
+import com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX
+import com.android.tools.lint.checks.ApiDetector
+import com.android.tools.lint.checks.CommentDetector
+import com.android.tools.lint.checks.ParcelDetector
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.TextFormat
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiElement
+import com.intellij.psi.search.GlobalSearchScope
+import org.jetbrains.android.inspections.lint.AndroidLintQuickFix
+import org.jetbrains.android.inspections.lint.AndroidLintQuickFixProvider
+
+
+class KotlinAndroidQuickFixProvider : AndroidLintQuickFixProvider {
+ override fun getQuickFixes(
+ issue: Issue,
+ startElement: PsiElement,
+ endElement: PsiElement,
+ message: String,
+ data: Any?
+ ): Array<AndroidLintQuickFix> {
+ val fixes: Array<AndroidLintQuickFix> = when (issue) {
+ ApiDetector.UNSUPPORTED, ApiDetector.INLINED -> getApiQuickFixes(issue, startElement, message)
+ ParcelDetector.ISSUE -> arrayOf(ParcelableQuickFix())
+ else -> emptyArray()
+ }
+
+ if (issue != CommentDetector.STOP_SHIP) {
+ return fixes + SuppressLintQuickFix(issue.id)
+ }
+
+ return fixes
+ }
+
+ fun getApiQuickFixes(issue: Issue, element: PsiElement, message: String): Array<AndroidLintQuickFix> {
+ val api = ApiDetector.getRequiredVersion(issue, message, TextFormat.RAW)
+ if (api == -1) {
+ return AndroidLintQuickFix.EMPTY_ARRAY
+ }
+
+ val project = element.project
+ if (JavaPsiFacade.getInstance(project).findClass(REQUIRES_API_ANNOTATION, GlobalSearchScope.allScope(project)) != null) {
+ return arrayOf(AddTargetApiQuickFix(api, true), AddTargetApiQuickFix(api, false), AddTargetVersionCheckQuickFix(api))
+ }
+
+ return arrayOf(AddTargetApiQuickFix(api, false), AddTargetVersionCheckQuickFix(api))
+ }
+
+ companion object {
+ val REQUIRES_API_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "RequiresApi"
+ }
+}
\ No newline at end of file
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ParcelableQuickFix.kt
similarity index 89%
rename from plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt
rename to idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ParcelableQuickFix.kt
index 527bf64..dc93707 100644
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ParcelableQuickFix.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package org.jetbrains.android.inspections.klint
+package org.jetbrains.kotlin.android.quickfix
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.android.inspections.lint.AndroidLintQuickFix
+import org.jetbrains.android.inspections.lint.AndroidQuickfixContexts
import org.jetbrains.android.util.AndroidBundle
import org.jetbrains.kotlin.android.canAddParcelable
import org.jetbrains.kotlin.android.implementParcelable
diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/SuppressLintQuickFix.kt
similarity index 61%
rename from plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt
rename to idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/SuppressLintQuickFix.kt
index e3ef7e9..d12b286 100644
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/SuppressLintQuickFix.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2016 JetBrains s.r.o.
+ * 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.
@@ -14,51 +14,25 @@
* limitations under the License.
*/
-package org.jetbrains.android.inspections.klint
+package org.jetbrains.kotlin.android.quickfix
-import com.android.SdkConstants.FQCN_SUPPRESS_LINT
+import com.android.SdkConstants
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.inspections.lint.AndroidLintQuickFix
+import org.jetbrains.android.inspections.lint.AndroidQuickfixContexts
import org.jetbrains.android.util.AndroidBundle
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)
- }
-
+class SuppressLintQuickFix(id: String) : AndroidLintQuickFix {
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
+ override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) {
+ val annotationContainer = PsiTreeUtil.findFirstParent(startElement, true) { it.isSuppressLintTarget() } ?: return
if (!FileModificationService.getInstance().preparePsiElementForWrite(annotationContainer)) {
return
}
@@ -66,14 +40,24 @@
val argument = "\"$lintId\""
when (annotationContainer) {
- is KtModifierListOwner -> annotationContainer.addAnnotation(
- FQNAME_SUPPRESS_LINT,
- argument,
- whiteSpaceText = if (annotationContainer.isNewLineNeededForAnnotation()) "\n" else " ",
- addToExistingAnnotation = { entry -> addArgumentToAnnotation(entry, argument) })
+ is KtModifierListOwner -> {
+ annotationContainer.addAnnotation(
+ FQNAME_SUPPRESS_LINT,
+ argument,
+ whiteSpaceText = if (annotationContainer.isNewLineNeededForAnnotation()) "\n" else " ",
+ addToExistingAnnotation = { entry -> addArgumentToAnnotation(entry, argument) })
+ }
}
}
+ override fun getName(): String = AndroidBundle.message(SUPPRESS_LINT_MESSAGE, lintId)
+
+ override fun isApplicable(
+ startElement: PsiElement,
+ endElement: PsiElement,
+ contextType: AndroidQuickfixContexts.ContextType
+ ): Boolean = true
+
private fun addArgumentToAnnotation(entry: KtAnnotationEntry, argument: String): Boolean {
// add new arguments to an existing entry
val args = entry.valueArgumentList
@@ -102,4 +86,9 @@
private fun PsiElement.isSuppressLintTarget() = this is KtDeclaration &&
this !is KtDestructuringDeclaration &&
this !is KtFunctionLiteral
+ private companion object {
+ val INTENTION_NAME_PREFIX = "AndroidLint"
+ val SUPPRESS_LINT_MESSAGE = "android.lint.fix.suppress.lint.api.annotation"
+ val FQNAME_SUPPRESS_LINT = FqName(SdkConstants.FQCN_SUPPRESS_LINT)
+ }
}
\ No newline at end of file
diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/AbstractKotlinLintTest.kt b/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/AbstractKotlinLintTest.kt
deleted file mode 100644
index 5bc5668..0000000
--- a/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/AbstractKotlinLintTest.kt
+++ /dev/null
@@ -1,72 +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.kotlin.android.lint
-
-import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
-import org.jetbrains.kotlin.android.KotlinAndroidTestCase
-import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
-import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
-import java.io.File
-
-abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
-
- override fun setUp() {
- super.setUp()
- ConfigLibraryUtil.configureKotlinRuntime(myModule)
- AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
- // needs access to .class files in kotlin runtime jar
- myFixture.allowTreeAccessForAllFiles()
- }
-
- override fun tearDown() {
- ConfigLibraryUtil.unConfigureKotlinRuntime(myModule)
- super.tearDown()
- }
-
- fun doTest(filename: String) {
- val ktFile = File(filename)
- val mainInspectionClassName = findStringWithPrefixes(ktFile.readText(), "// INSPECTION_CLASS: ") ?: error("Empty class name")
-
- val inspectionClassNames = mutableListOf(mainInspectionClassName)
- for (i in 2..100) {
- val className = findStringWithPrefixes(ktFile.readText(), "// INSPECTION_CLASS$i: ") ?: break
- inspectionClassNames += className
- }
-
- myFixture.enableInspections(*inspectionClassNames.map { className ->
- val inspectionClass = Class.forName(className)
- inspectionClass.newInstance() as AndroidLintInspectionBase
- }.toTypedArray())
-
- val additionalResourcesDir = File(ktFile.parentFile, getTestName(true))
- if (additionalResourcesDir.exists()) {
- for (file in additionalResourcesDir.listFiles()) {
- if (file.isFile) {
- myFixture.copyFileToProject(file.absolutePath, file.name)
- }
- else if (file.isDirectory) {
- myFixture.copyDirectoryToProject(file.absolutePath, file.name)
- }
- }
- }
-
- val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt")
- myFixture.configureFromExistingVirtualFile(virtualFile)
-
- myFixture.checkHighlighting(true, false, false)
- }
-}
\ No newline at end of file
diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/KotlinLintTestGenerated.java b/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/KotlinLintTestGenerated.java
deleted file mode 100644
index e1c0526..0000000
--- a/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/KotlinLintTestGenerated.java
+++ /dev/null
@@ -1,194 +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.kotlin.android.lint;
-
-import com.intellij.testFramework.TestDataPath;
-import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
-import org.jetbrains.kotlin.test.KotlinTestUtils;
-import org.jetbrains.kotlin.test.TargetBackend;
-import org.jetbrains.kotlin.test.TestMetadata;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.regex.Pattern;
-
-/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
-@SuppressWarnings("all")
-@TestMetadata("idea/testData/android/lint")
-@TestDataPath("$PROJECT_ROOT")
-@RunWith(JUnit3RunnerWithInners.class)
-public class KotlinLintTestGenerated extends AbstractKotlinLintTest {
- @TestMetadata("alarm.kt")
- public void testAlarm() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/alarm.kt");
- doTest(fileName);
- }
-
- public void testAllFilesPresentInLint() throws Exception {
- KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/lint"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
- }
-
- @TestMetadata("apiCheck.kt")
- public void testApiCheck() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/apiCheck.kt");
- doTest(fileName);
- }
-
- @TestMetadata("callSuper.kt")
- public void testCallSuper() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/callSuper.kt");
- doTest(fileName);
- }
-
- @TestMetadata("closeCursor.kt")
- public void testCloseCursor() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/closeCursor.kt");
- doTest(fileName);
- }
-
- @TestMetadata("commitFragment.kt")
- public void testCommitFragment() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/commitFragment.kt");
- doTest(fileName);
- }
-
- @TestMetadata("javaPerformance.kt")
- public void testJavaPerformance() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/javaPerformance.kt");
- doTest(fileName);
- }
-
- @TestMetadata("javaScriptInterface.kt")
- public void testJavaScriptInterface() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/javaScriptInterface.kt");
- doTest(fileName);
- }
-
- @TestMetadata("layoutInflation.kt")
- public void testLayoutInflation() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/layoutInflation.kt");
- doTest(fileName);
- }
-
- @TestMetadata("log.kt")
- public void testLog() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/log.kt");
- doTest(fileName);
- }
-
- @TestMetadata("noInternationalSms.kt")
- public void testNoInternationalSms() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/noInternationalSms.kt");
- doTest(fileName);
- }
-
- @TestMetadata("overrideConcrete.kt")
- public void testOverrideConcrete() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/overrideConcrete.kt");
- doTest(fileName);
- }
-
- @TestMetadata("parcel.kt")
- public void testParcel() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/parcel.kt");
- doTest(fileName);
- }
-
- @TestMetadata("sdCardTest.kt")
- public void testSdCardTest() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/sdCardTest.kt");
- doTest(fileName);
- }
-
- @TestMetadata("setJavaScriptEnabled.kt")
- public void testSetJavaScriptEnabled() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/setJavaScriptEnabled.kt");
- doTest(fileName);
- }
-
- @TestMetadata("sharedPrefs.kt")
- public void testSharedPrefs() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/sharedPrefs.kt");
- doTest(fileName);
- }
-
- @TestMetadata("showDiagnosticsWhenFileIsRed.kt")
- public void testShowDiagnosticsWhenFileIsRed() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/showDiagnosticsWhenFileIsRed.kt");
- doTest(fileName);
- }
-
- @TestMetadata("sqlite.kt")
- public void testSqlite() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/sqlite.kt");
- doTest(fileName);
- }
-
- @TestMetadata("systemServices.kt")
- public void testSystemServices() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/systemServices.kt");
- doTest(fileName);
- }
-
- @TestMetadata("toast.kt")
- public void testToast() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/toast.kt");
- doTest(fileName);
- }
-
- @TestMetadata("valueOf.kt")
- public void testValueOf() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/valueOf.kt");
- doTest(fileName);
- }
-
- @TestMetadata("velocityTrackerRecycle.kt")
- public void testVelocityTrackerRecycle() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/velocityTrackerRecycle.kt");
- doTest(fileName);
- }
-
- @TestMetadata("viewConstructor.kt")
- public void testViewConstructor() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/viewConstructor.kt");
- doTest(fileName);
- }
-
- @TestMetadata("viewHolder.kt")
- public void testViewHolder() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/viewHolder.kt");
- doTest(fileName);
- }
-
- @TestMetadata("wrongAnnotation.kt")
- public void testWrongAnnotation() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/wrongAnnotation.kt");
- doTest(fileName);
- }
-
- @TestMetadata("wrongImport.kt")
- public void testWrongImport() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/wrongImport.kt");
- doTest(fileName);
- }
-
- @TestMetadata("wrongViewCall.kt")
- public void testWrongViewCall() throws Exception {
- String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lint/wrongViewCall.kt");
- doTest(fileName);
- }
-}
diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt
index b46f1d7..c2f6989 100644
--- a/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt
+++ b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt
@@ -19,7 +19,7 @@
import com.intellij.codeInspection.InspectionProfileEntry
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.PathUtil
-import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
+import org.jetbrains.android.inspections.lint.AndroidLintInspectionBase
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.io.File
diff --git a/idea/src/META-INF/android-lint.xml b/idea/src/META-INF/android-lint.xml
index c930aff..eb81b66 100644
--- a/idea/src/META-INF/android-lint.xml
+++ b/idea/src/META-INF/android-lint.xml
@@ -1,122 +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"/>
<projectService serviceInterface="org.jetbrains.uast.kotlin.KotlinUastBindingContextProviderService"
serviceImplementation="org.jetbrains.uast.kotlin.internal.IdeaKotlinUastBindingContextProviderService"/>
-
- <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>
<extensions defaultExtensionNs="org.jetbrains.uast">
diff --git a/idea/src/META-INF/android.xml b/idea/src/META-INF/android.xml
index 976bbf1..9437800 100644
--- a/idea/src/META-INF/android.xml
+++ b/idea/src/META-INF/android.xml
@@ -76,6 +76,10 @@
<projectConfigurator implementation="org.jetbrains.kotlin.android.configure.KotlinAndroidGradleModuleConfigurator"/>
</extensions>
+ <extensions defaultExtensionNs="org.jetbrains.android">
+ <androidLintQuickFixProvider implementation="org.jetbrains.kotlin.android.quickfix.KotlinAndroidQuickFixProvider" />
+ </extensions>
+
<project-components>
<component>
<interface-class>org.jetbrains.kotlin.android.facet.KotlinAndroidStartupManager</interface-class>
diff --git a/idea/testData/android/lint/alarm.kt b/idea/testData/android/lint/alarm.kt
deleted file mode 100644
index eaa7dfb1..0000000
--- a/idea/testData/android/lint/alarm.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintShortAlarmInspection
-
-import android.app.AlarmManager
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class TestAlarm {
- fun test(alarmManager: AlarmManager) {
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, 60000, null); // OK
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 6000, 70000, null); // OK
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, <warning descr="Value will be forced up to 5000 as of Android 5.1; don't rely on this to be exact">50</warning>, <warning descr="Value will be forced up to 60000 as of Android 5.1; don't rely on this to be exact">10</warning>, null); // ERROR
-
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, // ERROR
- <warning descr="Value will be forced up to 60000 as of Android 5.1; don't rely on this to be exact">OtherClass.MY_INTERVAL</warning>, null); // ERROR
-
- val interval = 10;
- val interval2 = 2L * interval;
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, <warning descr="Value will be forced up to 60000 as of Android 5.1; don't rely on this to be exact">interval2</warning>, null); // ERROR
- }
-
- private object OtherClass {
- val MY_INTERVAL = 1000L;
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/apiCheck.kt b/idea/testData/android/lint/apiCheck.kt
deleted file mode 100644
index e6f3d75..0000000
--- a/idea/testData/android/lint/apiCheck.kt
+++ /dev/null
@@ -1,479 +0,0 @@
-
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
-// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
-// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection
-
-import android.animation.RectEvaluator
-import android.annotation.SuppressLint
-import android.annotation.TargetApi
-import org.w3c.dom.DOMError
-import org.w3c.dom.DOMErrorHandler
-import org.w3c.dom.DOMLocator
-
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.app.Activity
-import android.app.ApplicationErrorReport
-import android.graphics.drawable.VectorDrawable
-import android.graphics.Path
-import android.graphics.PorterDuff
-import android.graphics.Rect
-import android.os.Build
-import android.widget.*
-import dalvik.bytecode.OpcodeInfo
-
-import android.os.Build.VERSION
-import <warning descr="Field requires API level 4 (current min is 1): `android.os.Build.VERSION#SDK_INT`">android.os.Build.VERSION.SDK_INT</warning>
-import android.os.Build.VERSION_CODES
-import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH
-import android.os.Build.VERSION_CODES.JELLY_BEAN
-import android.os.Bundle
-import android.os.Parcelable
-import android.system.ErrnoException
-import android.widget.TextView
-
-@Suppress("SENSELESS_COMPARISON", "UNUSED_EXPRESSION", "UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION", "USELESS_CAST")
-class ApiCallTest: Activity() {
-
- fun method(chronometer: Chronometer, locator: DOMLocator) {
- chronometer.<error descr="Call requires API level 16 (current min is 1): android.view.View#setBackground">setBackground</error>(null)
-
- // Ok
- Bundle().getInt("")
-
- View.<warning descr="Field requires API level 16 (current min is 1): `android.view.View#SYSTEM_UI_FLAG_FULLSCREEN`">SYSTEM_UI_FLAG_FULLSCREEN</warning>
-
- // Virtual call
- <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">getActionBar</error>() // API 11
- <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">actionBar</error> // API 11
-
- // Class references (no call or field access)
- val error: DOMError? = null // API 8
- val clz = <error descr="Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler">DOMErrorHandler::class</error> // API 8
-
- // Method call
- chronometer.<error descr="Call requires API level 3 (current min is 1): android.widget.Chronometer#getOnChronometerTickListener">onChronometerTickListener</error> // API 3
-
- // Inherited method call (from TextView
- chronometer.<error descr="Call requires API level 11 (current min is 1): android.widget.TextView#setTextIsSelectable">setTextIsSelectable</error>(true) // API 11
-
- <error descr="Class requires API level 14 (current min is 1): android.widget.GridLayout">GridLayout::class</error>
-
- // Field access
- val field = OpcodeInfo.<warning descr="Field requires API level 11 (current min is 1): `dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE`">MAXIMUM_VALUE</warning> // API 11
-
-
- val fillParent = LayoutParams.FILL_PARENT // API 1
- // This is a final int, which means it gets inlined
- val matchParent = LayoutParams.MATCH_PARENT // API 8
- // Field access: non final
- val batteryInfo = report!!.<error descr="Field requires API level 14 (current min is 1): `android.app.ApplicationErrorReport#batteryInfo`">batteryInfo</error>
-
- // Enum access
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
- val mode = PorterDuff.Mode.<error descr="Field requires API level 11 (current min is 1): `android.graphics.PorterDuff.Mode#OVERLAY`">OVERLAY</error> // API 11
- }
- }
-
- fun test(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- RectEvaluator(rect); // OK
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- if (rect != null) {
- RectEvaluator(rect); // OK
- }
- }
- }
-
- fun test2(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- RectEvaluator(rect); // OK
- }
- }
-
- fun test3(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
- <error descr="Call requires API level 18 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(); // ERROR
- }
- }
-
- fun test4(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- System.out.println("Something");
- RectEvaluator(rect); // OK
- } else {
- <error descr="Call requires API level 21 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(rect); // ERROR
- }
- }
-
- fun test5(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
- <error descr="Call requires API level 21 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(rect); // ERROR
- } else {
- <error descr="Call requires API level 21 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(rect); // ERROR
- }
- }
-
- fun test(priority: Boolean, layout: ViewGroup) {
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
-
- if (SDK_INT >= ICE_CREAM_SANDWICH) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- } else {
- GridLayout(null).getOrientation(); // Not flagged
- }
-
- if (Build.VERSION.SDK_INT >= 14) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
-
- if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
-
- // Nested conditionals
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- if (priority) {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- }
-
- // Nested conditionals 2
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- if (priority) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- GridLayout(null).getOrientation(); // Not flagged
- }
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
- }
-
- fun test2(priority: Boolean) {
- if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
-
- if (android.os.Build.VERSION.SDK_INT >= 16) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
-
- if (android.os.Build.VERSION.SDK_INT >= 13) {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
-
- if (SDK_INT >= JELLY_BEAN) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- } else {
- GridLayout(null).getOrientation(); // Not flagged
- }
-
- if (Build.VERSION.SDK_INT >= 16) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
-
- if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
- GridLayout(null).getOrientation(); // Not flagged
- } else {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
- }
- }
-
- fun test(textView: TextView) {
- if (textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>()) {
- //ERROR
- }
- if (textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>) {
- //ERROR
- }
-
- if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled()) {
- //NO ERROR
- }
-
- if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled) {
- //NO ERROR
- }
-
- if (SDK_INT >= JELLY_BEAN && (textView.text != "" || textView.isSuggestionsEnabled)) {
- //NO ERROR
- }
-
- if (SDK_INT < JELLY_BEAN && (textView.text != "" || textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>)) {
- //ERROR
- }
-
- if (SDK_INT < JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>()) {
- //ERROR
- }
-
- if (SDK_INT < JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>) {
- //ERROR
- }
-
- if (SDK_INT < JELLY_BEAN || textView.isSuggestionsEnabled) {
- //NO ERROR
- }
-
- if (SDK_INT > JELLY_BEAN || textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>) {
- //ERROR
- }
-
-
- // getActionBar() API 11
- if (SDK_INT <= 10 || getActionBar() == null) {
- //NO ERROR
- }
-
- if (SDK_INT < 10 || <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">getActionBar</error>() == null) {
- //ERROR
- }
-
- if (SDK_INT < 11 || getActionBar() == null) {
- //NO ERROR
- }
-
- if (SDK_INT != 11 || getActionBar() == null) {
- //NO ERROR
- }
-
- if (SDK_INT != 12 || <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">getActionBar</error>() == null) {
- //ERROR
- }
-
- if (SDK_INT <= 11 || getActionBar() == null) {
- //NO ERROR
- }
-
- if (SDK_INT < 12 || getActionBar() == null) {
- //NO ERROR
- }
-
- if (SDK_INT <= 12 || getActionBar() == null) {
- //NO ERROR
- }
-
- if (SDK_INT < 9 || <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">getActionBar</error>() == null) {
- //ERROR
- }
-
- if (SDK_INT <= 9 || <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">getActionBar</error>() == null) {
- //ERROR
- }
- }
-
- fun testReturn() {
- if (SDK_INT < 11) {
- return
- }
-
- // No Error
- val actionBar = getActionBar()
- }
-
- fun testThrow() {
- if (SDK_INT < 11) {
- throw IllegalStateException()
- }
-
- // No Error
- val actionBar = getActionBar()
- }
-
- fun testError() {
- if (SDK_INT < 11) {
- error("Api")
- }
-
- // No Error
- val actionBar = getActionBar()
- }
-
- fun testWithoutAnnotation(textView: TextView) {
- if (textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>()) {
-
- }
-
- if (textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>) {
-
- }
- }
-
- @TargetApi(JELLY_BEAN)
- fun testWithTargetApiAnnotation(textView: TextView) {
- if (textView.isSuggestionsEnabled()) {
- //NO ERROR, annotation
- }
-
- if (textView.isSuggestionsEnabled) {
- //NO ERROR, annotation
- }
- }
-
- @SuppressLint("NewApi")
- fun testWithSuppressLintAnnotation(textView: TextView) {
- if (textView.isSuggestionsEnabled()) {
- //NO ERROR, annotation
- }
-
- if (textView.isSuggestionsEnabled) {
- //NO ERROR, annotation
- }
- }
-
- fun testCatch() {
- try {
-
- } catch (e: <error descr="Class requires API level 21 (current min is 1): android.system.ErrnoException">ErrnoException</error>) {
-
- }
- }
-
- fun testOverload() {
- // this overloaded addOval available only on API Level 21
- Path().<error descr="Call requires API level 21 (current min is 1): android.graphics.Path#addOval">addOval</error>(0f, 0f, 0f, 0f, Path.Direction.CW)
- }
-
- // KT-14737 False error with short-circuit evaluation
- fun testShortCircuitEvaluation() {
- <error descr="Call requires API level 21 (current min is 1): android.content.Context#getDrawable">getDrawable</error>(0) // error here as expected
- if(Build.VERSION.SDK_INT >= 23
- && null == getDrawable(0)) // error here should not occur
- {
- getDrawable(0) // no error here as expected
- }
- }
-
- // KT-1482 Kotlin Lint: "Calling new methods on older versions" does not report call on receiver in extension function
- private fun Bundle.caseE1a() { <error descr="Call requires API level 18 (current min is 1): android.os.Bundle#getBinder">getBinder</error>("") }
-
- private fun Bundle.caseE1c() { this.<error descr="Call requires API level 18 (current min is 1): android.os.Bundle#getBinder">getBinder</error>("") }
-
- private fun caseE1b(bundle: Bundle) { bundle.<error descr="Call requires API level 18 (current min is 1): android.os.Bundle#getBinder">getBinder</error>("") }
-
- // KT-12023 Kotlin Lint: Cast doesn't trigger minSdk error
- fun testCast(layout: ViewGroup) {
- if (layout is LinearLayout) {} // OK API 1
- layout as? LinearLayout // OK API 1
- layout as LinearLayout // OK API 1
-
- if (layout !is <error descr="Class requires API level 14 (current min is 1): android.widget.GridLayout">GridLayout</error>) {}
- layout as? <error descr="Class requires API level 14 (current min is 1): android.widget.GridLayout">GridLayout</error>
- layout as <error descr="Class requires API level 14 (current min is 1): android.widget.GridLayout">GridLayout</error>
-
- val grid = layout as? <error descr="Class requires API level 14 (current min is 1): android.widget.GridLayout">GridLayout</error>
- val linear = layout as LinearLayout // OK API 1
- }
-
- abstract class ErrorVectorDravable : <error descr="Class requires API level 21 (current min is 1): android.graphics.drawable.VectorDrawable">VectorDrawable</error>(), Parcelable
-
- @TargetApi(21)
- class MyVectorDravable : VectorDrawable()
-
- fun testTypes() {
- <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(this)
- val c = <error descr="Class requires API level 21 (current min is 1): android.graphics.drawable.VectorDrawable">VectorDrawable::class</error>.java
- }
-
- fun testCallWithApiAnnotation(textView: TextView) {
- <error descr="Call requires API level 21 (current min is 1): ApiCallTest.MyVectorDravable#MyVectorDravable">MyVectorDravable</error>()
- <error descr="Call requires API level 16 (current min is 1): ApiCallTest#testWithTargetApiAnnotation">testWithTargetApiAnnotation</error>(textView)
- }
-
- companion object : Activity() {
- fun test() {
- <error descr="Call requires API level 21 (current min is 1): android.content.Context#getDrawable">getDrawable</error>(0)
- }
- }
-
- // Return type
- internal // API 14
- val gridLayout: GridLayout?
- get() = null
-
- private val report: ApplicationErrorReport?
- get() = null
-}
-
-object O: Activity() {
- fun test() {
- <error descr="Call requires API level 21 (current min is 1): android.content.Context#getDrawable">getDrawable</error>(0)
- }
-}
-
-fun testJava8() {
- // Error, Api 24, Java8
- mutableListOf(1, 2, 3).<error descr="Call requires API level 24 (current min is 1): java.util.Collection#removeIf">removeIf</error> {
- true
- }
-
- // Ok, Kotlin
- mutableListOf(1, 2, 3).removeAll {
- true
- }
-
- // Error, Api 24, Java8
- mapOf(1 to 2).<error descr="Call requires API level 24 (current min is 1): java.util.Map#forEach">forEach</error> { key, value -> key + value }
-
- // Ok, Kotlin
- mapOf(1 to 2).forEach { (key, value) -> key + value }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/callSuper.kt b/idea/testData/android/lint/callSuper.kt
deleted file mode 100644
index df30cfe..0000000
--- a/idea/testData/android/lint/callSuper.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMissingSuperCallInspection
-
-package android.support.annotation
-
-@Retention(AnnotationRetention.BINARY)
-@Target(AnnotationTarget.FUNCTION)
-annotation class CallSuper
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class CallSuperTest {
- private class Child : Parent() {
- override fun <error descr="Overriding method should call `super.test1`">test1</error>() {
- // ERROR
- }
-
- override fun <error descr="Overriding method should call `super.test2`">test2</error>() {
- // ERROR
- }
-
- override fun <error descr="Overriding method should call `super.test3`">test3</error>() {
- // ERROR
- }
-
- override fun <error descr="Overriding method should call `super.test4`">test4</error>(arg: Int) {
- // ERROR
- }
-
- override fun test4(arg: String) {
- // OK
- }
-
-
- override fun <error descr="Overriding method should call `super.test5`">test5</error>(arg1: Int, arg2: Boolean, arg3: Map<List<String>, *>, // ERROR
- arg4: Array<IntArray>, vararg arg5: Int) {
- }
-
- override fun <error descr="Overriding method should call `super.test5`">test5</error>() {
- // ERROR
- super.test6() // (wrong super)
- }
-
- override fun test6() {
- // OK
- val x = 5
- super.test6()
- System.out.println(x)
- }
- }
-
- private open class Parent : ParentParent() {
- @CallSuper
- protected open fun test1() {
- }
-
- override fun test3() {
- super.test3()
- }
-
- @CallSuper
- protected open fun test4(arg: Int) {
- }
-
- protected open fun test4(arg: String) {
- }
-
- @CallSuper
- protected open fun test5() {
- }
-
- @CallSuper
- protected open fun test5(arg1: Int, arg2: Boolean, arg3: Map<List<String>, *>,
- arg4: Array<IntArray>, vararg arg5: Int) {
- }
- }
-
- private open class ParentParent : ParentParentParent() {
- @CallSuper
- protected open fun test2() {
- }
-
- @CallSuper
- protected open fun test3() {
- }
-
- @CallSuper
- protected open fun test6() {
- }
-
- @CallSuper
- protected fun test7() {
- }
-
-
- }
-
- private open class ParentParentParent
-}
diff --git a/idea/testData/android/lint/closeCursor.kt b/idea/testData/android/lint/closeCursor.kt
deleted file mode 100644
index ca26201..0000000
--- a/idea/testData/android/lint/closeCursor.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRecycleInspection
-
-@file:Suppress("UNUSED_VARIABLE")
-
-import android.app.Activity
-import android.os.Bundle
-
-class MainActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val cursor = contentResolver.<warning descr="This `Cursor` should be freed up after use with `#close()`">query</warning>(null, null, null, null, null)
-
- // WARNING
- contentResolver.<warning descr="This `Cursor` should be freed up after use with `#close()`">query</warning>(null, null, null, null, null)
-
- // OK, closed in chained call
- contentResolver.query(null, null, null, null, null).close()
-
- // KT-14677: Kotlin Lint: "Missing recycle() calls" report cursor with `use()` call
- val cursorUsed = contentResolver.query(null, null, null, null, null)
- cursorUsed.use { }
-
- // OK, used in chained call
- contentResolver.query(null, null, null, null, null).use {
-
- }
-
- // KT-13372: Android Lint for Kotlin: false positive "Cursor should be freed" inside 'if' expression
- if (true) {
- val c = contentResolver.query(null, null, null, null, null)
- c.close()
- }
- }
-}
diff --git a/idea/testData/android/lint/commitFragment.kt b/idea/testData/android/lint/commitFragment.kt
deleted file mode 100644
index fc6f333..0000000
--- a/idea/testData/android/lint/commitFragment.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitTransactionInspection
-
-@file:Suppress("UNUSED_VARIABLE")
-
-import android.app.Activity
-import android.app.FragmentTransaction
-import android.app.FragmentManager
-import android.os.Bundle
-
-class MainActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- //OK
- val transaction = fragmentManager.beginTransaction()
- val transaction2: FragmentTransaction
- transaction2 = fragmentManager.beginTransaction()
- transaction.commit()
- transaction2.commit()
-
- //WARNING
- val transaction3 = fragmentManager.<warning descr="This transaction should be completed with a `commit()` call">beginTransaction</warning>()
-
- //OK
- fragmentManager.beginTransaction().commit()
- fragmentManager.beginTransaction().add(null, "A").commit()
-
- //OK KT-14470
- Runnable {
- val a = fragmentManager.beginTransaction()
- a.commit()
- }
- }
-
- // KT-14780: Kotlin Lint: "Missing commit() calls" false positive when the result of `commit()` is assigned or used as receiver
- fun testResultOfCommit(fm: FragmentManager) {
- val r1 = fm.beginTransaction().hide(fm.findFragmentByTag("aTag")).commit()
- val r2 = fm.beginTransaction().hide(fm.findFragmentByTag("aTag")).commit().toString()
- }
-}
diff --git a/idea/testData/android/lint/javaPerformance.kt b/idea/testData/android/lint/javaPerformance.kt
deleted file mode 100644
index 02491db..0000000
--- a/idea/testData/android/lint/javaPerformance.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintDrawAllocationInspection
-// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseSparseArraysInspection
-// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseValueOfInspection
-
-import android.annotation.SuppressLint
-import java.util.HashMap
-import android.content.Context
-import android.graphics.*
-import android.util.AttributeSet
-import android.util.SparseArray
-import android.widget.Button
-
-@SuppressWarnings("unused")
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class JavaPerformanceTest(context: Context, attrs: AttributeSet, defStyle: Int) : Button(context, attrs, defStyle) {
-
- private var cachedRect: Rect? = null
- private var shader: LinearGradient? = null
- private var lastHeight: Int = 0
- private var lastWidth: Int = 0
-
- override fun onDraw(canvas: android.graphics.Canvas) {
- super.onDraw(canvas)
-
- // Various allocations:
- java.lang.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">String("foo")</warning>
- val s = java.lang.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">String("bar")</warning>
-
- // This one should not be reported:
- @SuppressLint("DrawAllocation")
- val i = 5
-
- // Cached object initialized lazily: should not complain about these
- if (cachedRect == null) {
- cachedRect = Rect(0, 0, 100, 100)
- }
- if (cachedRect == null || cachedRect!!.width() != 50) {
- cachedRect = Rect(0, 0, 50, 100)
- }
-
- val b = java.lang.Boolean.valueOf(true)!! // auto-boxing
- dummy(1, 2)
-
- // Non-allocations
- super.animate()
- dummy2(1, 2)
-
- // This will involve allocations, but we don't track
- // inter-procedural stuff here
- someOtherMethod()
- }
-
- internal fun dummy(foo: Int?, bar: Int) {
- dummy2(foo!!, bar)
- }
-
- internal fun dummy2(foo: Int, bar: Int) {
- }
-
- internal fun someOtherMethod() {
- // Allocations are okay here
- java.lang.String("foo")
- val s = java.lang.String("bar")
- val b = java.lang.Boolean.valueOf(true)!! // auto-boxing
-
-
- // Sparse array candidates
- val myMap = <warning descr="Use `new SparseArray<String>(...)` instead for better performance">HashMap<Int, String>()</warning>
- // Should use SparseBooleanArray
- val myBoolMap = <warning descr="Use `new SparseBooleanArray(...)` instead for better performance">HashMap<Int, Boolean>()</warning>
- // Should use SparseIntArray
- val myIntMap = java.util.<warning descr="Use new `SparseIntArray(...)` instead for better performance">HashMap<Int, Int>()</warning>
-
- // This one should not be reported:
- @SuppressLint("UseSparseArrays")
- val myOtherMap = <warning descr="Use `new SparseArray<Object>(...)` instead for better performance">HashMap<Int, Any>()</warning>
- }
-
- protected fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int,
- x: Boolean) {
- // wrong signature
- java.lang.String("not an error")
- }
-
- protected fun onMeasure(widthMeasureSpec: Int) {
- // wrong signature
- java.lang.String("not an error")
- }
-
- protected fun onLayout(changed: Boolean, left: Int, top: Int, right: Int,
- bottom: Int, wrong: Int) {
- // wrong signature
- java.lang.String("not an error")
- }
-
- protected fun onLayout(changed: Boolean, left: Int, top: Int, right: Int) {
- // wrong signature
- java.lang.String("not an error")
- }
-
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int,
- bottom: Int) {
- java.lang.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">String("flag me")</warning>
- }
-
- @SuppressWarnings("null") // not real code
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- java.lang.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">String("flag me")</warning>
-
- // Forbidden factory methods:
- Bitmap.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">createBitmap(100, 100, null)</warning>
- android.graphics.Bitmap.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">createScaledBitmap(null, 100, 100, false)</warning>
- BitmapFactory.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">decodeFile(null)</warning>
- val canvas: Canvas? = null
- canvas!!.<warning descr="Avoid object allocations during draw operations: Use `Canvas.getClipBounds(Rect)` instead of `Canvas.getClipBounds()` which allocates a temporary `Rect`">getClipBounds()</warning> // allocates on your behalf
- canvas.<warning descr="Avoid object allocations during draw operations: Use `Canvas.getClipBounds(Rect)` instead of `Canvas.getClipBounds()` which allocates a temporary `Rect`">clipBounds</warning> // allocates on your behalf
- canvas.getClipBounds(null) // NOT an error
-
- val layoutWidth = width
- val layoutHeight = height
- if (mAllowCrop && (mOverlay == null || mOverlay!!.width != layoutWidth ||
- mOverlay!!.height != layoutHeight)) {
- mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888)
- mOverlayCanvas = Canvas(mOverlay!!)
- }
-
- if (widthMeasureSpec == 42) {
- throw IllegalStateException("Test") // NOT an allocation
- }
-
- // More lazy init tests
- var initialized = false
- if (!initialized) {
- java.lang.String("foo")
- initialized = true
- }
-
- // NOT lazy initialization
- if (!initialized || mOverlay == null) {
- java.lang.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">String("foo")</warning>
- }
- }
-
- internal fun factories() {
- val i1 = 42
- val l1 = 42L
- val b1 = true
- val c1 = 'c'
- val f1 = 1.0f
- val d1 = 1.0
-
- // The following should not generate errors:
- val i3 = Integer.valueOf(42)
- }
-
- private val mAllowCrop: Boolean = false
- private var mOverlayCanvas: Canvas? = null
- private var mOverlay: Bitmap? = null
-
- override fun layout(l: Int, t: Int, r: Int, b: Int) {
- // Using "this." to reference fields
- if (this.shader == null)
- this.shader = LinearGradient(0f, 0f, width.toFloat(), 0f, intArrayOf(0), null,
- Shader.TileMode.REPEAT)
-
- val width = width
- val height = height
-
- if (shader == null || lastWidth != width || lastHeight != height) {
- lastWidth = width
- lastHeight = height
-
- shader = LinearGradient(0f, 0f, width.toFloat(), 0f, intArrayOf(0), null, Shader.TileMode.REPEAT)
- }
- }
-
- fun inefficientSparseArray() {
- <warning descr="Use `new SparseIntArray(...)` instead for better performance">SparseArray<Int>()</warning> // Use SparseIntArray instead
- SparseArray<Long>() // Use SparseLongArray instead
- <warning descr="Use `new SparseBooleanArray(...)` instead for better performance">SparseArray<Boolean>()</warning> // Use SparseBooleanArray instead
- SparseArray<Any>() // OK
- }
-
- fun longSparseArray() {
- // but only minSdkVersion >= 17 or if has v4 support lib
- val myStringMap = HashMap<Long, String>()
- }
-
- fun byteSparseArray() {
- // bytes easily apply to ints
- val myByteMap = <warning descr="Use `new SparseArray<String>(...)` instead for better performance">HashMap<Byte, String>()</warning>
- }
-}
diff --git a/idea/testData/android/lint/javaScriptInterface.kt b/idea/testData/android/lint/javaScriptInterface.kt
deleted file mode 100644
index 8d42559..0000000
--- a/idea/testData/android/lint/javaScriptInterface.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintJavascriptInterfaceInspection
-
-import android.annotation.SuppressLint
-import android.webkit.JavascriptInterface
-import android.webkit.WebView
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "UNUSED_VALUE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class JavaScriptTestK {
- fun test(webview: WebView) {
- webview.addJavascriptInterface(AnnotatedObject(), "myobj")
-
- webview.addJavascriptInterface(InheritsFromAnnotated(), "myobj")
- webview.addJavascriptInterface(NonAnnotatedObject(), "myobj")
-
- webview.addJavascriptInterface(null, "nothing")
- webview.addJavascriptInterface(object : Any() { @JavascriptInterface fun method() {} }, "nothing")
- webview.addJavascriptInterface(JavascriptFace(), "nothing")
-
- var o: Any = NonAnnotatedObject()
- webview.addJavascriptInterface(o, "myobj")
- o = InheritsFromAnnotated()
- webview.addJavascriptInterface(o, "myobj")
- }
-
- fun test(webview: WebView, object1: AnnotatedObject, object2: NonAnnotatedObject) {
- webview.addJavascriptInterface(object1, "myobj")
- webview.addJavascriptInterface(object2, "myobj")
- }
-
- @SuppressLint("JavascriptInterface")
- fun testSuppressed(webview: WebView) {
- webview.addJavascriptInterface(NonAnnotatedObject(), "myobj")
- }
-
- fun testLaterReassignment(webview: WebView) {
- var o: Any = NonAnnotatedObject()
- val t = o
- webview.addJavascriptInterface(t, "myobj")
- o = AnnotatedObject()
- }
-
- class NonAnnotatedObject() {
- fun test1() {}
- fun test2() {}
- }
-
- open class AnnotatedObject {
- @JavascriptInterface
- open fun test1() {}
-
- open fun test2() {}
-
- @JavascriptInterface
- fun test3() {}
- }
-
- class InheritsFromAnnotated : AnnotatedObject() {
- override fun test1() {}
- override fun test2() {}
- }
-
-}
-
-class JavascriptFace {
- fun method() {}
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/layoutInflation.kt b/idea/testData/android/lint/layoutInflation.kt
deleted file mode 100644
index 65bd1e7..0000000
--- a/idea/testData/android/lint/layoutInflation.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInflateParamsInspection
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.BaseAdapter
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-abstract class LayoutInflationTest : BaseAdapter() {
- lateinit var mInflater: LayoutInflater
-
- override fun getView(position: Int, convertView: View, parent: ViewGroup): View {
- var view = <warning descr="[VARIABLE_WITH_REDUNDANT_INITIALIZER] Variable 'view' initializer is redundant">convertView</warning>
- <warning descr="[UNUSED_VALUE] The value 'mInflater.inflate(R.layout.your_layout, null)' assigned to 'var view: View defined in LayoutInflationTest.getView' is never used">view =</warning> mInflater.inflate(R.layout.your_layout, null)
- <warning descr="[UNUSED_VALUE] The value 'mInflater.inflate(R.layout.your_layout, null, true)' assigned to 'var view: View defined in LayoutInflationTest.getView' is never used">view =</warning> mInflater.inflate(R.layout.your_layout, null, true)
- view = mInflater.inflate(R.layout.your_layout, parent)
- view = WeirdInflater.inflate(view, null)
-
- return view
- }
-
- object WeirdInflater {
- fun inflate(view: View, parent: View?) = view
- }
-
- object R {
- object layout {
- val your_layout = 1
- }
- }
-}
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-abstract class LayoutInflationTest2 : BaseAdapter() {
- lateinit var mInflater: LayoutInflater
-
- override fun getView(position: Int, convertView: View, parent: ViewGroup): View? {
- return if (true) {
- mInflater.inflate(R.layout.your_layout, parent)
- } else {
- null
- }
- }
-
- object R {
- object layout {
- val your_layout = 1
- }
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/log.kt b/idea/testData/android/lint/log.kt
deleted file mode 100644
index ebf4d97..0000000
--- a/idea/testData/android/lint/log.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogConditionalInspection
-// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogTagMismatchInspection
-// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLongLogTagInspection
-
-import android.annotation.SuppressLint
-import android.util.Log
-import android.util.Log.DEBUG
-
-@SuppressWarnings("UnusedDeclaration")
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class LogTest {
-
- fun checkConditional(m: String) {
- Log.d(TAG1, "message") // ok: unconditional, but not performing computation
- Log.d(TAG1, m) // ok: unconditional, but not performing computation
- Log.d(TAG1, "a" + "b") // ok: unconditional, but not performing non-constant computation
- Log.d(TAG1, Constants.MY_MESSAGE) // ok: unconditional, but constant string
- Log.<warning descr="The log call Log.i(...) should be conditional: surround with `if (Log.isLoggable(...))` or `if (BuildConfig.DEBUG) { ... }`">i(TAG1, "message" + m)</warning> // error: unconditional w/ computation
- Log.<warning descr="The log call Log.i(...) should be conditional: surround with `if (Log.isLoggable(...))` or `if (BuildConfig.DEBUG) { ... }`">i(TAG1, toString())</warning> // error: unconditional w/ computation
- Log.e(TAG1, toString()) // ok: only flagging debug/info messages
- Log.w(TAG1, toString()) // ok: only flagging debug/info messages
- Log.wtf(TAG1, toString()) // ok: only flagging debug/info messages
- if (Log.isLoggable(TAG1, 0)) {
- Log.d(TAG1, toString()) // ok: conditional
- }
- }
-
- fun checkWrongTag(tag: String) {
- if (Log.isLoggable(<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `getTAG1` versus `getTAG2` (Conflicting tag)">TAG1</error>, Log.DEBUG)) {
- Log.d(<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `getTAG1` versus `getTAG2`">TAG2</error>, "message") // warn: mismatched tags!
- }
- if (Log.isLoggable("<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `\"my_tag\"` versus `\"other_tag\"` (Conflicting tag)">my_tag</error>", Log.DEBUG)) {
- Log.d("<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `\"my_tag\"` versus `\"other_tag\"`">other_tag</error>", "message") // warn: mismatched tags!
- }
- if (Log.isLoggable("my_tag", Log.DEBUG)) {
- Log.d("my_tag", "message") // ok: strings equal
- }
- if (Log.isLoggable(tag, Log.DEBUG)) {
- Log.d(tag, "message") // ok: same variable
- }
- }
-
- fun checkLongTag(shouldLog: Boolean) {
- if (shouldLog) {
- // String literal tags
- Log.d("short_tag", "message") // ok: short
- Log.<error descr="The logging tag can be at most 23 characters, was 43 (really_really_really_really_really_long_tag)">d("really_really_really_really_really_long_tag", "message")</error> // error: too long
-
- // Resolved field tags
- Log.d(TAG1, "message") // ok: short
- Log.d(TAG22, "message") // ok: short
- Log.d(TAG23, "message") // ok: threshold
- Log.<error descr="The logging tag can be at most 23 characters, was 24 (123456789012345678901234)">d(TAG24, "message")</error> // error: too long
- Log.<error descr="The logging tag can be at most 23 characters, was 39 (MyReallyReallyReallyReallyReallyLongTag)">d(LONG_TAG, "message")</error> // error: way too long
-
- // Locally defined variable tags
- val LOCAL_TAG = "MyReallyReallyReallyReallyReallyLongTag"
- Log.<error descr="The logging tag can be at most 23 characters, was 39 (MyReallyReallyReallyReallyReallyLongTag)">d(LOCAL_TAG, "message")</error> // error: too long
-
- // Concatenated tags
- Log.<error descr="The logging tag can be at most 23 characters, was 28 (1234567890123456789012MyTag1)">d(TAG22 + TAG1, "message")</error> // error: too long
- Log.<error descr="The logging tag can be at most 23 characters, was 27 (1234567890123456789012MyTag)">d(TAG22 + "MyTag", "message")</error> // error: too long
- }
- }
-
- fun checkWrongLevel(tag: String) {
- if (Log.isLoggable(TAG1, Log.DEBUG)) {
- Log.d(TAG1, "message") // ok: right level
- }
- if (Log.isLoggable(TAG1, Log.INFO)) {
- Log.i(TAG1, "message") // ok: right level
- }
- if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v` (Conflicting tag)">Log.DEBUG</error>)) {
- Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v`">v</error>(TAG1, "message") // warn: wrong level
- }
- if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v` (Conflicting tag)">DEBUG</error>)) {
- // static import of level
- Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v`">v</error>(TAG1, "message") // warn: wrong level
- }
- if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `VERBOSE`, the corresponding log call should be `Log.v`, not `Log.d` (Conflicting tag)">Log.VERBOSE</error>)) {
- Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `VERBOSE`, the corresponding log call should be `Log.v`, not `Log.d`">d</error>(TAG1, "message") // warn? verbose is a lower logging level, which includes debug
- }
- if (Log.isLoggable(TAG1, Constants.MY_LEVEL)) {
- Log.d(TAG1, "message") // ok: unknown level alias
- }
- }
-
- @SuppressLint("all")
- fun suppressed1() {
- Log.d(TAG1, "message") // ok: suppressed
- }
-
- @SuppressLint("LogConditional")
- fun suppressed2() {
- Log.d(TAG1, "message") // ok: suppressed
- }
-
- private object Constants {
- val MY_MESSAGE = "My Message"
- val MY_LEVEL = 5
- }
-
- companion object {
- private val TAG1 = "MyTag1"
- private val TAG2 = "MyTag2"
- private val TAG22 = "1234567890123456789012"
- private val TAG23 = "12345678901234567890123"
- private val TAG24 = "123456789012345678901234"
- private val LONG_TAG = "MyReallyReallyReallyReallyReallyLongTag"
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/noInternationalSms.kt b/idea/testData/android/lint/noInternationalSms.kt
deleted file mode 100644
index 31fecc2..0000000
--- a/idea/testData/android/lint/noInternationalSms.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnlocalizedSmsInspection
-
-import android.content.Context
-import android.telephony.SmsManager
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class NonInternationalizedSmsDetectorTest {
- private fun sendLocalizedMessage(context: Context) {
- // Don't warn here
- val sms = SmsManager.getDefault()
- sms.sendTextMessage("+1234567890", null, null, null, null)
- }
-
- private fun sendAlternativeCountryPrefix(context: Context) {
- // Do warn here
- val sms = SmsManager.getDefault()
- sms.sendMultipartTextMessage("<warning descr="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.">001234567890</warning>", null, null, null, null)
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/overrideConcrete.kt b/idea/testData/android/lint/overrideConcrete.kt
deleted file mode 100644
index 55a5bb3..0000000
--- a/idea/testData/android/lint/overrideConcrete.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideAbstractInspection
-
-import android.annotation.SuppressLint
-import android.annotation.TargetApi
-import android.os.Build
-import android.service.notification.NotificationListenerService
-import android.service.notification.StatusBarNotification
-
-@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class OverrideConcreteTest2 {
- // OK: This one specifies both methods
- private open class MyNotificationListenerService1 : NotificationListenerService() {
- override fun onNotificationPosted(statusBarNotification: StatusBarNotification) {
- }
-
- override fun onNotificationRemoved(statusBarNotification: StatusBarNotification) {
- }
- }
-
- // Error: Misses onNotificationPosted
- private class <error descr="Must override `android.service.notification.NotificationListenerService.onNotificationPosted(android.service.notification.StatusBarNotification)`: Method was abstract until 21, and your `minSdkVersion` is 18">MyNotificationListenerService2</error> : NotificationListenerService() {
- override fun onNotificationRemoved(statusBarNotification: StatusBarNotification) {
- }
- }
-
- // Error: Misses onNotificationRemoved
- private open class <error descr="Must override `android.service.notification.NotificationListenerService.onNotificationRemoved(android.service.notification.StatusBarNotification)`: Method was abstract until 21, and your `minSdkVersion` is 18">MyNotificationListenerService3</error> : NotificationListenerService() {
- override fun onNotificationPosted(statusBarNotification: StatusBarNotification) {
- }
- }
-
- // Error: Missing both; wrong signatures (first has wrong arg count, second has wrong type)
- private class <error descr="Must override `android.service.notification.NotificationListenerService.onNotificationPosted(android.service.notification.StatusBarNotification)`: Method was abstract until 21, and your `minSdkVersion` is 18">MyNotificationListenerService4</error> : NotificationListenerService() {
- fun onNotificationPosted(statusBarNotification: StatusBarNotification, flags: Int) {
- }
-
- fun onNotificationRemoved(statusBarNotification: Int) {
- }
- }
-
- // OK: Inherits from a class which define both
- private class MyNotificationListenerService5 : MyNotificationListenerService1()
-
- // OK: Inherits from a class which defines only one, but the other one is defined here
- private class MyNotificationListenerService6 : MyNotificationListenerService3() {
- override fun onNotificationRemoved(statusBarNotification: StatusBarNotification) {
- }
- }
-
- // Error: Inheriting from a class which only defines one
- private class <error descr="Must override `android.service.notification.NotificationListenerService.onNotificationRemoved(android.service.notification.StatusBarNotification)`: Method was abstract until 21, and your `minSdkVersion` is 18">MyNotificationListenerService7</error> : MyNotificationListenerService3()
-
- // OK: Has target api setting a local version that is high enough
- @TargetApi(21)
- private class MyNotificationListenerService8 : NotificationListenerService()
-
- // OK: Suppressed
- @SuppressLint("OverrideAbstract")
- private class MyNotificationListenerService9 : MyNotificationListenerService1()
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/parcel.kt b/idea/testData/android/lint/parcel.kt
deleted file mode 100644
index 600130a..0000000
--- a/idea/testData/android/lint/parcel.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection
-
-@file:Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-import android.os.Parcel
-import android.os.Parcelable
-
-class <error descr="This class implements `Parcelable` but does not provide a `CREATOR` field">MyParcelable1</error> : Parcelable {
- override fun describeContents() = 0
- override fun writeToParcel(arg0: Parcel, arg1: Int) {}
-}
-
-internal class MyParcelable2 : Parcelable {
- override fun describeContents() = 0
-
- override fun writeToParcel(arg0: Parcel, arg1: Int) {}
-
- companion object {
- @JvmField
- val CREATOR: Parcelable.Creator<String> = object : Parcelable.Creator<String> {
- override fun newArray(size: Int) = null!!
- override fun createFromParcel(source: Parcel?) = null!!
- }
- }
-}
-
-internal class MyParcelable3 : Parcelable {
- override fun describeContents() = 0
- override fun writeToParcel(arg0: Parcel, arg1: Int) {}
-
- companion object {
- @JvmField
- val CREATOR = 0 // Wrong type
- }
-}
-
-class RecyclerViewScrollPosition(val position: Int, val topOffset: Int): Parcelable {
- override fun describeContents(): Int = 0
- override fun writeToParcel(dest: Parcel, flags: Int) {
- dest.writeInt(position)
- dest.writeInt(topOffset)
- }
-
- companion object {
- @JvmField
- val CREATOR = object : Parcelable.Creator<RecyclerViewScrollPosition> {
- override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition {
- val position = parcel.readInt()
- val topOffset = parcel.readInt()
- return RecyclerViewScrollPosition(position, topOffset)
- }
-
- override fun newArray(size: Int): Array<RecyclerViewScrollPosition?> = arrayOfNulls(size)
- }
-
- }
-}
-
-class RecyclerViewScrollPositionWithoutJvmF(val position: Int, val topOffset: Int): Parcelable {
- override fun describeContents(): Int = 0
- override fun writeToParcel(dest: Parcel, flags: Int) {
- dest.writeInt(position)
- dest.writeInt(topOffset)
- }
-
- companion object {
- val CREATOR = object : Parcelable.Creator<RecyclerViewScrollPosition> {
- override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition {
- val position = parcel.readInt()
- val topOffset = parcel.readInt()
- return RecyclerViewScrollPosition(position, topOffset)
- }
-
- override fun newArray(size: Int): Array<RecyclerViewScrollPosition?> = arrayOfNulls(size)
- }
-
- }
-}
-
-class RecyclerViewScrollPosition2(val position: Int, val topOffset: Int): Parcelable {
- override fun describeContents(): Int = 0
- override fun writeToParcel(dest: Parcel, flags: Int) {
- dest.writeInt(position)
- dest.writeInt(topOffset)
- }
-
- companion object CREATOR: Parcelable.Creator<RecyclerViewScrollPosition> {
- override fun createFromParcel(parcel: Parcel): RecyclerViewScrollPosition {
- val position = parcel.readInt()
- val topOffset = parcel.readInt()
- return RecyclerViewScrollPosition(position, topOffset)
- }
-
- override fun newArray(size: Int): Array<RecyclerViewScrollPosition?> = arrayOfNulls(size)
- }
-}
-
-internal abstract class MyParcelable4 : Parcelable {
- override fun describeContents() = 0
- override fun writeToParcel(arg0: Parcel, arg1: Int) {}
-}
diff --git a/idea/testData/android/lint/sdCardTest.kt b/idea/testData/android/lint/sdCardTest.kt
deleted file mode 100644
index 41c48d4..0000000
--- a/idea/testData/android/lint/sdCardTest.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
-
-import java.io.File
-import android.content.Intent
-import android.net.Uri
-
-/**
- * Ignore comments - create("/sdcard/foo")
- */
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class SdCardTest {
- internal var deviceDir = File("<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard/vr</warning>")
-
- init {
- if (PROFILE_STARTUP) {
- android.os.Debug.startMethodTracing("<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead"><warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard/launcher</warning></warning>")
- }
-
- if (File("<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead"><warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard</warning></warning>").exists()) {
- }
- val FilePath = "<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead"><warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard/</warning></warning>" + File("test")
- System.setProperty("foo.bar", "file://sdcard")
-
-
- val intent = Intent(Intent.ACTION_PICK)
- intent.setDataAndType(Uri.parse("<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead"><warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">file://sdcard/foo.json</warning></warning>"), "application/bar-json")
- intent.putExtra("path-filter", "<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead"><warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard(/.+)*</warning></warning>")
- intent.putExtra("start-dir", "<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead"><warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard</warning></warning>")
- val mypath = "<warning descr="Do not hardcode \"`/data/`\"; use `Context.getFilesDir().getPath()` instead"><warning descr="Do not hardcode \"`/data/`\"; use `Context.getFilesDir().getPath()` instead">/data/data/foo</warning></warning>"
- val base = "<warning descr="Do not hardcode \"`/data/`\"; use `Context.getFilesDir().getPath()` instead"><warning descr="Do not hardcode \"`/data/`\"; use `Context.getFilesDir().getPath()` instead">/data/data/foo.bar/test-profiling</warning></warning>"
- val s = "<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead"><warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">file://sdcard/foo</warning></warning>"
-}
-
-companion object {
- private val PROFILE_STARTUP = true
- private val SDCARD_TEST_HTML = "<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard/test.html</warning>"
- val SDCARD_ROOT = "<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard</warning>"
- val PACKAGES_PATH = "<warning descr="Do not hardcode \"/sdcard/\"; use `Environment.getExternalStorageDirectory().getPath()` instead">/sdcard/o/packages/</warning>"
-}
-}
diff --git a/idea/testData/android/lint/setJavaScriptEnabled.kt b/idea/testData/android/lint/setJavaScriptEnabled.kt
deleted file mode 100644
index 2c11557..0000000
--- a/idea/testData/android/lint/setJavaScriptEnabled.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSetJavaScriptEnabledInspection
-
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.webkit.WebView
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-public class HelloWebApp : Activity() {
-
- fun test(webView: WebView) {
- webView.settings.<warning descr="Using `setJavaScriptEnabled` can introduce XSS vulnerabilities into you application, review carefully.">javaScriptEnabled</warning> = true // bad
- webView.getSettings().<warning descr="Using `setJavaScriptEnabled` can introduce XSS vulnerabilities into you application, review carefully.">setJavaScriptEnabled(true)</warning> // bad
- webView.getSettings().setJavaScriptEnabled(false) // good
- webView.loadUrl("file:///android_asset/www/index.html")
- }
-
- @SuppressLint("SetJavaScriptEnabled")
- fun suppressed(webView: WebView) {
- webView.getSettings().javaScriptEnabled = true; // bad
- webView.getSettings().setJavaScriptEnabled(true) // bad
- webView.getSettings().setJavaScriptEnabled(false); // good
- webView.loadUrl("file:///android_asset/www/index.html");
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/sharedPrefs.kt b/idea/testData/android/lint/sharedPrefs.kt
deleted file mode 100644
index 37e5514..0000000
--- a/idea/testData/android/lint/sharedPrefs.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitPrefEditsInspection
-
-import android.app.Activity
-import android.content.Context
-import android.os.Bundle
-import android.preference.PreferenceManager
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class SharedPrefsText(context: Context) : Activity() {
- // OK 1
- fun onCreate1(savedInstanceState: Bundle) {
- super.onCreate(savedInstanceState)
- val preferences = PreferenceManager.getDefaultSharedPreferences(this)
- val editor = preferences.edit()
- editor.putString("foo", "bar")
- editor.putInt("bar", 42)
- editor.commit()
- }
-
- // OK 2
- fun onCreate2(savedInstanceState: Bundle, apply: Boolean) {
- super.onCreate(savedInstanceState)
- val preferences = PreferenceManager.getDefaultSharedPreferences(this)
- val editor = preferences.edit()
- editor.putString("foo", "bar")
- editor.putInt("bar", 42)
- if (apply) {
- editor.apply()
- }
- }
-
- // Not a bug
- fun test(foo: Foo) {
- val bar1 = foo.edit()
- val bar3 = edit()
- apply()
- }
-
- internal fun apply() {
-
- }
-
- fun edit(): Bar {
- return Bar()
- }
-
- class Foo {
- internal fun edit(): Bar {
- return Bar()
- }
- }
-
- class Bar
-
- // Bug
- fun bug1(savedInstanceState: Bundle) {
- super.onCreate(savedInstanceState)
- val preferences = PreferenceManager.getDefaultSharedPreferences(this)
- val editor = preferences.<warning descr="`SharedPreferences.edit()` without a corresponding `commit()` or `apply()` call">edit()</warning>
- editor.putString("foo", "bar")
- editor.putInt("bar", 42)
- }
-
- init {
- val preferences = PreferenceManager.getDefaultSharedPreferences(context)
- val editor = preferences.<warning descr="`SharedPreferences.edit()` without a corresponding `commit()` or `apply()` call"><warning descr="`SharedPreferences.edit()` without a corresponding `commit()` or `apply()` call">edit()</warning></warning>
- editor.putString("foo", "bar")
- }
-
- fun testResultOfCommit() {
- val r1 = PreferenceManager.getDefaultSharedPreferences(this).edit().putString("wat", "wat").commit()
- val r2 = PreferenceManager.getDefaultSharedPreferences(this).edit().putString("wat", "wat").commit().toString()
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/showDiagnosticsWhenFileIsRed.kt b/idea/testData/android/lint/showDiagnosticsWhenFileIsRed.kt
deleted file mode 100644
index 5623005..0000000
--- a/idea/testData/android/lint/showDiagnosticsWhenFileIsRed.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSQLiteStringInspection
-
-import android.database.sqlite.SQLiteDatabase
-
-fun test(db: SQLiteDatabase) {
- val <warning descr="[UNUSED_VARIABLE] Variable 'a' is never used">a</warning>: String = <error descr="[CONSTANT_EXPECTED_TYPE_MISMATCH] The integer literal does not conform to the expected type String">1</error>
-
- db.<warning descr="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.)">execSQL("CREATE TABLE COMPANY(NAME STRING)")</warning>
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/sqlite.kt b/idea/testData/android/lint/sqlite.kt
deleted file mode 100644
index 93eab53..0000000
--- a/idea/testData/android/lint/sqlite.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSQLiteStringInspection
-
-import android.database.sqlite.SQLiteDatabase
-
-fun test(db: SQLiteDatabase) {
- db.<warning descr="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.)">execSQL("CREATE TABLE COMPANY(NAME STRING)")</warning>
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/systemServices.kt b/idea/testData/android/lint/systemServices.kt
deleted file mode 100644
index 2e68605..0000000
--- a/idea/testData/android/lint/systemServices.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintServiceCastInspection
-
-import android.content.ClipboardManager
-import android.app.Activity
-import android.app.WallpaperManager
-import android.content.Context
-import android.hardware.display.DisplayManager
-import android.service.wallpaper.WallpaperService
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class SystemServiceTest : Activity() {
-
- fun test1() {
- val displayServiceOk = getSystemService(DISPLAY_SERVICE) as DisplayManager
- val displayServiceWrong = <error descr="Suspicious cast to `DisplayManager` for a `DEVICE_POLICY_SERVICE`: expected `DevicePolicyManager`">getSystemService(DEVICE_POLICY_SERVICE) as DisplayManager</error>
- val wallPaperOk = getSystemService(WALLPAPER_SERVICE) as WallpaperService
- val wallPaperWrong = <error descr="Suspicious cast to `WallpaperManager` for a `WALLPAPER_SERVICE`: expected `WallpaperService`">getSystemService(WALLPAPER_SERVICE) as WallpaperManager</error>
- }
-
- fun test2(context: Context) {
- val displayServiceOk = context.getSystemService(DISPLAY_SERVICE) as DisplayManager
- val displayServiceWrong = <error descr="Suspicious cast to `DisplayManager` for a `DEVICE_POLICY_SERVICE`: expected `DevicePolicyManager`">context.getSystemService(DEVICE_POLICY_SERVICE) as DisplayManager</error>
- }
-
- fun clipboard(context: Context) {
- val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- val clipboard2 = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/toast.kt b/idea/testData/android/lint/toast.kt
deleted file mode 100644
index 9cb2510..0000000
--- a/idea/testData/android/lint/toast.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintShowToastInspection
-
-import android.app.Activity
-import android.content.Context
-import android.widget.Toast
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class ToastTest(context: Context) : Activity() {
- private fun createToast(context: Context): Toast {
- // Don't warn here
- return Toast.makeText(context, "foo", Toast.LENGTH_LONG)
- }
-
- private fun insideRunnable(context: Context) {
- Runnable {
- Toast.makeText(context, "foo", Toast.LENGTH_LONG).show()
- }
-
- Runnable {
- val toast = Toast.makeText(context, "foo", Toast.LENGTH_LONG)
- if (5 > 3) {
- toast.show()
- }
- }
-
- Runnable {
- Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, "foo", Toast.LENGTH_LONG)
- }
- }
-
- private fun showToast(context: Context) {
- // Don't warn here
- val toast = Toast.makeText(context, "foo", Toast.LENGTH_LONG)
- System.out.println("Other intermediate code here")
- val temp = 5 + 2
- toast.show()
- }
-
- private fun showToast2(context: Context) {
- // Don't warn here
- val duration = Toast.LENGTH_LONG
- Toast.makeText(context, "foo", Toast.LENGTH_LONG).show()
- Toast.makeText(context, R.string.app_name, duration).show()
- }
-
- private fun broken(context: Context) {
- // Errors
- Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, "foo", Toast.LENGTH_LONG)
- val toast = Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, R.string.app_name, <warning descr="Expected duration `Toast.LENGTH_SHORT` or `Toast.LENGTH_LONG`, a custom duration value is not supported">5000</warning>)
- toast.duration
- }
-
- init {
- Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, "foo", Toast.LENGTH_LONG)
- }
-
- @android.annotation.SuppressLint("ShowToast")
- private fun checkSuppress1(context: Context) {
- val toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG)
- }
-
- private fun checkSuppress2(context: Context) {
- @android.annotation.SuppressLint("ShowToast")
- val toast = Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(this, "MyToast", Toast.LENGTH_LONG)
- }
-
- class R {
- object string {
- val app_name = 1
- }
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/valueOf.kt b/idea/testData/android/lint/valueOf.kt
deleted file mode 100644
index 1702302..0000000
--- a/idea/testData/android/lint/valueOf.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseValueOfInspection
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class Simple {
- fun test() {
- <warning descr="Use `Integer.valueOf(5)` instead">Integer(5)</warning>
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/velocityTrackerRecycle.kt b/idea/testData/android/lint/velocityTrackerRecycle.kt
deleted file mode 100644
index 73a5933..0000000
--- a/idea/testData/android/lint/velocityTrackerRecycle.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintRecycleInspection
-
-@file:Suppress("UNUSED_VARIABLE")
-
-import android.app.Activity
-import android.os.Bundle
-import android.view.VelocityTracker
-
-class MainActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- VelocityTracker.<warning descr="This `VelocityTracker` should be recycled after use with `#recycle()`">obtain</warning>()
-
- VelocityTracker.obtain().recycle()
-
- val v1 = VelocityTracker.<warning descr="This `VelocityTracker` should be recycled after use with `#recycle()`">obtain</warning>()
-
- val v2 = VelocityTracker.obtain()
- v2.recycle()
- }
-}
diff --git a/idea/testData/android/lint/viewConstructor.kt b/idea/testData/android/lint/viewConstructor.kt
deleted file mode 100644
index 2087cee..0000000
--- a/idea/testData/android/lint/viewConstructor.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintViewConstructorInspection
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.View
-import android.widget.TextView
-
-class View1(context: Context?) : View(context)
-class View2(context: Context?, attrs: AttributeSet?) : View(context, attrs)
-class View3(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : TextView(context, attrs, defStyleAttr)
-
-// Error
-class <warning descr="Custom view `View4` is missing constructor used by tools: `(Context)` or `(Context,AttributeSet)` or `(Context,AttributeSet,int)`">View4</warning>(<warning descr="[UNUSED_PARAMETER] Parameter 'int' is never used">int</warning>: Int, context: Context?) : View(context)
-
-// Error
-class <warning descr="Custom view `View5` is missing constructor used by tools: `(Context)` or `(Context,AttributeSet)` or `(Context,AttributeSet,int)`">View5</warning>(context: Context?, attrs: AttributeSet?, val name: String) : View(context, attrs)
-
-class View6 : View {
- constructor(context: Context) : super(context) {
-
- }
-}
-
-class View7 : View {
- constructor(context: Context) : super(context)
- constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
-}
-
-// Error
-class <warning descr="Custom view `View8` is missing constructor used by tools: `(Context)` or `(Context,AttributeSet)` or `(Context,AttributeSet,int)`">View8</warning> : View {
- constructor(context: Context, <warning descr="[UNUSED_PARAMETER] Parameter 'a' is never used">a</warning>: Int) : super(context)
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/viewHolder.kt b/idea/testData/android/lint/viewHolder.kt
deleted file mode 100644
index ad2f7fc..0000000
--- a/idea/testData/android/lint/viewHolder.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintViewHolderInspection
-
-@file:Suppress("NAME_SHADOWING", "unused", "UNUSED_VALUE", "VARIABLE_WITH_REDUNDANT_INITIALIZER", "UNUSED_VARIABLE")
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.BaseAdapter
-import android.widget.LinearLayout
-import android.widget.TextView
-import java.util.ArrayList
-
-@SuppressWarnings("ConstantConditions", "UnusedDeclaration")
-abstract class ViewHolderTest : BaseAdapter() {
- override fun getCount() = 0
- override fun getItem(position: Int) = null
- override fun getItemId(position: Int) = 0L
-
- class Adapter1 : ViewHolderTest() {
- override fun getView(position: Int, convertView: View, parent: ViewGroup) = null
- }
-
- class Adapter2 : ViewHolderTest() {
- lateinit var mInflater: LayoutInflater
-
- override fun getView(position: Int, convertView: View, parent: ViewGroup): View {
- var convertView = convertView
- // Should use View Holder pattern here
- convertView = mInflater.<warning descr="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">inflate(R.layout.your_layout, null)</warning>
-
- val text = convertView.findViewById(R.id.text) as TextView
- text.text = "Position " + position
-
- return convertView
- }
- }
-
- class Adapter3 : ViewHolderTest() {
- lateinit var mInflater: LayoutInflater
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- var convertView = convertView
- // Already using View Holder pattern
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.your_layout, null)
- }
-
- val text = convertView!!.findViewById(R.id.text) as TextView
- text.text = "Position " + position
-
- return convertView
- }
- }
-
- class Adapter4 : ViewHolderTest() {
- lateinit var mInflater: LayoutInflater
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- var convertView = convertView
- // Already using View Holder pattern
- //noinspection StatementWithEmptyBody
- if (convertView != null) {
- } else {
- convertView = mInflater.inflate(R.layout.your_layout, null)
- }
-
- val text = convertView!!.findViewById(R.id.text) as TextView
- text.text = "Position " + position
-
- return convertView
- }
- }
-
- class Adapter5 : ViewHolderTest() {
- lateinit var mInflater: LayoutInflater
-
- override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- var convertView = convertView
- // Already using View Holder pattern
- convertView = if (convertView == null) mInflater.inflate(R.layout.your_layout, null) else convertView
-
- val text = convertView!!.findViewById(R.id.text) as TextView
- text.text = "Position " + position
-
- return convertView
- }
- }
-
- class Adapter6 : ViewHolderTest() {
- private val mContext: Context? = null
- private var mLayoutInflator: LayoutInflater? = null
- private lateinit var mLapTimes: ArrayList<Double>
-
- override fun getView(position: Int, convertView: View, parent: ViewGroup): View {
- if (mLayoutInflator == null)
- mLayoutInflator = mContext!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-
- var v: View? = convertView
- if (v == null) v = mLayoutInflator!!.inflate(R.layout.your_layout, null)
-
- val listItemHolder = v!!.findViewById(R.id.laptimes_list_item_holder) as LinearLayout
- listItemHolder.removeAllViews()
-
- for (i in 1..5) {
- val lapItemView = mLayoutInflator!!.inflate(R.layout.laptime_item, null)
- if (i == 0) {
- val t = lapItemView.findViewById(R.id.laptime_text) as TextView
- }
-
- val t2 = lapItemView.findViewById(R.id.laptime_text2) as TextView
- if (i < mLapTimes.size - 1 && mLapTimes.size > 1) {
- var laptime = mLapTimes[i] - mLapTimes[i + 1]
- if (laptime < 0) laptime = mLapTimes[i]
- }
-
- listItemHolder.addView(lapItemView)
-
- }
- return v
- }
- }
-
- class Adapter7 : ViewHolderTest() {
- lateinit var inflater: LayoutInflater
-
- override fun getView(position: Int, convertView: View, parent: ViewGroup): View {
- var rootView: View? = convertView
- val itemViewType = getItemViewType(position)
- when (itemViewType) {
- 0 -> {
- if (rootView != null)
- return rootView
- rootView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false)
- }
- }
- return rootView!!
- }
- }
-
- class R {
- object layout {
- val your_layout = 1
- val laptime_item = 2
- }
-
- object id {
- val laptime_text = 1
- val laptime_text2 = 2
- val laptimes_list_item_holder = 3
- val text = 4
- }
- }
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/wrongAnnotation.kt b/idea/testData/android/lint/wrongAnnotation.kt
deleted file mode 100644
index 62cb9f4..0000000
--- a/idea/testData/android/lint/wrongAnnotation.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLocalSuppressInspection
-
-import android.annotation.SuppressLint
-import android.view.View
-
-@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
-class WrongAnnotation2 {
- @SuppressLint("NewApi")
- private val field1: Int = 0
-
- @SuppressLint("NewApi")
- private val field2 = 5
-
- companion object {
- @SuppressLint("NewApi") // Valid: class-file check on method
- fun foobar(view: View, @SuppressLint("NewApi") foo: Int) {
- // Invalid: class-file check
- @SuppressLint("NewApi") // Invalid
- val a: Boolean
- @SuppressLint("SdCardPath", "NewApi") // Invalid: class-file based check on local variable
- val b: Boolean
- @android.annotation.SuppressLint("SdCardPath", "NewApi") // Invalid (FQN)
- val c: Boolean
- @SuppressLint("SdCardPath") // Valid: AST-based check
- val d: Boolean
- }
-
- init {
- // Local variable outside method: invalid
- @SuppressLint("NewApi")
- val localvar = 5
- }
-
- private fun test() {
- @SuppressLint("NewApi") // Invalid
- val a = View.MEASURED_STATE_MASK
- }
- }
-}
diff --git a/idea/testData/android/lint/wrongImport.kt b/idea/testData/android/lint/wrongImport.kt
deleted file mode 100644
index b688362..0000000
--- a/idea/testData/android/lint/wrongImport.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSuspiciousImportInspection
-
-//Warning
-<warning descr="Don't include `android.R` here; use a fully qualified name for each usage instead">import android.R</warning>
-
-fun a() {
- R.id.button1
-}
\ No newline at end of file
diff --git a/idea/testData/android/lint/wrongViewCall.kt b/idea/testData/android/lint/wrongViewCall.kt
deleted file mode 100644
index 0ee6e92..0000000
--- a/idea/testData/android/lint/wrongViewCall.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintWrongCallInspection
-
-import android.content.Context
-import android.graphics.Canvas
-import android.util.AttributeSet
-import android.widget.FrameLayout
-import android.widget.LinearLayout
-
-abstract class WrongViewCall(context: Context, attrs: AttributeSet, defStyle: Int) : LinearLayout(context, attrs, defStyle) {
- private val child: MyChild? = null
-
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- child?.<error descr="Suspicious method call; should probably call \"`draw`\" rather than \"`onDraw`\"">onDraw</error>(canvas)
- }
-
- private inner class MyChild(context: Context, attrs: AttributeSet, defStyle: Int) : FrameLayout(context, attrs, defStyle) {
-
- public override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- }
- }
-}
diff --git a/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt
index 6274bc4..5323b6d 100644
--- a/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt
+++ b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add Parcelable Implementation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintParcelCreatorInspection
import android.os.Parcel
import android.os.Parcelable
diff --git a/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt
index e045a31..77379bd 100644
--- a/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt
+++ b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add Parcelable Implementation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintParcelCreatorInspection
import android.os.Parcelable
class <caret>NoImplementation : Parcelable
\ No newline at end of file
diff --git a/idea/testData/android/lintQuickfix/requiresApi/annotation.kt b/idea/testData/android/lintQuickfix/requiresApi/annotation.kt
index dbf09a0..76db9697 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/annotation.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/annotation.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/requiresApi/companion.kt b/idea/testData/android/lintQuickfix/requiresApi/companion.kt
index d332523..68568d4 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/companion.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/companion.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/requiresApi/defaultParameter.kt b/idea/testData/android/lintQuickfix/requiresApi/defaultParameter.kt
index 9f7d6c6..cddbbe5 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/defaultParameter.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/defaultParameter.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/requiresApi/extend.kt b/idea/testData/android/lintQuickfix/requiresApi/extend.kt
index 7b95585..233c746 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/extend.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/extend.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/requiresApi/functionLiteral.kt b/idea/testData/android/lintQuickfix/requiresApi/functionLiteral.kt
index 9b04c508..00575be 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/functionLiteral.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/functionLiteral.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/requiresApi/inlinedConstant.kt b/idea/testData/android/lintQuickfix/requiresApi/inlinedConstant.kt
index ee42d76..d838f2b 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/inlinedConstant.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/inlinedConstant.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(KITKAT) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintInlinedApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
class Test {
diff --git a/idea/testData/android/lintQuickfix/requiresApi/method.kt b/idea/testData/android/lintQuickfix/requiresApi/method.kt
index 79068fc..14ccb78 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/method.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/method.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/requiresApi/property.kt b/idea/testData/android/lintQuickfix/requiresApi/property.kt
index d4c0ae8..fa17b13 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/property.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/property.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/requiresApi/when.kt b/idea/testData/android/lintQuickfix/requiresApi/when.kt
index 67d45b8..71997f7 100644
--- a/idea/testData/android/lintQuickfix/requiresApi/when.kt
+++ b/idea/testData/android/lintQuickfix/requiresApi/when.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/suppressLint/activityMethod.kt b/idea/testData/android/lintQuickfix/suppressLint/activityMethod.kt
index 34cac7d..7df0f39 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/activityMethod.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/activityMethod.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
import android.app.Activity
import android.os.Environment
diff --git a/idea/testData/android/lintQuickfix/suppressLint/addToExistingAnnotation.kt b/idea/testData/android/lintQuickfix/suppressLint/addToExistingAnnotation.kt
index a0a917d..ba329c0 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/addToExistingAnnotation.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/addToExistingAnnotation.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
import android.annotation.SuppressLint
import android.app.Activity
diff --git a/idea/testData/android/lintQuickfix/suppressLint/constructorParameter.kt b/idea/testData/android/lintQuickfix/suppressLint/constructorParameter.kt
index bfed3b9..c9669de 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/constructorParameter.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/constructorParameter.kt
@@ -1,4 +1,4 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
class SdCard(val path: String = "<caret>/sdcard")
\ No newline at end of file
diff --git a/idea/testData/android/lintQuickfix/suppressLint/destructuringDeclaration.kt b/idea/testData/android/lintQuickfix/suppressLint/destructuringDeclaration.kt
index 346336d..0790faf 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/destructuringDeclaration.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/destructuringDeclaration.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
fun foo() {
val (a: String, b: String) = "<caret>/sdcard"
diff --git a/idea/testData/android/lintQuickfix/suppressLint/lambdaArgument.kt b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgument.kt
index 08556f1..3e28560 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/lambdaArgument.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgument.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
fun foo(l: Any) = l
diff --git a/idea/testData/android/lintQuickfix/suppressLint/lambdaArgumentProperty.kt b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgumentProperty.kt
index 233767c..4e880f5 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/lambdaArgumentProperty.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgumentProperty.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
fun foo(l: Any) = l
diff --git a/idea/testData/android/lintQuickfix/suppressLint/methodParameter.kt b/idea/testData/android/lintQuickfix/suppressLint/methodParameter.kt
index 854638e..f9bc321 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/methodParameter.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/methodParameter.kt
@@ -1,4 +1,4 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
fun foo(path: String = "<caret>/sdcard") = path
\ No newline at end of file
diff --git a/idea/testData/android/lintQuickfix/suppressLint/propertyWithLambda.kt b/idea/testData/android/lintQuickfix/suppressLint/propertyWithLambda.kt
index 8afada5..529015f 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/propertyWithLambda.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/propertyWithLambda.kt
@@ -1,4 +1,4 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
val getPath = { "<caret>/sdcard" }
\ No newline at end of file
diff --git a/idea/testData/android/lintQuickfix/suppressLint/simpleProperty.kt b/idea/testData/android/lintQuickfix/suppressLint/simpleProperty.kt
index c4fee12..a9ff8da 100644
--- a/idea/testData/android/lintQuickfix/suppressLint/simpleProperty.kt
+++ b/idea/testData/android/lintQuickfix/suppressLint/simpleProperty.kt
@@ -1,4 +1,4 @@
// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection
val path = "<caret>/sdcard"
\ No newline at end of file
diff --git a/idea/testData/android/lintQuickfix/targetApi/annotation.kt b/idea/testData/android/lintQuickfix/targetApi/annotation.kt
index f9f0553..ceceadd 100644
--- a/idea/testData/android/lintQuickfix/targetApi/annotation.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/annotation.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
import kotlin.reflect.KClass
diff --git a/idea/testData/android/lintQuickfix/targetApi/companion.kt b/idea/testData/android/lintQuickfix/targetApi/companion.kt
index a35ae52..6ecbaca 100644
--- a/idea/testData/android/lintQuickfix/targetApi/companion.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/companion.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetApi/defaultParameter.kt b/idea/testData/android/lintQuickfix/targetApi/defaultParameter.kt
index 8cff109..673405e 100644
--- a/idea/testData/android/lintQuickfix/targetApi/defaultParameter.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/defaultParameter.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetApi/extend.kt b/idea/testData/android/lintQuickfix/targetApi/extend.kt
index b47ea57..0c26fe2 100644
--- a/idea/testData/android/lintQuickfix/targetApi/extend.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/extend.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetApi/functionLiteral.kt b/idea/testData/android/lintQuickfix/targetApi/functionLiteral.kt
index ea1b9c2..a4c3c411 100644
--- a/idea/testData/android/lintQuickfix/targetApi/functionLiteral.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/functionLiteral.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetApi/inlinedConstant.kt b/idea/testData/android/lintQuickfix/targetApi/inlinedConstant.kt
index ed8cd3c3..45fb5d2 100644
--- a/idea/testData/android/lintQuickfix/targetApi/inlinedConstant.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/inlinedConstant.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(KITKAT) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintInlinedApiInspection
class Test {
fun foo(): Int {
diff --git a/idea/testData/android/lintQuickfix/targetApi/method.kt b/idea/testData/android/lintQuickfix/targetApi/method.kt
index 4df86f6..aeac2f9 100644
--- a/idea/testData/android/lintQuickfix/targetApi/method.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/method.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetApi/property.kt b/idea/testData/android/lintQuickfix/targetApi/property.kt
index 0cc187c..7418db2 100644
--- a/idea/testData/android/lintQuickfix/targetApi/property.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/property.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetApi/when.kt b/idea/testData/android/lintQuickfix/targetApi/when.kt
index 27a5919..df92ce5 100644
--- a/idea/testData/android/lintQuickfix/targetApi/when.kt
+++ b/idea/testData/android/lintQuickfix/targetApi/when.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/annotation.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/annotation.kt
index e052b68..31fed00 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/annotation.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/annotation.kt
@@ -1,6 +1,6 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
// INTENTION_NOT_AVAILABLE
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
import kotlin.reflect.KClass
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/defaultParameter.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/defaultParameter.kt
index 97f5da2..f448c22 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/defaultParameter.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/defaultParameter.kt
@@ -1,6 +1,6 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
// INTENTION_NOT_AVAILABLE
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/expressionBody.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/expressionBody.kt
index 16ee93f..dd8fe5b 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/expressionBody.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/expressionBody.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/functionLiteral.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/functionLiteral.kt
index 915c5c6..17633e8 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/functionLiteral.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/functionLiteral.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/if.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/if.kt
index e2f81f2..4087654 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/if.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/if.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/ifWithBlock.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/ifWithBlock.kt
index c74f3ee..0e6ba0f 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/ifWithBlock.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/ifWithBlock.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/inlinedConstant.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/inlinedConstant.kt
index febb06d..64281cd 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/inlinedConstant.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/inlinedConstant.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { ... }
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintInlinedApiInspection
class Test {
fun foo(): Int {
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/method.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/method.kt
index a1bbff2..f0ad689 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/method.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/method.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/when.kt b/idea/testData/android/lintQuickfix/targetVersionCheck/when.kt
index 11474ac..5a00d18 100644
--- a/idea/testData/android/lintQuickfix/targetVersionCheck/when.kt
+++ b/idea/testData/android/lintQuickfix/targetVersionCheck/when.kt
@@ -1,5 +1,5 @@
// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... }
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection
import android.graphics.drawable.VectorDrawable
diff --git a/plugins/lint/android-annotations/android-annotations.iml b/plugins/lint/android-annotations/android-annotations.iml
deleted file mode 100644
index c90834f..0000000
--- a/plugins/lint/android-annotations/android-annotations.iml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- </component>
-</module>
\ No newline at end of file
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/lint-api.iml b/plugins/lint/lint-api/lint-api.iml
deleted file mode 100644
index e654110..0000000
--- a/plugins/lint/lint-api/lint-api.iml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- </content>
- <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="guava" level="project" />
- <orderEntry type="library" name="android-plugin" level="project" />
- <orderEntry type="library" name="intellij-core" level="project" />
- <orderEntry type="module" module-name="android-annotations" />
- <orderEntry type="library" name="kotlin-runtime" level="project" />
- <orderEntry type="library" name="uast-android-studio" level="project" />
- </component>
-</module>
\ No newline at end of file
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 b66a841..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 f1584b2..0000000
--- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaVisitor.java
+++ /dev/null
@@ -1,1480 +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.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.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 java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AlternateConstructorInvocation;
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationDeclaration;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationMethodDeclaration;
-import lombok.ast.AnnotationValueArray;
-import lombok.ast.ArrayAccess;
-import lombok.ast.ArrayCreation;
-import lombok.ast.ArrayDimension;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.Assert;
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.Block;
-import lombok.ast.BooleanLiteral;
-import lombok.ast.Break;
-import lombok.ast.Case;
-import lombok.ast.Cast;
-import lombok.ast.Catch;
-import lombok.ast.CharLiteral;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ClassLiteral;
-import lombok.ast.Comment;
-import lombok.ast.CompilationUnit;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Continue;
-import lombok.ast.Default;
-import lombok.ast.DoWhile;
-import lombok.ast.EmptyDeclaration;
-import lombok.ast.EmptyStatement;
-import lombok.ast.EnumConstant;
-import lombok.ast.EnumDeclaration;
-import lombok.ast.EnumTypeBody;
-import lombok.ast.Expression;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.FloatingPointLiteral;
-import lombok.ast.For;
-import lombok.ast.ForEach;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Identifier;
-import lombok.ast.If;
-import lombok.ast.ImportDeclaration;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.InstanceInitializer;
-import lombok.ast.InstanceOf;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.InterfaceDeclaration;
-import lombok.ast.KeywordModifier;
-import lombok.ast.LabelledStatement;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.NullLiteral;
-import lombok.ast.PackageDeclaration;
-import lombok.ast.Return;
-import lombok.ast.Select;
-import lombok.ast.StaticInitializer;
-import lombok.ast.StringLiteral;
-import lombok.ast.Super;
-import lombok.ast.SuperConstructorInvocation;
-import lombok.ast.Switch;
-import lombok.ast.Synchronized;
-import lombok.ast.This;
-import lombok.ast.Throw;
-import lombok.ast.Try;
-import lombok.ast.TypeReference;
-import lombok.ast.TypeReferencePart;
-import lombok.ast.TypeVariable;
-import lombok.ast.UnaryExpression;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-import lombok.ast.While;
-
-/**
- * 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 (e.getClass().getSimpleName().equals("ProcessCanceledException")) {
- // 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 a357003..0000000
--- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintDriver.java
+++ /dev/null
@@ -1,2862 +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.UElement;
-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.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 PsiModifierListOwner) {
- PsiModifierListOwner owner = (PsiModifierListOwner) scope;
- if (isSuppressed(issue, owner.getModifierList())) {
- return true;
- }
- }
-
- if (checkComments && context.isSuppressedWithComment(scope, issue)) {
- return true;
- }
-
- scope = scope.getUastParent();
- if (scope instanceof PsiFile) {
- 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;
- }
-
- 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 99a602c..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 (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 a0ced7f..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.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(ResourceUrl.create(style.type, style.name, false), null));
- 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(ResourceUrl.create(ResourceType.STYLE, p.name,
- false), null));
- }
- }
-
- int index = name.lastIndexOf('.');
- if (index > 0) {
- String parentName = name.substring(0, index);
- if (!seen.contains(parentName)) {
- seen.add(parentName);
- queue.add(new ResourceValue(ResourceUrl.create(ResourceType.STYLE, parentName,
- false), null));
- }
- }
- }
- }
- }
-
- 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(ResourceUrl.create(style.type, style.name, false), null));
- 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(ResourceUrl.create(ResourceType.STYLE, p.name,
- false), null));
- }
- }
-
- int index = name.lastIndexOf('.');
- if (index > 0) {
- String parentName = name.substring(0, index);
- if (!seen.contains(parentName)) {
- seen.add(parentName);
- queue.add(new ResourceValue(ResourceUrl.create(ResourceType.STYLE, parentName,
- false), null));
- }
- }
- }
- }
- }
-
- 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 9b3368e..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.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);
- }
- }
- }
- }
- } 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);
- }
- }
- }
- }
- 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);
- }
-
- 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/lint-checks.iml b/plugins/lint/lint-checks/lint-checks.iml
deleted file mode 100644
index 7f00be7..0000000
--- a/plugins/lint/lint-checks/lint-checks.iml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- </content>
- <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module" module-name="android-annotations" />
- <orderEntry type="module" module-name="lint-api" />
- <orderEntry type="module" module-name="lint-idea" />
- <orderEntry type="library" name="android-plugin" level="project" />
- <orderEntry type="library" name="guava" level="project" />
- <orderEntry type="library" name="intellij-core" level="project" />
- <orderEntry type="library" name="kotlin-runtime" level="project" />
- </component>
-</module>
\ No newline at end of file
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 81531cf..0000000
--- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java
+++ /dev/null
@@ -1,1992 +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 visitMethod(@NotNull UMethod method) {
- // API check for default methods
- if (method.getModifierList().hasExplicitModifier(PsiModifier.DEFAULT)) {
- int api = 24; // minSdk for default methods
- int minSdk = getMinSdk(mContext);
-
- if (!isSuppressed(api, method, minSdk, mContext, UNSUPPORTED)) {
- Location location = mContext.getLocation(method);
- String message = String.format("Default method requires API level %1$d "
- + "(current min is %2$d)", api, minSdk);
- mContext.reportUast(UNSUPPORTED, method, location, message);
- }
- }
-
- return super.visitMethod(method);
- }
-
- @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 == ifStatement.getThenExpression();
- boolean fromElse = ifStatement != null && prev == 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 5989552..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.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 12f6a26..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.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 72c5136..0000000
--- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java
+++ /dev/null
@@ -1,105 +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 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;
- }
-
- 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 cac223b..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.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 98305d2..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.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 d2529c4..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.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 d29ca09..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.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/lint-idea.iml b/plugins/lint/lint-idea/lint-idea.iml
deleted file mode 100644
index 55aac4b..0000000
--- a/plugins/lint/lint-idea/lint-idea.iml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- </content>
- <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module" module-name="lint-api" />
- <orderEntry type="module" module-name="lint-checks" />
- <orderEntry type="library" name="kotlin-runtime" level="project" />
- <orderEntry type="library" name="kotlin-reflect" level="project" />
- <orderEntry type="library" name="idea-full" level="project" />
- <orderEntry type="module" module-name="uast-kotlin" />
- <orderEntry type="library" name="android-plugin" level="project" />
- <orderEntry type="module" module-name="android-annotations" />
- <orderEntry type="module" module-name="frontend" />
- <orderEntry type="module" module-name="idea-analysis" />
- <orderEntry type="module" module-name="idea-core" />
- <orderEntry type="module" module-name="idea" />
- <orderEntry type="module" module-name="idea-android" />
- </component>
-</module>
\ 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 d00ebe5..0000000
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintExternalAnnotator.java
+++ /dev/null
@@ -1,414 +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.ModuleUtilCore;
-import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
-import com.intellij.openapi.project.Project;
-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.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 && !IntellijLintProject.hasAndroidModule(module.getProject())) {
- 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);
- }
-
- @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 93e5243..0000000
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintGlobalInspectionContext.java
+++ /dev/null
@@ -1,213 +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.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 = 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/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 e01191c..0000000
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java
+++ /dev/null
@@ -1,867 +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.project.AndroidProjectInfo;
-import com.android.tools.idea.res.AppResourceRepository;
-import com.android.tools.idea.res.LocalResourceRepository;
-import com.android.tools.idea.res.ModuleResourceRepository;
-import com.android.tools.idea.res.ProjectResourceRepository;
-import com.android.tools.idea.sdk.IdeSdks;
-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.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-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 = AndroidSdkData.getSdkData(facet); // AS24 AndroidSdkData.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
- ? ProjectResourceRepository.getOrCreateInstance(facet) // AS24
- : ModuleResourceRepository.getOrCreateInstance(facet); // AS24
- }
- }
-
- 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.getOrCreateInstance(module); // AS24
- 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 b811362..0000000
--- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java
+++ /dev/null
@@ -1,1132 +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.LibraryOrderEntry;
-import com.intellij.openapi.roots.ModuleRootManager;
-import com.intellij.openapi.roots.OrderEntry;
-import com.intellij.openapi.roots.OrderRootType;
-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.getInstance(myFacet).getMinSdkVersion(); // AS24 getInstance()
- }
- return mMinSdkVersion;
- }
-
- @NonNull
- @Override
- public AndroidVersion getTargetSdkVersion() {
- if (mTargetSdkVersion == null) {
- mTargetSdkVersion = AndroidModuleInfo.getInstance(myFacet).getTargetSdkVersion(); // AS24 getInstance()
- }
-
- 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/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;
- }
-}