as32: Fix fqname constant usages for AS 3.2 C3

This commit is contained in:
Vyacheslav Gerasimov
2018-02-15 16:37:42 +03:00
committed by Nikolay Krasko
parent 25e193546d
commit 054ae37ef4
3 changed files with 313 additions and 0 deletions
@@ -0,0 +1,138 @@
/*
* 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
import com.android.SdkConstants
import com.android.resources.ResourceType
import com.intellij.codeHighlighting.Pass
import com.intellij.codeInsight.daemon.GutterIconNavigationHandler
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.LineMarkerProvider
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.icons.AllIcons
import com.intellij.ide.highlighter.XmlFileType
import com.intellij.navigation.GotoRelatedItem
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.xml.XmlAttributeValue
import com.intellij.ui.awt.RelativePoint
import com.intellij.util.Function
import org.jetbrains.android.dom.manifest.Manifest
import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.android.resourceManagers.ModuleResourceManagers
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import java.awt.event.MouseEvent
import javax.swing.Icon
class KotlinAndroidLineMarkerProvider : LineMarkerProvider {
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? = null
override fun collectSlowLineMarkers(elements: List<PsiElement>, result: MutableCollection<LineMarkerInfo<PsiElement>>) {
elements.forEach {
(it as? KtClass)?.getLineMarkerInfo()?.let { marker -> result.add(marker) }
}
}
private fun KtClass.getLineMarkerInfo(): LineMarkerInfo<PsiElement>? {
val nameIdentifier = nameIdentifier ?: return null
val androidFacet = AndroidFacet.getInstance(this) ?: return null
val manifest = androidFacet.manifest ?: return null
val manifestItems = collectGoToRelatedManifestItems(manifest)
if (manifestItems.isEmpty() && !isClassWithLayoutXml()) {
return null
}
return LineMarkerInfo(
nameIdentifier,
nameIdentifier.textRange,
AllIcons.FileTypes.Xml,
Pass.LINE_MARKERS,
Function { "Related XML file" },
GutterIconNavigationHandler { e: MouseEvent, _: PsiElement ->
NavigationUtil
.getRelatedItemsPopup(
manifestItems + collectGoToRelatedLayoutItems(androidFacet),
"Go to Related Files")
.show(RelativePoint(e))
},
GutterIconRenderer.Alignment.RIGHT)
}
private fun KtClass.collectGoToRelatedLayoutItems(androidFacet: AndroidFacet): List<GotoRelatedItem> {
val resources = mutableSetOf<PsiFile>()
accept(object: KtVisitorVoid() {
override fun visitKtElement(element: KtElement) {
element.acceptChildren(this)
}
override fun visitReferenceExpression(expression: KtReferenceExpression) {
super.visitReferenceExpression(expression)
val resClassName = ResourceType.LAYOUT.getName()
val info = (expression as? KtSimpleNameExpression)?.let {
getReferredResourceOrManifestField(androidFacet, it, resClassName, true)
}
if (info == null || info.isFromManifest) {
return
}
val files = ModuleResourceManagers
.getInstance(androidFacet)
.localResourceManager
.findResourcesByFieldName(resClassName, info.fieldName)
.filterIsInstance<PsiFile>()
resources.addAll(files)
}
})
return resources.map { GotoRelatedLayoutItem(it) }
}
private fun KtClass.collectGoToRelatedManifestItems(manifest: Manifest): List<GotoRelatedItem> =
findComponentDeclarationInManifest(manifest)?.xmlAttributeValue?.let { listOf(GotoManifestItem(it)) } ?: emptyList()
private class GotoManifestItem(attributeValue: XmlAttributeValue) : GotoRelatedItem(attributeValue) {
override fun getCustomName(): String? = "AndroidManifest.xml"
override fun getCustomContainerName(): String? = ""
override fun getCustomIcon(): Icon? = XmlFileType.INSTANCE.icon
}
private class GotoRelatedLayoutItem(private val file: PsiFile) : GotoRelatedItem(file, "Layout Files") {
override fun getCustomContainerName(): String? = "(${file.containingDirectory.name})"
}
companion object {
private val CLASSES_WITH_LAYOUT_XML = arrayOf(
SdkConstants.CLASS_ACTIVITY,
SdkConstants.CLASS_FRAGMENT,
SdkConstants.CLASS_V4_FRAGMENT.oldName(),
SdkConstants.CLASS_V4_FRAGMENT.newName(),
"android.widget.Adapter")
private fun KtClass.isClassWithLayoutXml(): Boolean {
val type = (unsafeResolveToDescriptor(BodyResolveMode.PARTIAL) as? ClassDescriptor)?.defaultType ?: return false
return CLASSES_WITH_LAYOUT_XML.any { type.isSubclassOf(it, true) }
}
}
}
@@ -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.defaultName())
}
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
}
}
@@ -0,0 +1,80 @@
/*
* 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.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
import java.util.regex.Pattern
class KotlinAndroidQuickFixProvider : AndroidLintQuickFixProvider {
override fun getQuickFixes(
issue: Issue,
startElement: PsiElement,
endElement: PsiElement,
message: String,
data: Any?
): Array<AndroidLintQuickFix> {
val fixes: Array<AndroidLintQuickFix> = when (issue) {
ApiDetector.UNSUPPORTED, ApiDetector.INLINED -> getApiQuickFixes(issue, startElement, message)
ParcelDetector.ISSUE -> arrayOf(ParcelableQuickFix())
else -> emptyArray()
}
if (issue != CommentDetector.STOP_SHIP) {
return fixes + SuppressLintQuickFix(issue.id)
}
return fixes
}
fun getApiQuickFixes(issue: Issue, element: PsiElement, message: String): Array<AndroidLintQuickFix> {
val api = getRequiredVersion(message)
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))
}
private fun getRequiredVersion(errorMessage: String): Int {
val pattern = Pattern.compile("\\s(\\d+)\\s")
val matcher = pattern.matcher(errorMessage)
if (matcher.find()) {
return Integer.parseInt(matcher.group(1))
}
return -1
}
companion object {
val REQUIRES_API_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX.defaultName() + "RequiresApi"
}
}