diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as31 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as31 index 492f91baf49..2bd0a5210e1 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as31 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as31 @@ -25,7 +25,6 @@ import org.jetbrains.kotlin.android.configure.AbstractConfigureProjectTest 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.parcel.AbstractParcelBytecodeListingTest import org.jetbrains.kotlin.android.quickfix.AbstractAndroidLintQuickfixTest import org.jetbrains.kotlin.android.quickfix.AbstractAndroidQuickFixMultiFileTest @@ -1115,10 +1114,6 @@ fun main(args: Array) { model("android/quickfix", pattern = """^(\w+)\.((before\.Main\.\w+)|(test))$""", testMethod = "doTestWithExtraFile") } - testClass { - model("android/lint", excludeParentDirs = true) - } - testClass { model("android/lintQuickfix", pattern = "^([\\w\\-_]+)\\.kt$") } diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetApiQuickFix.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetApiQuickFix.kt.as31 new file mode 100644 index 00000000000..a05fc15de40 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetApiQuickFix.kt.as31 @@ -0,0 +1,95 @@ +/* + * 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 +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.android.hasBackingField +import org.jetbrains.kotlin.idea.util.addAnnotation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* + + +class AddTargetApiQuickFix( + val api: Int, + val useRequiresApi: Boolean +) : AndroidLintQuickFix { + + private companion object { + val FQNAME_TARGET_API = FqName(SdkConstants.FQCN_TARGET_API) + val FQNAME_REQUIRES_API = FqName(REQUIRES_API_ANNOTATION) + } + + override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean = + getAnnotationContainer(startElement, useRequiresApi) != null + + override fun getName(): String = getAnnotationValue(false).let { + if (useRequiresApi) { + // Not Available in Android plugin 2.0 + // AndroidBundle.message("android.lint.fix.add.requires.api", it) + "Add @RequiresApi($it) Annotation" + } else { + AndroidBundle.message("android.lint.fix.add.target.api", it) + } + } + + override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) { + val annotationContainer = getAnnotationContainer(startElement, useRequiresApi) ?: return + if (!FileModificationService.getInstance().preparePsiElementForWrite(annotationContainer)) { + return + } + + if (annotationContainer is KtModifierListOwner) { + annotationContainer.addAnnotation( + if (useRequiresApi) FQNAME_REQUIRES_API else FQNAME_TARGET_API, + getAnnotationValue(true), + whiteSpaceText = if (annotationContainer.isNewLineNeededForAnnotation()) "\n" else " ") + } + } + + private fun KtElement.isNewLineNeededForAnnotation() = !(this is KtParameter || this is KtTypeParameter || this is KtPropertyAccessor) + + private fun getAnnotationValue(fullyQualified: Boolean) = getVersionField(api, fullyQualified) + + private fun getAnnotationContainer(element: PsiElement, useRequiresApi: Boolean): PsiElement? { + return PsiTreeUtil.findFirstParent(element) { + if (useRequiresApi) + it.isRequiresApiAnnotationValidTarget() + else + it.isTargetApiAnnotationValidTarget() + } + } + + private fun PsiElement.isRequiresApiAnnotationValidTarget(): Boolean { + return this is KtClassOrObject || + (this is KtFunction && this !is KtFunctionLiteral) || + (this is KtProperty && !isLocal && hasBackingField()) || + this is KtPropertyAccessor + } + + private fun PsiElement.isTargetApiAnnotationValidTarget(): Boolean { + return this is KtClassOrObject || + (this is KtFunction && this !is KtFunctionLiteral) || + this is KtPropertyAccessor + } +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetVersionCheckQuickFix.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetVersionCheckQuickFix.kt.as31 new file mode 100644 index 00000000000..fbc5b6d4289 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/AddTargetVersionCheckQuickFix.kt.as31 @@ -0,0 +1,94 @@ +/* + * 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.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.caches.resolve.analyze +import org.jetbrains.kotlin.idea.codeInsight.surroundWith.statement.KotlinIfSurrounder +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.inspections.findExistingEditor +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + + +class AddTargetVersionCheckQuickFix(val api: Int) : AndroidLintQuickFix { + + override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) { + val targetExpression = getTargetExpression(startElement) + val project = targetExpression?.project ?: return + val editor = targetExpression.findExistingEditor() ?: return + + val file = targetExpression.containingFile + val documentManager = PsiDocumentManager.getInstance(project) + val document = documentManager.getDocument(file) ?: return + + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return + } + + val surrounder = getSurrounder(targetExpression, "\"VERSION.SDK_INT < ${getVersionField(api, false)}\"") + val conditionRange = surrounder.surroundElements(project, editor, arrayOf(targetExpression)) ?: return + val conditionText = "android.os.Build.VERSION.SDK_INT >= ${getVersionField(api, true)}" + document.replaceString(conditionRange.startOffset, conditionRange.endOffset, conditionText) + documentManager.commitDocument(document) + + ShortenReferences.DEFAULT.process(documentManager.getPsiFile(document) as KtFile, + conditionRange.startOffset, + conditionRange.startOffset + conditionText.length) + } + + override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean = + getTargetExpression(startElement) != null + + override fun getName(): String = "Surround with if (VERSION.SDK_INT >= VERSION_CODES.${getVersionField(api, false)}) { ... }" + + private fun getTargetExpression(element: PsiElement): KtElement? { + var current = PsiTreeUtil.getParentOfType(element, KtExpression::class.java) + while (current != null) { + if (current.parent is KtBlockExpression || + current.parent is KtContainerNode || + current.parent is KtWhenEntry || + current.parent is KtFunction || + current.parent is KtPropertyAccessor || + current.parent is KtProperty || + current.parent is KtReturnExpression || + current.parent is KtDestructuringDeclaration) { + break + } + current = PsiTreeUtil.getParentOfType(current, KtExpression::class.java, true) + } + + return current + } + + private fun getSurrounder(element: KtElement, todoText: String?): KotlinIfSurrounder { + val used = element.analyze(BodyResolveMode.PARTIAL_WITH_CFA)[BindingContext.USED_AS_EXPRESSION, element] ?: false + return if (used) { + object : KotlinIfSurrounder() { + override fun getCodeTemplate(): String = "if (a) { \n} else {\nTODO(${todoText ?: ""})\n}" + } + } else { + KotlinIfSurrounder() + } + } +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ApiUtils.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ApiUtils.kt.as31 new file mode 100644 index 00000000000..3a52671ba84 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ApiUtils.kt.as31 @@ -0,0 +1,24 @@ +/* + * 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.sdklib.SdkVersionInfo.* + + +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.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/KotlinAndroidQuickFixProvider.kt.as31 new file mode 100644 index 00000000000..050b103d208 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/KotlinAndroidQuickFixProvider.kt.as31 @@ -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 { + val fixes: Array = 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 { + 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/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ParcelableQuickFix.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ParcelableQuickFix.kt.as31 new file mode 100644 index 00000000000..d3442300f5e --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/ParcelableQuickFix.kt.as31 @@ -0,0 +1,43 @@ +/* + * 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.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 +import org.jetbrains.kotlin.android.isParcelize +import org.jetbrains.kotlin.psi.KtClass + + +class ParcelableQuickFix : AndroidLintQuickFix { + override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) { + startElement.getTargetClass()?.implementParcelable() + } + + override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean { + val targetClass = startElement.getTargetClass() ?: return false + return targetClass.canAddParcelable() && !targetClass.isParcelize() + } + + override fun getName(): String = AndroidBundle.message("implement.parcelable.intention.text") + + private fun PsiElement.getTargetClass(): KtClass? = PsiTreeUtil.getParentOfType(this, KtClass::class.java, false) +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/SuppressLintQuickFix.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/SuppressLintQuickFix.kt.as31 new file mode 100644 index 00000000000..724f718c257 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/quickfix/SuppressLintQuickFix.kt.as31 @@ -0,0 +1,99 @@ +/* + * 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 +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.android.hasBackingField +import org.jetbrains.kotlin.idea.util.addAnnotation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* + + +class SuppressLintQuickFix(id: String) : AndroidLintQuickFix { + private val lintId = getLintId(id) + + 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 + } + + val argument = "\"$lintId\"" + + when (annotationContainer) { + 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 + val psiFactory = KtPsiFactory(entry) + val newArgList = psiFactory.createCallArguments("($argument)") + when { + args == null -> // new argument list + entry.addAfter(newArgList, entry.lastChild) + args.arguments.isEmpty() -> // replace '()' with a new argument list + args.replace(newArgList) + args.arguments.none { it.textMatches(argument) } -> + args.addArgument(newArgList.arguments[0]) + } + + return true + } + + private fun getLintId(intentionId: String) = + if (intentionId.startsWith(INTENTION_NAME_PREFIX)) intentionId.substring(INTENTION_NAME_PREFIX.length) else intentionId + + private fun KtElement.isNewLineNeededForAnnotation(): Boolean { + return !(this is KtParameter || + this is KtTypeParameter || + this is KtPropertyAccessor) + } + + private fun PsiElement.isSuppressLintTarget(): Boolean { + return this is KtDeclaration && + (this as? KtProperty)?.hasBackingField() ?: true && + this !is KtFunctionLiteral && + this !is KtDestructuringDeclaration + } + 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.as31 b/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/AbstractKotlinLintTest.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/KotlinLintTestGenerated.java.as31 b/idea/idea-android/tests/org/jetbrains/kotlin/android/lint/KotlinLintTestGenerated.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt.as31 b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt.as31 new file mode 100644 index 00000000000..c2f6989d0bc --- /dev/null +++ b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt.as31 @@ -0,0 +1,62 @@ +/* + * 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.intellij.codeInspection.InspectionProfileEntry +import com.intellij.openapi.util.io.FileUtil +import com.intellij.util.PathUtil +import org.jetbrains.android.inspections.lint.AndroidLintInspectionBase +import org.jetbrains.kotlin.android.KotlinAndroidTestCase +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import java.io.File + + +abstract class AbstractAndroidLintQuickfixTest : KotlinAndroidTestCase() { + + override fun setUp() { + AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap() + super.setUp() + } + + fun doTest(path: String) { + val fileText = FileUtil.loadFile(File(path), true) + val intentionText = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// INTENTION_TEXT: ") ?: error("Empty intention text") + val mainInspectionClassName = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// INSPECTION_CLASS: ") ?: error("Empty inspection class name") + val dependency = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// DEPENDENCY: ") + val intentionAvailable = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// INTENTION_NOT_AVAILABLE") + + val inspection = Class.forName(mainInspectionClassName).newInstance() as InspectionProfileEntry + myFixture.enableInspections(inspection) + + val sourceFile = myFixture.copyFileToProject(path, "src/${PathUtil.getFileName(path)}") + myFixture.configureFromExistingVirtualFile(sourceFile) + + if (dependency != null) { + val (dependencyFile, dependencyTargetPath) = dependency.split(" -> ").map(String::trim) + myFixture.copyFileToProject("${PathUtil.getParentPath(path)}/$dependencyFile", "src/$dependencyTargetPath") + } + + if (intentionAvailable) { + val intention = myFixture.getAvailableIntention(intentionText) ?: error("Failed to find intention") + myFixture.launchAction(intention) + myFixture.checkResultByFile(path + ".expected") + } + else { + assertNull("Intention should not be available", myFixture.availableIntentions.find { it.text == intentionText }) + } + } +} \ No newline at end of file diff --git a/idea/src/META-INF/android-lint.xml.as31 b/idea/src/META-INF/android-lint.xml.as31 new file mode 100644 index 00000000000..895eacad82a --- /dev/null +++ b/idea/src/META-INF/android-lint.xml.as31 @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/idea/src/META-INF/android.xml.as31 b/idea/src/META-INF/android.xml.as31 index fd7d903225a..86af22ef97a 100644 --- a/idea/src/META-INF/android.xml.as31 +++ b/idea/src/META-INF/android.xml.as31 @@ -102,6 +102,10 @@ + + + + diff --git a/idea/testData/android/lint/alarm.kt.as31 b/idea/testData/android/lint/alarm.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/apiCheck.kt.as31 b/idea/testData/android/lint/apiCheck.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/callSuper.kt.as31 b/idea/testData/android/lint/callSuper.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/closeCursor.kt.as31 b/idea/testData/android/lint/closeCursor.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/commitFragment.kt.as31 b/idea/testData/android/lint/commitFragment.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/javaPerformance.kt.as31 b/idea/testData/android/lint/javaPerformance.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/javaScriptInterface.kt.as31 b/idea/testData/android/lint/javaScriptInterface.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/layoutInflation.kt.as31 b/idea/testData/android/lint/layoutInflation.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/log.kt.as31 b/idea/testData/android/lint/log.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/noInternationalSms.kt.as31 b/idea/testData/android/lint/noInternationalSms.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/overrideConcrete.kt.as31 b/idea/testData/android/lint/overrideConcrete.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/parcel.kt.as31 b/idea/testData/android/lint/parcel.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/sdCardTest.kt.as31 b/idea/testData/android/lint/sdCardTest.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/setJavaScriptEnabled.kt.as31 b/idea/testData/android/lint/setJavaScriptEnabled.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/sharedPrefs.kt.as31 b/idea/testData/android/lint/sharedPrefs.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/showDiagnosticsWhenFileIsRed.kt.as31 b/idea/testData/android/lint/showDiagnosticsWhenFileIsRed.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/sqlite.kt.as31 b/idea/testData/android/lint/sqlite.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/systemServices.kt.as31 b/idea/testData/android/lint/systemServices.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/toast.kt.as31 b/idea/testData/android/lint/toast.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/valueOf.kt.as31 b/idea/testData/android/lint/valueOf.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/velocityTrackerRecycle.kt.as31 b/idea/testData/android/lint/velocityTrackerRecycle.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/viewConstructor.kt.as31 b/idea/testData/android/lint/viewConstructor.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/viewHolder.kt.as31 b/idea/testData/android/lint/viewHolder.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/wrongAnnotation.kt.as31 b/idea/testData/android/lint/wrongAnnotation.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/wrongImport.kt.as31 b/idea/testData/android/lint/wrongImport.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lint/wrongViewCall.kt.as31 b/idea/testData/android/lint/wrongViewCall.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt.as31 b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt.as31 new file mode 100644 index 00000000000..5323b6d44bf --- /dev/null +++ b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt.as31 @@ -0,0 +1,14 @@ +// INTENTION_TEXT: Add Parcelable Implementation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintParcelCreatorInspection +import android.os.Parcel +import android.os.Parcelable + +class MissingCreator : Parcelable { + override fun writeToParcel(dest: Parcel?, flags: Int) { + TODO("not implemented") + } + + override fun describeContents(): Int { + TODO("not implemented") + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt.as31 b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt.as31 new file mode 100644 index 00000000000..77379bdf08c --- /dev/null +++ b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt.as31 @@ -0,0 +1,5 @@ +// INTENTION_TEXT: Add Parcelable Implementation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintParcelCreatorInspection +import android.os.Parcelable + +class NoImplementation : Parcelable \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/annotation.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/annotation.kt.as31 new file mode 100644 index 00000000000..76db9697a88 --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/annotation.kt.as31 @@ -0,0 +1,12 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable +import kotlin.reflect.KClass + +annotation class SomeAnnotationWithClass(val cls: KClass<*>) + +@SomeAnnotationWithClass(VectorDrawable::class) +class VectorDrawableProvider { +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/companion.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/companion.kt.as31 new file mode 100644 index 00000000000..68568d40ab5 --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/companion.kt.as31 @@ -0,0 +1,11 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + companion object { + val VECTOR_DRAWABLE = VectorDrawable() + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/defaultParameter.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/defaultParameter.kt.as31 new file mode 100644 index 00000000000..cddbbe524a8 --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/defaultParameter.kt.as31 @@ -0,0 +1,10 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable + + +fun withDefaultParameter(vector: VectorDrawable = VectorDrawable()) { + +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/extend.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/extend.kt.as31 new file mode 100644 index 00000000000..233c746a212 --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/extend.kt.as31 @@ -0,0 +1,9 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable + +class MyVectorDrawable : VectorDrawable() { + +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/functionLiteral.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/functionLiteral.kt.as31 new file mode 100644 index 00000000000..00575be4e2c --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/functionLiteral.kt.as31 @@ -0,0 +1,13 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + fun getVectorDrawable(): VectorDrawable { + with(this) { + return VectorDrawable() + } + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/inlinedConstant.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/inlinedConstant.kt.as31 new file mode 100644 index 00000000000..d838f2b22fd --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/inlinedConstant.kt.as31 @@ -0,0 +1,9 @@ +// INTENTION_TEXT: Add @RequiresApi(KITKAT) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintInlinedApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +class Test { + fun foo(): Int { + return android.R.attr.windowTranslucentStatus + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/method.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/method.kt.as31 new file mode 100644 index 00000000000..14ccb785f24 --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/method.kt.as31 @@ -0,0 +1,11 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + fun getVectorDrawable(): VectorDrawable { + return VectorDrawable() + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/property.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/property.kt.as31 new file mode 100644 index 00000000000..fa17b13e2e6 --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/property.kt.as31 @@ -0,0 +1,9 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + val VECTOR_DRAWABLE = VectorDrawable() +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/requiresApi/when.kt.as31 b/idea/testData/android/lintQuickfix/requiresApi/when.kt.as31 new file mode 100644 index 00000000000..71997f75229 --- /dev/null +++ b/idea/testData/android/lintQuickfix/requiresApi/when.kt.as31 @@ -0,0 +1,15 @@ +// INTENTION_TEXT: Add @RequiresApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection +// DEPENDENCY: RequiresApi.java -> android/support/annotation/RequiresApi.java + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + val flag = false + fun getVectorDrawable(): VectorDrawable { + return when (flag) { + true -> VectorDrawable() + else -> VectorDrawable() + } + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/activityMethod.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/activityMethod.kt.as31 new file mode 100644 index 00000000000..7df0f390fd9 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/activityMethod.kt.as31 @@ -0,0 +1,10 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +import android.app.Activity +import android.os.Environment + + +class MainActivity : Activity() { + fun getSdCard(fromEnvironment: Boolean) = if (fromEnvironment) Environment.getExternalStorageDirectory().path else "/sdcard" +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/addToExistingAnnotation.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/addToExistingAnnotation.kt.as31 new file mode 100644 index 00000000000..ba329c02661 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/addToExistingAnnotation.kt.as31 @@ -0,0 +1,12 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +import android.annotation.SuppressLint +import android.app.Activity +import android.os.Environment + + +class MainActivity : Activity() { + @SuppressLint("Something") + fun getSdCard(fromEnvironment: Boolean) = if (fromEnvironment) Environment.getExternalStorageDirectory().path else "/sdcard" +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/constructorParameter.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/constructorParameter.kt.as31 new file mode 100644 index 00000000000..c9669dec932 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/constructorParameter.kt.as31 @@ -0,0 +1,4 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +class SdCard(val path: String = "/sdcard") \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/destructuringDeclaration.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/destructuringDeclaration.kt.as31 new file mode 100644 index 00000000000..0790fafd3f6 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/destructuringDeclaration.kt.as31 @@ -0,0 +1,9 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +fun foo() { + val (a: String, b: String) = "/sdcard" +} + +operator fun CharSequence.component1(): String = "component1" +operator fun CharSequence.component2(): String = "component2" diff --git a/idea/testData/android/lintQuickfix/suppressLint/lambdaArgument.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgument.kt.as31 new file mode 100644 index 00000000000..3e285608023 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgument.kt.as31 @@ -0,0 +1,10 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +fun foo(l: Any) = l + +fun bar() { + foo() { + "/sdcard" + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/lambdaArgumentProperty.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgumentProperty.kt.as31 new file mode 100644 index 00000000000..4e880f5fef6 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/lambdaArgumentProperty.kt.as31 @@ -0,0 +1,6 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +fun foo(l: Any) = l + +val bar = foo() { "/sdcard" } \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/methodParameter.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/methodParameter.kt.as31 new file mode 100644 index 00000000000..f9bc3212ec8 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/methodParameter.kt.as31 @@ -0,0 +1,4 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +fun foo(path: String = "/sdcard") = path \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/propertyWithLambda.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/propertyWithLambda.kt.as31 new file mode 100644 index 00000000000..529015f3f18 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/propertyWithLambda.kt.as31 @@ -0,0 +1,4 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +val getPath = { "/sdcard" } \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/suppressLint/simpleProperty.kt.as31 b/idea/testData/android/lintQuickfix/suppressLint/simpleProperty.kt.as31 new file mode 100644 index 00000000000..a9ff8da9c33 --- /dev/null +++ b/idea/testData/android/lintQuickfix/suppressLint/simpleProperty.kt.as31 @@ -0,0 +1,4 @@ +// INTENTION_TEXT: Suppress: Add @SuppressLint("SdCardPath") annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintSdCardPathInspection + +val path = "/sdcard" \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/annotation.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/annotation.kt.as31 new file mode 100644 index 00000000000..ceceadd4e83 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/annotation.kt.as31 @@ -0,0 +1,11 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable +import kotlin.reflect.KClass + +annotation class SomeAnnotationWithClass(val cls: KClass<*>) + +@SomeAnnotationWithClass(VectorDrawable::class) +class VectorDrawableProvider { +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/companion.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/companion.kt.as31 new file mode 100644 index 00000000000..6ecbaca7297 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/companion.kt.as31 @@ -0,0 +1,10 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + companion object { + val VECTOR_DRAWABLE = VectorDrawable() + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/defaultParameter.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/defaultParameter.kt.as31 new file mode 100644 index 00000000000..673405e5810 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/defaultParameter.kt.as31 @@ -0,0 +1,9 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + + +fun withDefaultParameter(vector: VectorDrawable = VectorDrawable()) { + +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/extend.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/extend.kt.as31 new file mode 100644 index 00000000000..0c26fe23373 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/extend.kt.as31 @@ -0,0 +1,8 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class MyVectorDrawable : VectorDrawable() { + +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/functionLiteral.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/functionLiteral.kt.as31 new file mode 100644 index 00000000000..a4c3c4117af --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/functionLiteral.kt.as31 @@ -0,0 +1,12 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + fun getVectorDrawable(): VectorDrawable { + with(this) { + return VectorDrawable() + } + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/inlinedConstant.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/inlinedConstant.kt.as31 new file mode 100644 index 00000000000..45fb5d2be16 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/inlinedConstant.kt.as31 @@ -0,0 +1,8 @@ +// INTENTION_TEXT: Add @TargetApi(KITKAT) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintInlinedApiInspection + +class Test { + fun foo(): Int { + return android.R.attr.windowTranslucentStatus + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/method.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/method.kt.as31 new file mode 100644 index 00000000000..aeac2f99d03 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/method.kt.as31 @@ -0,0 +1,10 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + fun getVectorDrawable(): VectorDrawable { + return VectorDrawable() + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/property.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/property.kt.as31 new file mode 100644 index 00000000000..7418db232d1 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/property.kt.as31 @@ -0,0 +1,8 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + val VECTOR_DRAWABLE = VectorDrawable() +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetApi/when.kt.as31 b/idea/testData/android/lintQuickfix/targetApi/when.kt.as31 new file mode 100644 index 00000000000..df92ce5d786 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetApi/when.kt.as31 @@ -0,0 +1,14 @@ +// INTENTION_TEXT: Add @TargetApi(LOLLIPOP) Annotation +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + val flag = false + fun getVectorDrawable(): VectorDrawable { + return when (flag) { + true -> VectorDrawable() + else -> VectorDrawable() + } + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/annotation.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/annotation.kt.as31 new file mode 100644 index 00000000000..31fed0067bd --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/annotation.kt.as31 @@ -0,0 +1,12 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INTENTION_NOT_AVAILABLE +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable +import kotlin.reflect.KClass + +annotation class SomeAnnotationWithClass(val cls: KClass<*>) + +@SomeAnnotationWithClass(VectorDrawable::class) +class VectorDrawableProvider { +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/defaultParameter.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/defaultParameter.kt.as31 new file mode 100644 index 00000000000..f448c22496f --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/defaultParameter.kt.as31 @@ -0,0 +1,10 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INTENTION_NOT_AVAILABLE +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + + +fun withDefaultParameter(vector: VectorDrawable = VectorDrawable()) { + +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/expressionBody.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/expressionBody.kt.as31 new file mode 100644 index 00000000000..dd8fe5b6d81 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/expressionBody.kt.as31 @@ -0,0 +1,8 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + fun getVectorDrawable(): VectorDrawable = VectorDrawable() +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/functionLiteral.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/functionLiteral.kt.as31 new file mode 100644 index 00000000000..17633e8f5a4 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/functionLiteral.kt.as31 @@ -0,0 +1,12 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + fun getVectorDrawable(): VectorDrawable { + with(this) { + return VectorDrawable() + } + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/if.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/if.kt.as31 new file mode 100644 index 00000000000..40876545dfd --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/if.kt.as31 @@ -0,0 +1,12 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + val flag = false + fun getVectorDrawable(): VectorDrawable { + if (flag) + return VectorDrawable() + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/ifWithBlock.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/ifWithBlock.kt.as31 new file mode 100644 index 00000000000..0e6ba0ff2c6 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/ifWithBlock.kt.as31 @@ -0,0 +1,13 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + val flag = false + fun getVectorDrawable(): VectorDrawable { + if (flag) { + return VectorDrawable() + } + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/inlinedConstant.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/inlinedConstant.kt.as31 new file mode 100644 index 00000000000..64281cde0ea --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/inlinedConstant.kt.as31 @@ -0,0 +1,8 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { ... } +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintInlinedApiInspection + +class Test { + fun foo(): Int { + return android.R.attr.windowTranslucentStatus + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/method.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/method.kt.as31 new file mode 100644 index 00000000000..f0ad689051a --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/method.kt.as31 @@ -0,0 +1,10 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + fun getVectorDrawable(): VectorDrawable { + return VectorDrawable() + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/targetVersionCheck/when.kt.as31 b/idea/testData/android/lintQuickfix/targetVersionCheck/when.kt.as31 new file mode 100644 index 00000000000..5a00d1882f5 --- /dev/null +++ b/idea/testData/android/lintQuickfix/targetVersionCheck/when.kt.as31 @@ -0,0 +1,14 @@ +// INTENTION_TEXT: Surround with if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { ... } +// INSPECTION_CLASS: org.jetbrains.android.inspections.lint.AndroidLintInspectionToolProvider$AndroidLintNewApiInspection + +import android.graphics.drawable.VectorDrawable + +class VectorDrawableProvider { + val flag = false + fun getVectorDrawable(): VectorDrawable { + return when (flag) { + true -> VectorDrawable() + else -> VectorDrawable() + } + } +} \ No newline at end of file diff --git a/idea/testData/multiFileInspections/kotlinInternalInJava/before/B/B.iml.as31 b/idea/testData/multiFileInspections/kotlinInternalInJava/before/B/B.iml.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/android-annotations/src/com/android/annotations/NonNull.java.as31 b/plugins/lint/android-annotations/src/com/android/annotations/NonNull.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/android-annotations/src/com/android/annotations/NonNullByDefault.java.as31 b/plugins/lint/android-annotations/src/com/android/annotations/NonNullByDefault.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/android-annotations/src/com/android/annotations/Nullable.java.as31 b/plugins/lint/android-annotations/src/com/android/annotations/Nullable.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/android-annotations/src/com/android/annotations/VisibleForTesting.java.as31 b/plugins/lint/android-annotations/src/com/android/annotations/VisibleForTesting.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/android-annotations/src/com/android/annotations/concurrency/GuardedBy.java.as31 b/plugins/lint/android-annotations/src/com/android/annotations/concurrency/GuardedBy.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/android-annotations/src/com/android/annotations/concurrency/Immutable.java.as31 b/plugins/lint/android-annotations/src/com/android/annotations/concurrency/Immutable.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AndroidReference.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AndroidReference.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AsmVisitor.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/AsmVisitor.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CircularDependencyException.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CircularDependencyException.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ClassEntry.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ClassEntry.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CompositeIssueRegistry.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/CompositeIssueRegistry.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/Configuration.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/Configuration.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultConfiguration.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultConfiguration.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultSdkInfo.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/DefaultSdkInfo.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ExternalReferenceExpression.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ExternalReferenceExpression.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/IssueRegistry.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/IssueRegistry.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JarFileIssueRegistry.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JarFileIssueRegistry.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaEvaluator.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaEvaluator.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaParser.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaParser.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaPsiVisitor.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaPsiVisitor.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaVisitor.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/JavaVisitor.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintClient.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintClient.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintDriver.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintDriver.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintListener.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintListener.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintRequest.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/LintRequest.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/OtherFileVisitor.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/OtherFileVisitor.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ResourceVisitor.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/ResourceVisitor.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/SdkInfo.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/SdkInfo.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UElementVisitor.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UElementVisitor.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/XmlParser.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/XmlParser.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Category.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Category.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ClassContext.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ClassContext.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ConstantEvaluator.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ConstantEvaluator.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Context.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Context.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/DefaultPosition.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/DefaultPosition.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Detector.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Detector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Implementation.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Implementation.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Issue.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Issue.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/JavaContext.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/JavaContext.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LayoutDetector.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LayoutDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LintUtils.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LintUtils.java.as31 index a0ced7f40e4..e69de29bb2d 100644 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LintUtils.java.as31 +++ b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/LintUtils.java.as31 @@ -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. - *

- * 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. - */ -@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 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 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 getChildren(@NonNull Node node) { - NodeList childNodes = node.getChildNodes(); - List children = new ArrayList(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 number 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. - *

- * 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 splitPath(@NonNull String path) { - if (path.indexOf(';') != -1) { - return Splitter.on(';').omitEmptyStrings().trimResults().split(path); - } - - List combined = new ArrayList(); - 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 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. - *

- * 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 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 result = null; - - Queue queue = new ArrayDeque(); - queue.add(new ResourceValue(ResourceUrl.create(style.type, style.name, false), null)); - Set 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 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 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 result = null; - - Queue queue = new ArrayDeque(); - queue.add(new ResourceValue(ResourceUrl.create(style.type, style.name, false), null)); - Set seen = Sets.newHashSet(); - int count = 0; - while (count < 30 && !queue.isEmpty()) { - ResourceValue front = queue.remove(); - String name = front.getName(); - seen.add(name); - List 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. - *

- * 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 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 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.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Location.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Position.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Position.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Project.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Project.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceContext.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceContext.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceEvaluator.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceEvaluator.java.as31 index 9b3368e9a47..e69de29bb2d 100644 --- a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceEvaluator.java.as31 +++ b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceEvaluator.java.as31 @@ -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 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 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 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 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 left = getResourceTypes( - expression.getThenExpression()); - EnumSet right = getResourceTypes( - expression.getElseExpression()); - if (left == null) { - return right; - } else if (right == null) { - return left; - } else { - EnumSet 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 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 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 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 left = getResourceTypes( - expression.getThenExpression()); - EnumSet right = getResourceTypes( - expression.getElseExpression()); - if (left == null) { - return right; - } else if (right == null) { - return left; - } else { - EnumSet 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 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 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 getAnyRes() { - EnumSet 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.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/ResourceXmlDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Scope.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Scope.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Severity.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Severity.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Speed.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/Speed.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TextFormat.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TextFormat.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TypeEvaluator.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/TypeEvaluator.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/XmlContext.java.as31 b/plugins/lint/lint-api/src/com/android/tools/klint/detector/api/XmlContext.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AddJavascriptInterfaceDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AddJavascriptInterfaceDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlarmDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlarmDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AllowAllHostnameVerifierDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AllowAllHostnameVerifierDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlwaysShowActionDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AlwaysShowActionDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AndroidAutoDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AndroidAutoDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/Api.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/Api.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiClass.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiClass.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiLookup.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiLookup.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiPackage.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiPackage.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiParser.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiParser.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppCompatCallDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppCompatCallDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppIndexingApiDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppIndexingApiDetector.java.as31 index 5989552db15..e69de29bb2d 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppIndexingApiDetector.java.as31 +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AppIndexingApiDetector.java.as31 @@ -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 getApplicableElements() { - return Collections.singletonList(NODE_APPLICATION); - } - - @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element application) { - List activities = extractChildrenByName(application, NODE_ACTIVITY); - boolean applicationHasActionView = false; - for (Element activity : activities) { - List 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 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 mStartMethods; - private final List mEndMethods; - private final List mConnectMethods; - private final List 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 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 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 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 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 getActivitiesToCheck(Context context) { - Set activitiesToCheck = Sets.newHashSet(); - List 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 children = LintUtils.getChildren(doc); - for (Element child : children) { - if (child.getNodeName().equals(NODE_MANIFEST)) { - List apps = extractChildrenByName(child, NODE_APPLICATION); - for (Element app : apps) { - List acts = extractChildrenByName(app, NODE_ACTIVITY); - for (Element act : acts) { - List intents = extractChildrenByName(act, NODE_INTENT); - for (Element intent : intents) { - List 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 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 - // ://:[||] - // 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 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 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 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 list) { - for (UCallExpression call : list) { - List 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 list) { - for (UCallExpression method : list) { - UElement operand2 = method.getReceiver(); - if (operand2 != null && operand.asSourceString().equals(operand2.asSourceString())) { - return true; - } - } - return false; - } - - private static List extractChildrenByName(@NonNull Element node, - @NonNull String name) { - List result = Lists.newArrayList(); - List 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.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BadHostnameVerifierDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BatteryDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BatteryDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CallSuperDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CallSuperDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CipherGetInstanceDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CipherGetInstanceDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CleanupDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CommentDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CommentDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ControlFlowGraph.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ControlFlowGraph.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CustomViewDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CustomViewDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CutPasteDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/CutPasteDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/DateFormatDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/DateFormatDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/FragmentDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/FragmentDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/GetSignaturesDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/GetSignaturesDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/HandlerDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/HandlerDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/IconDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/IconDetector.java.as31 index 12f6a267892..e69de29bb2d 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/IconDetector.java.as31 +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/IconDetector.java.as31 @@ -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 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 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 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 pixelSizes = null; - Map fileSizes = null; - if (checkDipSizes || checkDuplicates) { - pixelSizes = new HashMap(); - fileSizes = new HashMap(); - } - Map> folderToNames = new HashMap>(); - Map> nonDpiFolderNames = new HashMap>(); - 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 names = new HashSet(files.length); - for (File f : files) { - String name = f.getName(); - if (isDrawableFile(name)) { - names.add(name); - } - } - folderToNames.put(folder, names); - } else if (checkFolders) { - Set names = new HashSet(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 pixelSizes, - Map fileSizes) { - Map> sameSizes = new HashMap>(); - Map seenSizes = new HashMap(fileSizes.size()); - for (Map.Entry entry : fileSizes.entrySet()) { - File file = entry.getKey(); - Long size = entry.getValue(); - if (seenSizes.containsKey(size)) { - Set set = sameSizes.get(size); - if (set == null) { - set = new HashSet(); - 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> candidateLists = sameSizes.values(); - for (Set candidates : candidateLists) { - Map> sameDimensions = new HashMap>( - candidates.size()); - List noSize = new ArrayList(); - for (File file : candidates) { - Dimension dimension = pixelSizes.get(file); - if (dimension != null) { - Set set = sameDimensions.get(dimension); - if (set == null) { - set = new HashSet(); - sameDimensions.put(dimension, set); - } - set.add(file); - } else { - noSize.add(file); - } - } - - - // Files that we have no dimensions for must be compared against everything - Collection> sets = sameDimensions.values(); - if (!noSize.isEmpty()) { - if (!sets.isEmpty()) { - for (Set set : sets) { - set.addAll(noSize); - } - } else { - // Must just test the noSize elements against themselves - HashSet noSizeSet = new HashSet(noSize); - sets = Collections.>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 fileContents = new HashMap(); - - // 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 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 equal = new HashMap(); - - // Now go and compare all the files. This isn't an efficient algorithm - // but the number of candidates should be very small - - List files = new ArrayList(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> partitions = new HashMap>(); - List> sameSets = new ArrayList>(); - for (Map.Entry entry : equal.entrySet()) { - File file1 = entry.getKey(); - File file2 = entry.getValue(); - Set set1 = partitions.get(file1); - Set set2 = partitions.get(file2); - if (set1 != null) { - set1.add(file2); - } else if (set2 != null) { - set2.add(file1); - } else { - set = new HashSet(); - 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> lists = new ArrayList>(); - for (Set same : sameSets) { - assert !same.isEmpty(); - ArrayList sorted = new ArrayList(same); - Collections.sort(sorted); - lists.add(sorted); - } - // Sort overall partitions by the first item in each list - Collections.sort(lists, new Comparator>() { - @Override - public int compare(List list1, List 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_dp.png. (See issue 74584 for more.) - ListIterator> iterator = lists.listIterator(); - while (iterator.hasNext()) { - List 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 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 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> nameToFiles = new HashMap>(); - for (File file : pixelSizes.keySet()) { - String name = file.getName(); - List list = nameToFiles.get(name); - if (list == null) { - list = new ArrayList(); - nameToFiles.put(name, list); - } - list.add(file); - } - - ArrayList names = new ArrayList(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>> configMap = - new HashMap>>(); - for (Map.Entry> entry : nameToFiles.entrySet()) { - String name = entry.getKey(); - List 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> folderMap = configMap.get(name); - if (folderMap == null) { - folderMap = new HashMap>(); - configMap.put(name, folderMap); - } - // Map from name to a map from parent folder to files - List list = folderMap.get(parentName); - if (list == null) { - list = new ArrayList(); - folderMap.put(parentName, list); - } - list.add(file); - } - } - - for (String name : names) { - //List files = nameToFiles.get(name); - Map> 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> entry : configurations.entrySet()) { - List files = entry.getValue(); - - // Ensure that all the dip sizes are *roughly* the same - Map dipSizes = new HashMap(); - 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> entries = - new ArrayList>(); - for (Map.Entry entry2 : dipSizes.entrySet()) { - entries.add(entry2); - } - Collections.sort(entries, - new Comparator>() { - @Override - public int compare(Entry e1, - Entry 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 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> folderToNames, - Map> 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 definedDensities = new HashSet(); - 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 missing = new ArrayList(); - 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 noDpiNames = new HashSet(); - for (Map.Entry> 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 inBoth = new HashSet(); - List files = new ArrayList(); - for (Map.Entry> entry : folderToNames.entrySet()) { - File folder = entry.getKey(); - String folderName = folder.getName(); - if (!isNoDpiFolder(folder)) { - assert DENSITY_PATTERN.matcher(folderName).matches(); - Set overlap = nameIntersection(noDpiNames, entry.getValue()); - inBoth.addAll(overlap); - for (String name : overlap) { - files.add(new File(folder, name)); - } - } - } - - if (!inBoth.isEmpty()) { - List list = new ArrayList(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> folderMap = Maps.newHashMap(folderToNames); - folderMap.putAll(nonDpiFolderNames); - Set xmlNames = Sets.newHashSetWithExpectedSize(100); - Set bitmapNames = Sets.newHashSetWithExpectedSize(100); - - for (Map.Entry> entry : folderMap.entrySet()) { - Set 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 overlap = nameIntersection(xmlNames, bitmapNames); - if (!overlap.isEmpty()) { - Multimap map = ArrayListMultimap.create(); - Set bases = Sets.newHashSetWithExpectedSize(overlap.size()); - for (String name : overlap) { - bases.add(LintUtils.getBaseName(name)); - } - - for (String base : bases) { - for (Map.Entry> 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 sorted = new ArrayList(map.keySet()); - Collections.sort(sorted); - for (String name : sorted) { - List lists = Lists.newArrayList(map.get(name)); - Location location = chainLocations(lists); - - List 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 allNames = new HashSet(); - for (Entry> entry : folderToNames.entrySet()) { - if (!isNoDpiFolder(entry.getKey())) { - Set names = entry.getValue(); - allNames.addAll(names); - } - } - - for (Map.Entry> entry : folderToNames.entrySet()) { - File file = entry.getKey(); - if (isNoDpiFolder(file)) { - continue; - } - Set names = entry.getValue(); - if (names.size() != allNames.size()) { - List delta = new ArrayList(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 defined = new ArrayList(); - String name = delta.get(0); - for (Map.Entry> 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 getRequiredDensityFolders(@NonNull Context context) { - if (mCachedRequiredDensities == null - || context.getProject() != mCachedDensitiesForProject) { - mCachedDensitiesForProject = context.getProject(); - mCachedRequiredDensities = Lists.newArrayListWithExpectedSize(10); - - List 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 relevantDensities, - @Nullable List 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 without - * file extensions and return the result with.. - */ - private static Set nameDifferences(Set a, Set b) { - Set names1 = new HashSet(a.size()); - for (String s : a) { - names1.add(LintUtils.getBaseName(s)); - } - Set names2 = new HashSet(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 result = new HashSet(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 without - * file extensions and return the result with. - */ - private static Set nameIntersection(Set a, Set b) { - Set names1 = new HashSet(a.size()); - for (String s : a) { - names1.add(LintUtils.getBaseName(s)); - } - Set names2 = new HashSet(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 result = new HashSet(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 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 pixelSizes, Map 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 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> folderToNames) { - Set conflictSet = null; - - for (Entry> entry : folderToNames.entrySet()) { - Set baseNames = new HashSet(); - Set 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> conflicts = null; - for (Entry> entry : folderToNames.entrySet()) { - File dir = entry.getKey(); - Set 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 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 names = new ArrayList(conflicts.keySet()); - Collections.sort(names); - for (String name : names) { - List 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 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 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 mActionBarIcons; - private Set mNotificationIcons; - private Set mLauncherIcons; - private Multimap 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 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> getApplicableUastTypes() { - List> types = new ArrayList>(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 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 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 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.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaPerformanceDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaScriptInterfaceDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/JavaScriptInterfaceDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutConsistencyDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutConsistencyDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutInflationDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LayoutInflationDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LeakDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LeakDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LocaleDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LocaleDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LogDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/LogDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MathDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MathDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MergeRootFrameLayoutDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/MergeRootFrameLayoutDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/NonInternationalizedSmsDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/NonInternationalizedSmsDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverdrawDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverdrawDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverrideConcreteDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/OverrideConcreteDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ParcelDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionHolder.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionHolder.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PluralsDatabase.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PluralsDatabase.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PreferenceActivityDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PreferenceActivityDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PrivateResourceDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PrivateResourceDetector.java.as31 index cac223b63f1..e69de29bb2d 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PrivateResourceDetector.java.as31 +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PrivateResourceDetector.java.as31 @@ -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 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 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$ - // 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 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.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ReadParcelableDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RecyclerViewDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RecyclerViewDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RegistrationDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RegistrationDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RequiredAttributeDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RequiredAttributeDetector.java.as31 index 98305d27828..e69de29bb2d 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RequiredAttributeDetector.java.as31 +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RequiredAttributeDetector.java.as31 @@ -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 mStyleParents; - - /** Set of style names where the style sets the layout width */ - @Nullable private Set mWidthStyles; - - /** Set of style names where the style sets the layout height */ - @Nullable private Set mHeightStyles; - - /** Set of layout names for layouts that are included by an {@code } tag - * where the width is set on the include */ - @Nullable private Set mIncludedWidths; - - /** Set of layout names for layouts that are included by an {@code } tag - * where the height is set on the include */ - @Nullable private Set mIncludedHeights; - - /** Set of layout names for layouts that are included by an {@code } tag - * where the width is not set on the include */ - @Nullable private Set mNotIncludedWidths; - - /** Set of layout names for layouts that are included by an {@code } tag - * where the height is not set on the include */ - @Nullable private Set 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 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 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 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 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 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 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 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.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/RtlDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SQLiteDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SQLiteDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SdCardDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SdCardDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecureRandomDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecureRandomDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecurityDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SecurityDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ServiceCastDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ServiceCastDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetJavaScriptEnabledDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetJavaScriptEnabledDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetTextDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SetTextDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SslCertificateSocketFactoryDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SslCertificateSocketFactoryDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringAuthLeakDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringAuthLeakDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java.as31 index d2529c4aebb..e69de29bb2d 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java.as31 +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java.as31 @@ -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. - *

- * 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" + - " Try again in %d seconds.\n" + - "you should be using a plural:\n" + - " \n" + - " Try again in %d second\n" + - " Try again in %d seconds\n" + - " \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>> mFormatStrings; - - /** - * Map of strings that do not contain any formatting. - */ - private final Map mNotFormatStrings = new HashMap(); - - /** - * 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 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 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 - * Escaping apostrophes and quotes - */ - @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(); - } - 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>>(); - } - - List> list = mFormatStrings.get(name); - if (list == null) { - list = new ArrayList>(); - 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>> entry : mFormatStrings.entrySet()) { - String name = entry.getKey(); - List> list = entry.getValue(); - - // Check argument counts - if (checkCount) { - Handle notFormatted = mNotFormatStrings.get(name); - if (notFormatted != null) { - list = ImmutableList.>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> list) { - Map types = new HashMap(); - Map typeDefinition = new HashMap(); - for (Pair 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> list) { - // Check to make sure that the argument counts and types are consistent - int prevCount = -1; - for (Pair pair : list) { - Set indices = new HashSet(); - 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 all = new HashSet(); - for (int j = 1; j < count; j++) { - all.add(j); - } - all.removeAll(indices); - List sorted = new ArrayList(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 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 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 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 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> 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 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 reported = null; - for (Pair 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.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TrustAllX509TrustManagerDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TrustAllX509TrustManagerDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TypoLookup.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/TypoLookup.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeBroadcastReceiverDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeBroadcastReceiverDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeNativeCodeDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/UnsafeNativeCodeDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewConstructorDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewConstructorDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewHolderDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewHolderDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTagDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTagDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTypeDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTypeDetector.java.as31 index d29ca095bca..e69de29bb2d 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTypeDetector.java.as31 +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ViewTypeDetector.java.as31 @@ -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 - *

- * TODO: Check findFragmentById - *

- * ((ItemListFragment) getSupportFragmentManager()
- *   .findFragmentById(R.id.item_list))
- *   .setActivateOnItemClick(true);
- * 
- * Here we should check the {@code } 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 mIdToViewTag = new HashMap(50); - - @NonNull - @Override - public Speed getSpeed() { - return Speed.SLOW; - } - - @Override - public boolean appliesTo(@NonNull ResourceFolderType folderType) { - return folderType == ResourceFolderType.LAYOUT; - } - - @Override - public Collection 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 list = new ArrayList(2); - list.add((String) existing); - list.add(view); - mIdToViewTag.put(id, list); - } - } else if (existing instanceof List) { - @SuppressWarnings("unchecked") - List list = (List) existing; - if (!list.contains(view)) { - list.add(view); - } - } - } - } - - // ---- Implements Detector.JavaScanner ---- - - @Override - public List 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 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 items = resources.getResourceItem(ResourceType.ID, - id); - if (items != null && !items.isEmpty()) { - Set compatible = Sets.newHashSet(); - for (ResourceItem item : items) { - Collection tags = getViewTags(context, item); - if (tags != null) { - compatible.addAll(tags); - } - } - if (!compatible.isEmpty()) { - ArrayList 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 layoutTypes = (List) types; - checkCompatible(context, castType, null, layoutTypes, cast); - } - } - } - } - } - } - - @Nullable - protected Collection 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 map = getIdToTagsIn(context, file); - if (map != null) { - return map.get(item.getName()); - } - } - - return null; - } - - - private Map> mFileIdMap; - - @Nullable - private Multimap getIdToTagsIn(@NonNull Context context, @NonNull File file) { - if (!file.getPath().endsWith(DOT_XML)) { - return null; - } - if (mFileIdMap == null) { - mFileIdMap = Maps.newHashMap(); - } - Multimap 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 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 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.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongCallDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongImportDetector.java.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/WrongImportDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/api-versions-support-library.xml.as31 b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/api-versions-support-library.xml.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetApiQuickFix.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AddTargetVersionCheckQuickFix.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidInspectionExtensionsFactory.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidInspectionExtensionsFactory.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintExternalAnnotator.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintExternalAnnotator.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintGlobalInspectionContext.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintGlobalInspectionContext.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionBase.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionBase.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintQuickFix.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintQuickFix.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintUtil.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintUtil.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidQuickfixContexts.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidQuickfixContexts.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ApiUtils.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiConverter.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiConverter.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiParser.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/DomPsiParser.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IdeaJavaParser.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IdeaJavaParser.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java.as31 index b01989a5a37..e69de29bb2d 100644 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java.as31 +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintClient.java.as31 @@ -1,863 +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.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.*; - -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 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>> problemMap, - @NotNull AnalysisScope scope, - @NotNull List 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 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 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 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 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 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() { - @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 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.) - *

- * 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 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() { - @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 getJavaSourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) { - final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(myState.getModule()).getSourceRoots(false); - final List result = new ArrayList(sourceRoots.length); - - for (VirtualFile root : sourceRoots) { - result.add(new File(root.getPath())); - } - return result; - } - - @NonNull - @Override - public List 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>> myProblemMap; - private final AnalysisScope myScope; - private final List myIssues; - - public BatchLintClient(@NotNull Project project, - @NotNull Map>> problemMap, - @NotNull AnalysisScope scope, - @NotNull List issues) { - super(project); - myProblemMap = problemMap; - myScope = scope; - myIssues = issues; - } - - @Nullable - @Override - protected Module getModule() { - // No default module - return null; - } - - @NonNull - @Override - protected List 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> file2ProblemList = myProblemMap.get(issue); - if (file2ProblemList == null) { - file2ProblemList = new HashMap>(); - myProblemMap.put(issue, file2ProblemList); - } - - List problemList = file2ProblemList.get(file); - if (problemList == null) { - problemList = new ArrayList(); - 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 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 result = new ArrayList(sourceRoots.length); - - for (VirtualFile root : sourceRoots) { - result.add(new File(root.getPath())); - } - return result; - } - - @NonNull - @Override - public List getResourceFolders(@NonNull com.android.tools.klint.detector.api.Project project) { - final Module module = findModuleForLintProject(myProject, project); - if (module != null) { - AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet != null) { - return IntellijLintUtils.getResourceDirectories(facet); - } - } - return super.getResourceFolders(project); - } - } - - @Override - public boolean checkForSuppressComments() { - return false; - } - - @Override - public boolean supportsProjectResources() { - return true; - } - - @Nullable - @Override - public AbstractResourceRepository getProjectResources(com.android.tools.klint.detector.api.Project project, boolean includeDependencies) { - final Module module = findModuleForLintProject(myProject, project); - if (module != null) { - AndroidFacet facet = AndroidFacet.getInstance(module); - if (facet != null) { - return includeDependencies ? facet.getProjectResources(true) : facet.getModuleResources(true); - } - } - - return null; - } - - @Nullable - @Override - public URLConnection openConnection(@NonNull URL url) throws IOException { - return HttpConfigurable.getInstance().openConnection(url.toExternalForm()); - } - - @Override - public ClassLoader createUrlClassLoader(@NonNull URL[] urls, @NonNull ClassLoader parent) { - return UrlClassLoader.build().parent(parent).urls(urls).get(); - } - - @NonNull - @Override - public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) { - XmlTag tag = LocalResourceRepository.getItemTag(myProject, item); - if (tag != null) { - ResourceFile source = item.getSource(); - assert source != null : item; - return new LocationHandle(source.getFile(), tag); - } - return super.createResourceItemHandle(item); - } - - @NonNull - @Override - public ResourceVisibilityLookup.Provider getResourceVisibilityProvider() { - Module module = getModule(); - if (module != null) { - AppResourceRepository appResources = AppResourceRepository.getAppResources(module, true); - if (appResources != null) { - ResourceVisibilityLookup.Provider provider = appResources.getResourceVisibilityProvider(); - if (provider != null) { - return provider; - } - } - } - return super.getResourceVisibilityProvider(); - } - - private static class LocationHandle implements Location.Handle, Computable { - 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.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java.as31 index b81136262c0..e69de29bb2d 100644 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java.as31 +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintProject.java.as31 @@ -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 create(@NonNull IntellijLintClient client, @Nullable List files, @NonNull Module... modules) { - List projects = Lists.newArrayList(); - - Map projectMap = Maps.newHashMap(); - Map moduleMap = Maps.newHashMap(); - Map 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 roots = new HashSet(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 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 projectMap = Maps.newHashMap(); - if (project != null) { - project.setDirectLibraries(Collections.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.singletonList(project)); - } - } - } - } - client.setModuleMap(projectMap); - - //noinspection ConstantConditions - return Pair.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 graph = ApplicationManager.getApplication().runReadAction(new Computable>() { - @Override - public Graph 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 facets = Sets.newHashSet(); - HashSet 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 androidFacets, Set seen, Graph graph, Module module) { - Iterator 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 files, - @NonNull Map moduleMap, - @NonNull Map libraryMap, - @NonNull Map projectMap, - @NonNull List 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 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 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 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 files, @NonNull LintModuleProject project) { - if (files != null && !files.isEmpty()) { - ListIterator 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.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 files, - @NonNull Map libraryMap, - @NonNull List projects, - @NonNull AndroidFacet facet, - @NonNull AndroidModuleModel AndroidModuleModel, - @NonNull LintModuleProject project, - @NonNull Map projectMap, - @NonNull List dependencies) { - Collection 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 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 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 getJavaSourceFolders() { - if (mJavaSourceFolders == null) { - VirtualFile[] sourceRoots = ModuleRootManager.getInstance(myModule).getSourceRoots(false); - List dirs = new ArrayList(sourceRoots.length); - for (VirtualFile root : sourceRoots) { - dirs.add(new File(root.getPath())); - } - mJavaSourceFolders = dirs; - } - - return mJavaSourceFolders; - } - - @NonNull - @Override - public List getTestSourceFolders() { - if (mTestSourceFolders == null) { - ModuleRootManager manager = ModuleRootManager.getInstance(myModule); - VirtualFile[] sourceRoots = manager.getSourceRoots(false); - VirtualFile[] sourceAndTestRoots = manager.getSourceRoots(true); - List dirs = new ArrayList(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 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 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 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 getProguardFiles() { - if (mProguardFiles == null) { - final JpsAndroidModuleProperties properties = myFacet.getProperties(); - - if (properties.RUN_PROGUARD) { - final List urls = properties.myProGuardCfgFiles; - - if (!urls.isEmpty()) { - mProguardFiles = new ArrayList(); - - 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 getResourceFolders() { - if (mResourceFolders == null) { - List folders = myFacet.getResourceFolderManager().getFolders(); - List 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 getManifestFiles() { - if (mManifestFiles == null) { - mManifestFiles = Lists.newArrayList(); - File mainManifest = myFacet.getMainSourceProvider().getManifestFile(); - if (mainManifest.exists()) { - mManifestFiles.add(mainManifest); - } - - List 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 getAssetFolders() { - if (mAssetFolders == null) { - mAssetFolders = Lists.newArrayList(); - for (SourceProvider provider : IdeaSourceProvider.getAllSourceProviders(myFacet)) { - Collection 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 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 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 getJavaLibraries(boolean includeProvided) { - if (SUPPORT_CLASS_FILES) { - if (mJavaLibraries == null) { - if (myFacet.requiresAndroidModel() && myFacet.getAndroidModel() != null) { - Collection 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 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 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 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 getJavaSourceFolders() { - return Collections.emptyList(); - } - - @NonNull - @Override - public List getJavaClassFolders() { - return Collections.emptyList(); - } - - private static boolean sOptionalAvailable = true; - - @NonNull - @Override - public List 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.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintRequest.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijViewTypeDetector.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijViewTypeDetector.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/LintInspectionDescriptionLinkHandler.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/LintInspectionDescriptionLinkHandler.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ProblemData.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ProblemData.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/State.java.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/State.java.as31 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt.as31 b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/SuppressLintIntentionAction.kt.as31 new file mode 100644 index 00000000000..e69de29bb2d