| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tools.lint.helpers |
| |
| import com.android.builder.model.Dependencies |
| import com.android.tools.lint.client.api.JavaEvaluator |
| import com.android.tools.lint.detector.api.Project |
| import com.android.tools.lint.detector.api.computeKotlinArgumentMapping |
| import com.android.tools.lint.detector.api.isKotlin |
| import com.google.common.collect.Sets |
| import com.intellij.codeInsight.AnnotationUtil |
| import com.intellij.psi.JavaDirectoryService |
| import com.intellij.psi.JavaPsiFacade |
| import com.intellij.psi.PsiAnnotation |
| import com.intellij.psi.PsiClass |
| import com.intellij.psi.PsiClassType |
| import com.intellij.psi.PsiCompiledFile |
| import com.intellij.psi.PsiElement |
| import com.intellij.psi.PsiFile |
| import com.intellij.psi.PsiJavaFile |
| import com.intellij.psi.PsiMethod |
| import com.intellij.psi.PsiModifierList |
| import com.intellij.psi.PsiModifierListOwner |
| import com.intellij.psi.PsiPackage |
| import com.intellij.psi.PsiParameter |
| import com.intellij.psi.PsiType |
| import com.intellij.psi.impl.file.PsiPackageImpl |
| import com.intellij.psi.impl.source.tree.java.PsiCompositeModifierList |
| import com.intellij.psi.search.GlobalSearchScope |
| import com.intellij.psi.util.InheritanceUtil |
| import com.intellij.psi.util.MethodSignatureUtil |
| import com.intellij.psi.util.TypeConversionUtil |
| import com.intellij.util.io.URLUtil |
| import org.jetbrains.uast.UAnnotated |
| import org.jetbrains.uast.UAnnotation |
| import org.jetbrains.uast.UCallExpression |
| import org.jetbrains.uast.UDeclaration |
| import org.jetbrains.uast.UElement |
| import org.jetbrains.uast.UExpression |
| import org.jetbrains.uast.getContainingUFile |
| import org.jetbrains.uast.java.JavaUAnnotation |
| |
| open class DefaultJavaEvaluator( |
| private val myProject: com.intellij.openapi.project.Project?, |
| private val myLintProject: Project? |
| ) : JavaEvaluator() { |
| // cache of package name to package-info.class. |
| private val packageInfoCache = mutableMapOf<String, PsiPackage>() |
| |
| override val dependencies: Dependencies? |
| get() { |
| if (myLintProject != null && myLintProject.isAndroidProject) { |
| val variant = myLintProject.currentVariant |
| if (variant != null) { |
| return variant.mainArtifact.dependencies |
| } |
| } |
| return null |
| } |
| |
| override fun extendsClass(cls: PsiClass?, className: String, strict: Boolean): Boolean { |
| // TODO: This checks interfaces too. Let's find a cheaper method which only checks direct super classes! |
| return InheritanceUtil.isInheritor(cls, strict, className) |
| } |
| |
| override fun implementsInterface( |
| cls: PsiClass, |
| interfaceName: String, |
| strict: Boolean |
| ): Boolean { |
| // TODO: This checks superclasses too. Let's find a cheaper method which only checks interfaces. |
| return InheritanceUtil.isInheritor(cls, strict, interfaceName) |
| } |
| |
| override fun inheritsFrom(cls: PsiClass, className: String, strict: Boolean): Boolean { |
| return InheritanceUtil.isInheritor(cls, strict, className) |
| } |
| |
| override fun findClass(qualifiedName: String): PsiClass? { |
| return JavaPsiFacade.getInstance(myProject ?: return null).findClass( |
| qualifiedName, |
| GlobalSearchScope.allScope(myProject) |
| ) |
| } |
| |
| override fun getClassType(psiClass: PsiClass?): PsiClassType? { |
| return if (myProject != null && psiClass != null) |
| JavaPsiFacade.getElementFactory(myProject).createType(psiClass) |
| else null |
| } |
| |
| override fun getAllAnnotations( |
| owner: UAnnotated, |
| inHierarchy: Boolean |
| ): List<UAnnotation> { |
| if (owner is UDeclaration) { |
| // Going to PSI means we drop vital context from Kotlin annotations. |
| // Therefore, call into UAST to get the full Kotlin annotations, but also |
| // merge in external annotations and inherited annotations from the class |
| // files, and pick unique. |
| val annotations = owner.annotations |
| val psiAnnotations = getAllAnnotations(owner.psi, inHierarchy) |
| |
| if (!annotations.isEmpty()) { |
| if (psiAnnotations.isEmpty()) { |
| return annotations |
| } |
| |
| // Filter with preference given to the builtins |
| val result = mutableListOf<UAnnotation>() |
| for (psi in psiAnnotations) { |
| val signature = psi.qualifiedName |
| var handled = false |
| for (ua in annotations) { |
| if (ua.qualifiedName == signature) { |
| result.add(ua) |
| handled = true |
| break |
| } |
| } |
| if (!handled) { |
| result.add(JavaUAnnotation.wrap(psi)) |
| } |
| } |
| |
| return result |
| } |
| |
| // Work around bug: Passing in a UAST node to this method generates a |
| // "class JavaUParameter not found among parameters: [PsiParameter:something]" error |
| return JavaUAnnotation.wrap(psiAnnotations) |
| } |
| |
| return owner.annotations |
| } |
| |
| override fun getAllAnnotations( |
| owner: PsiModifierListOwner, |
| inHierarchy: Boolean |
| ): Array<PsiAnnotation> { |
| if (owner is UDeclaration) { |
| // Work around bug: Passing in a UAST node to this method generates a |
| // "class JavaUParameter not found among parameters: [PsiParameter:something]" error |
| return getAllAnnotations(owner.psi, inHierarchy) |
| } |
| |
| // withInferred=false when running outside the IDE: we don't have |
| // an InferredAnnotationsManager |
| return AnnotationUtil.getAllAnnotations(owner, inHierarchy, null, false) |
| } |
| |
| override fun findAnnotationInHierarchy( |
| listOwner: PsiModifierListOwner, |
| vararg annotationNames: String |
| ): PsiAnnotation? { |
| if (listOwner is UDeclaration) { |
| // Work around UAST bug |
| return findAnnotationInHierarchy(listOwner.psi, *annotationNames) |
| } |
| return AnnotationUtil.findAnnotationInHierarchy( |
| listOwner, |
| Sets.newHashSet(*annotationNames) |
| ) |
| } |
| |
| override fun findAnnotation( |
| listOwner: PsiModifierListOwner?, |
| vararg annotationNames: String |
| ): PsiAnnotation? { |
| if (listOwner is UDeclaration) { |
| // Work around UAST bug |
| return findAnnotation(listOwner.psi, *annotationNames) |
| } |
| return AnnotationUtil.findAnnotation(listOwner, false, *annotationNames) |
| } |
| |
| override fun areSignaturesEqual(method1: PsiMethod, method2: PsiMethod): Boolean { |
| return MethodSignatureUtil.areSignaturesEqual(method1, method2) |
| } |
| |
| override fun findJarPath(element: PsiElement): String? { |
| val containingFile = element.containingFile |
| @Suppress("USELESS_CAST") |
| return findJarPath(containingFile as PsiFile?) |
| } |
| |
| override fun findJarPath(element: UElement): String? { |
| val uFile = element.getContainingUFile() |
| return if (uFile != null) findJarPath(uFile.psi as PsiFile?) else null |
| } |
| |
| private fun findJarPath(containingFile: PsiFile?): String? { |
| if (containingFile is PsiCompiledFile) { |
| // This code is roughly similar to the following: |
| // VirtualFile jarVirtualFile = PsiUtil.getJarFile(containingFile); |
| // if (jarVirtualFile != null) { |
| // return jarVirtualFile.getPath(); |
| // } |
| // However, the above methods will do some extra string manipulation and |
| // VirtualFile lookup which we don't actually need (we're just after the |
| // raw URL suffix) |
| val file = containingFile.virtualFile |
| if (file != null && file.fileSystem.protocol == URLUtil.JAR_PROTOCOL) { |
| val path = file.path |
| val separatorIndex = path.indexOf(URLUtil.JAR_SEPARATOR) |
| if (separatorIndex >= 0) { |
| return path.substring(0, separatorIndex) |
| } |
| } |
| } |
| |
| return null |
| } |
| |
| override fun getPackage(node: PsiElement): PsiPackage? { |
| val containingFile = node as? PsiFile ?: node.containingFile |
| if (containingFile != null) { |
| // Optimization: JavaDirectoryService can be slow so try to compute it directly |
| if (containingFile is PsiJavaFile) { |
| return packageInfoCache.computeIfAbsent(containingFile.packageName) { name -> |
| val cls = findClass(name + '.' + PsiPackage.PACKAGE_INFO_CLASS) |
| val modifierList = cls?.modifierList |
| object : PsiPackageImpl(node.manager, name) { |
| override fun getAnnotationList(): PsiModifierList? { |
| return if (modifierList != null) { |
| // Use composite even if we just have one such that we don't |
| // pass a modifier list tied to source elements in the class |
| // (modifier lists can be part of the AST) |
| PsiCompositeModifierList( |
| manager, |
| listOf(modifierList) |
| ) |
| } else null |
| } |
| } |
| } |
| } |
| |
| val dir = containingFile.parent |
| if (dir != null) { |
| return JavaDirectoryService.getInstance().getPackage(dir) |
| } |
| } |
| return null |
| } |
| |
| override fun getPackage(node: UElement): PsiPackage? { |
| val uFile = node.getContainingUFile() |
| return if (uFile != null) { |
| getPackage(uFile.psi) |
| } else null |
| } |
| |
| override fun getQualifiedName(psiClassType: PsiClassType): String? { |
| val erased = erasure(psiClassType) |
| return if (erased is PsiClassType) { |
| super.getQualifiedName((erased as PsiClassType?)!!) |
| } else super.getQualifiedName(psiClassType) |
| } |
| |
| override fun getQualifiedName(psiClass: PsiClass): String? { |
| return psiClass.qualifiedName |
| } |
| |
| @Suppress("OverridingDeprecatedMember", "DEPRECATION") |
| override fun getInternalName(psiClassType: PsiClassType): String? { |
| val erased = erasure(psiClassType) |
| return if (erased is PsiClassType) { |
| super.getInternalName((erased as PsiClassType?)!!) |
| } else super.getInternalName(psiClassType) |
| } |
| |
| @Suppress("OverridingDeprecatedMember") |
| override fun getInternalName(psiClass: PsiClass): String? { |
| return com.android.tools.lint.detector.api.getInternalName(psiClass) |
| } |
| |
| override fun erasure(type: PsiType?): PsiType? { |
| return TypeConversionUtil.erasure(type) |
| } |
| |
| override fun computeArgumentMapping(call: UCallExpression, method: PsiMethod): |
| Map<UExpression, PsiParameter> { |
| val parameterList = method.parameterList |
| if (parameterList.parametersCount == 0) { |
| return emptyMap() |
| } |
| |
| // Call into lint-kotlin to look up the argument mapping if this call is a Kotlin method. |
| val kotlinMap = computeKotlinArgumentMapping(call, method) |
| if (kotlinMap != null) { |
| return kotlinMap |
| } |
| |
| val arguments = call.valueArguments |
| val parameters = parameterList.parameters |
| |
| var j = 0 |
| if (parameters.isNotEmpty() && "\$receiver" == parameters[0].name && |
| isKotlin(call.sourcePsi) |
| ) { |
| // Kotlin extension method. |
| j++ |
| } |
| |
| var i = 0 |
| val n = Math.min(parameters.size, arguments.size) |
| val map = HashMap<UExpression, PsiParameter>(2 * n) |
| |
| /* Here is a UAST supported way to compute this, but it doesn't handle |
| varargs well (some unit tests fail showing the particular scenarios) |
| if (call is UCallExpressionEx) { |
| for (index in 0 until parameters.size) { |
| val argument = call.getArgumentForParameter(index) ?: continue |
| val parameter = parameters[index] |
| map[argument] = parameter |
| } |
| |
| return map |
| } |
| */ |
| |
| while (j < n) { |
| val argument = arguments[i] |
| val parameter = parameters[j] |
| map[argument] = parameter |
| i++ |
| j++ |
| } |
| |
| if (i < arguments.size && j > 0) { |
| // last parameter is varargs (same parameter annotations) |
| j-- |
| while (i < arguments.size) { |
| val argument = arguments[i] |
| val parameter = parameters[j] |
| map[argument] = parameter |
| i++ |
| } |
| } |
| |
| return map |
| } |
| } |