Switch to 181 platform
This commit is contained in:
@@ -47,6 +47,7 @@ dependencies {
|
||||
testRuntime(project(":allopen-ide-plugin"))
|
||||
|
||||
testRuntime(intellijPluginDep("android"))
|
||||
testRuntime(intellijPluginDep("smali"))
|
||||
testRuntime(intellijPluginDep("copyright"))
|
||||
testRuntime(intellijPluginDep("coverage"))
|
||||
testRuntime(intellijPluginDep("gradle"))
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testRuntime(intellijDep())
|
||||
|
||||
compileOnly(project(":kotlin-reflect-api"))
|
||||
compile(project(":compiler:util"))
|
||||
compile(project(":compiler:light-classes"))
|
||||
compile(project(":compiler:frontend"))
|
||||
compile(project(":compiler:frontend.java"))
|
||||
compile(project(":idea"))
|
||||
compile(project(":idea:idea-jvm"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:idea-gradle"))
|
||||
|
||||
compile(androidDxJar())
|
||||
|
||||
compileOnly(project(":kotlin-android-extensions-runtime"))
|
||||
compileOnly(intellijDep())
|
||||
compileOnly(intellijPluginDep("android"))
|
||||
|
||||
testCompile(projectDist(":kotlin-test:kotlin-test-jvm"))
|
||||
testCompile(projectTests(":idea:idea-test-framework")) { isTransitive = false }
|
||||
testCompile(project(":plugins:lint")) { isTransitive = false }
|
||||
testCompile(project(":idea:idea-jvm"))
|
||||
testCompile(projectTests(":compiler:tests-common"))
|
||||
testCompile(projectTests(":idea"))
|
||||
testCompile(projectTests(":idea:idea-gradle"))
|
||||
testCompile(commonDep("junit:junit"))
|
||||
|
||||
testCompile(intellijDep())
|
||||
testCompile(intellijPluginDep("properties"))
|
||||
testCompileOnly(intellijPluginDep("android"))
|
||||
|
||||
testRuntime(projectDist(":kotlin-reflect"))
|
||||
testRuntime(project(":plugins:android-extensions-ide"))
|
||||
testRuntime(project(":plugins:kapt3-idea"))
|
||||
testRuntime(project(":sam-with-receiver-ide-plugin"))
|
||||
testRuntime(project(":noarg-ide-plugin"))
|
||||
testRuntime(project(":allopen-ide-plugin"))
|
||||
|
||||
testRuntime(intellijPluginDep("android"))
|
||||
testRuntime(intellijPluginDep("copyright"))
|
||||
testRuntime(intellijPluginDep("coverage"))
|
||||
testRuntime(intellijPluginDep("gradle"))
|
||||
testRuntime(intellijPluginDep("Groovy"))
|
||||
testRuntime(intellijPluginDep("IntelliLang"))
|
||||
testRuntime(intellijPluginDep("java-decompiler"))
|
||||
testRuntime(intellijPluginDep("java-i18n"))
|
||||
testRuntime(intellijPluginDep("junit"))
|
||||
testRuntime(intellijPluginDep("maven"))
|
||||
testRuntime(intellijPluginDep("testng"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
projectTest {
|
||||
workingDir = rootDir
|
||||
useAndroidSdk()
|
||||
}
|
||||
|
||||
testsJar {}
|
||||
|
||||
+3
-1
@@ -34,6 +34,7 @@ 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.*
|
||||
@@ -95,7 +96,8 @@ class KotlinAndroidLineMarkerProvider : LineMarkerProvider {
|
||||
return
|
||||
}
|
||||
|
||||
val files = androidFacet
|
||||
val files = ModuleResourceManagers
|
||||
.getInstance(androidFacet)
|
||||
.localResourceManager
|
||||
.findResourcesByFieldName(resClassName, info.fieldName)
|
||||
.filterIsInstance<PsiFile>()
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.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 = 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,
|
||||
"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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-23
@@ -23,8 +23,8 @@ import com.android.ide.common.resources.ResourceRepository;
|
||||
import com.android.ide.common.resources.ResourceResolver;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.android.tools.idea.configurations.Configuration;
|
||||
import com.android.tools.idea.configurations.ConfigurationManager;
|
||||
import com.android.tools.idea.res.AppResourceRepository;
|
||||
import com.android.tools.idea.res.LocalResourceRepository;
|
||||
import com.android.tools.idea.res.ResourceHelper;
|
||||
import com.android.tools.idea.ui.resourcechooser.ColorPicker;
|
||||
import com.android.utils.XmlUtils;
|
||||
@@ -61,15 +61,14 @@ import java.awt.*;
|
||||
import java.io.File;
|
||||
|
||||
import static com.android.SdkConstants.*;
|
||||
import static com.android.SdkConstants.ANDROID_URI;
|
||||
import static com.android.SdkConstants.ATTR_DRAWABLE;
|
||||
import static com.android.tools.idea.uibuilder.property.renderer.NlDefaultRenderer.ICON_SIZE;
|
||||
import static org.jetbrains.android.AndroidColorAnnotator.pickLayoutFile;
|
||||
|
||||
/**
|
||||
* Contains copied privates from AndroidColorAnnotator, so we could use them for Kotlin AndroidResourceReferenceAnnotator
|
||||
*/
|
||||
public class ResourceReferenceAnnotatorUtil {
|
||||
|
||||
public static final int ICON_SIZE = 8;
|
||||
|
||||
@Nullable
|
||||
public static File pickBitmapFromXml(@NotNull File file, @NotNull ResourceResolver resourceResolver, @NotNull Project project) {
|
||||
try {
|
||||
@@ -146,7 +145,7 @@ public class ResourceReferenceAnnotatorUtil {
|
||||
ResourceItem item = frameworkResources.getResourceItem(type, name);
|
||||
return item.getResourceValue(type, configuration.getFullConfig(), false);
|
||||
} else {
|
||||
LocalResourceRepository appResources = AppResourceRepository.getAppResources(module, true);
|
||||
AppResourceRepository appResources = AppResourceRepository.getOrCreateInstance(module);
|
||||
if (appResources == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -161,26 +160,27 @@ public class ResourceReferenceAnnotatorUtil {
|
||||
@Nullable
|
||||
public static Configuration pickConfiguration(AndroidFacet facet, Module module, PsiFile file) {
|
||||
VirtualFile virtualFile = file.getVirtualFile();
|
||||
if (virtualFile == null) {
|
||||
if(virtualFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
VirtualFile parent = virtualFile.getParent();
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
VirtualFile layout;
|
||||
String parentName = parent.getName();
|
||||
if (!parentName.startsWith(FD_RES_LAYOUT)) {
|
||||
layout = pickLayoutFile(module, facet);
|
||||
if (layout == null) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
layout = virtualFile;
|
||||
}
|
||||
VirtualFile parent = virtualFile.getParent();
|
||||
if(parent == null) {
|
||||
return null;
|
||||
} else {
|
||||
String parentName = parent.getName();
|
||||
VirtualFile layout;
|
||||
if(!parentName.startsWith("layout")) {
|
||||
layout = ResourceHelper.pickAnyLayoutFile(module, facet);
|
||||
if(layout == null) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
layout = virtualFile;
|
||||
}
|
||||
|
||||
return facet.getConfigurationManager().getConfiguration(layout);
|
||||
return ConfigurationManager.getOrCreateInstance(module).getConfiguration(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ColorRenderer extends GutterIconRenderer {
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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.ide.common.rendering.api.ResourceValue;
|
||||
import com.android.ide.common.resources.ResourceItem;
|
||||
import com.android.ide.common.resources.ResourceRepository;
|
||||
import com.android.ide.common.resources.ResourceResolver;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.android.tools.idea.configurations.Configuration;
|
||||
import com.android.tools.idea.res.AppResourceRepository;
|
||||
import com.android.tools.idea.res.LocalResourceRepository;
|
||||
import com.android.tools.idea.res.ResourceHelper;
|
||||
import com.android.tools.idea.ui.resourcechooser.ColorPicker;
|
||||
import com.android.utils.XmlUtils;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.Files;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.markup.GutterIconRenderer;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.xml.XmlAttribute;
|
||||
import com.intellij.psi.xml.XmlAttributeValue;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ui.ColorIcon;
|
||||
import com.intellij.util.ui.EmptyIcon;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
|
||||
import static com.android.SdkConstants.*;
|
||||
import static com.android.SdkConstants.ANDROID_URI;
|
||||
import static com.android.SdkConstants.ATTR_DRAWABLE;
|
||||
import static com.android.tools.idea.uibuilder.property.renderer.NlDefaultRenderer.ICON_SIZE;
|
||||
import static org.jetbrains.android.AndroidColorAnnotator.pickLayoutFile;
|
||||
|
||||
/**
|
||||
* Contains copied privates from AndroidColorAnnotator, so we could use them for Kotlin AndroidResourceReferenceAnnotator
|
||||
*/
|
||||
public class ResourceReferenceAnnotatorUtil {
|
||||
@Nullable
|
||||
public static File pickBitmapFromXml(@NotNull File file, @NotNull ResourceResolver resourceResolver, @NotNull Project project) {
|
||||
try {
|
||||
String xml = Files.toString(file, Charsets.UTF_8);
|
||||
Document document = XmlUtils.parseDocumentSilently(xml, true);
|
||||
if (document != null && document.getDocumentElement() != null) {
|
||||
Element root = document.getDocumentElement();
|
||||
String tag = root.getTagName();
|
||||
Element target = null;
|
||||
String attribute = null;
|
||||
if ("vector".equals(tag)) {
|
||||
// Vectors are handled in the icon cache
|
||||
return file;
|
||||
}
|
||||
else if ("bitmap".equals(tag) || "nine-patch".equals(tag)) {
|
||||
target = root;
|
||||
attribute = ATTR_SRC;
|
||||
}
|
||||
else if ("selector".equals(tag) ||
|
||||
"level-list".equals(tag) ||
|
||||
"layer-list".equals(tag) ||
|
||||
"transition".equals(tag)) {
|
||||
NodeList children = root.getChildNodes();
|
||||
for (int i = children.getLength() - 1; i >= 0; i--) {
|
||||
Node item = children.item(i);
|
||||
if (item.getNodeType() == Node.ELEMENT_NODE && TAG_ITEM.equals(item.getNodeName())) {
|
||||
target = (Element)item;
|
||||
if (target.hasAttributeNS(ANDROID_URI, ATTR_DRAWABLE)) {
|
||||
attribute = ATTR_DRAWABLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("clip".equals(tag) || "inset".equals(tag) || "scale".equals(tag)) {
|
||||
target = root;
|
||||
attribute = ATTR_DRAWABLE;
|
||||
} else {
|
||||
// <shape> etc - no bitmap to be found
|
||||
return null;
|
||||
}
|
||||
if (attribute != null && target.hasAttributeNS(ANDROID_URI, attribute)) {
|
||||
String src = target.getAttributeNS(ANDROID_URI, attribute);
|
||||
ResourceValue value = resourceResolver.findResValue(src, false);
|
||||
if (value != null) {
|
||||
return ResourceHelper.resolveDrawable(resourceResolver, value, project);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
// Not logging for now; afraid to risk unexpected crashes in upcoming preview. TODO: Re-enable.
|
||||
//Logger.getInstance(AndroidColorAnnotator.class).warn(String.format("Could not read/render icon image %1$s", file), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Looks up the resource item of the given type and name for the given configuration, if any */
|
||||
@Nullable
|
||||
public static ResourceValue findResourceValue(ResourceType type,
|
||||
String name,
|
||||
boolean isFramework,
|
||||
Module module,
|
||||
Configuration configuration) {
|
||||
if (isFramework) {
|
||||
ResourceRepository frameworkResources = configuration.getFrameworkResources();
|
||||
if (frameworkResources == null) {
|
||||
return null;
|
||||
}
|
||||
if (!frameworkResources.hasResourceItem(type, name)) {
|
||||
return null;
|
||||
}
|
||||
ResourceItem item = frameworkResources.getResourceItem(type, name);
|
||||
return item.getResourceValue(type, configuration.getFullConfig(), false);
|
||||
} else {
|
||||
LocalResourceRepository appResources = AppResourceRepository.getAppResources(module, true);
|
||||
if (appResources == null) {
|
||||
return null;
|
||||
}
|
||||
if (!appResources.hasResourceItem(type, name)) {
|
||||
return null;
|
||||
}
|
||||
return appResources.getConfiguredValue(type, name, configuration.getFullConfig());
|
||||
}
|
||||
}
|
||||
|
||||
/** Picks a suitable configuration to use for resource resolution */
|
||||
@Nullable
|
||||
public static Configuration pickConfiguration(AndroidFacet facet, Module module, PsiFile file) {
|
||||
VirtualFile virtualFile = file.getVirtualFile();
|
||||
if (virtualFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
VirtualFile parent = virtualFile.getParent();
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
VirtualFile layout;
|
||||
String parentName = parent.getName();
|
||||
if (!parentName.startsWith(FD_RES_LAYOUT)) {
|
||||
layout = pickLayoutFile(module, facet);
|
||||
if (layout == null) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
layout = virtualFile;
|
||||
}
|
||||
|
||||
return facet.getConfigurationManager().getConfiguration(layout);
|
||||
}
|
||||
|
||||
public static class ColorRenderer extends GutterIconRenderer {
|
||||
private final PsiElement myElement;
|
||||
private final Color myColor;
|
||||
|
||||
ColorRenderer(@NotNull PsiElement element, @Nullable Color color) {
|
||||
myElement = element;
|
||||
myColor = color;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
Color color = getCurrentColor();
|
||||
return JBUI.scale(color == null ? EmptyIcon.create(ICON_SIZE) : new ColorIcon(ICON_SIZE, color));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Color getCurrentColor() {
|
||||
if (myColor != null) {
|
||||
return myColor;
|
||||
} else if (myElement instanceof XmlTag) {
|
||||
return ResourceHelper.parseColor(((XmlTag)myElement).getValue().getText());
|
||||
} else if (myElement instanceof XmlAttributeValue) {
|
||||
return ResourceHelper.parseColor(((XmlAttributeValue)myElement).getValue());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnAction getClickAction() {
|
||||
if (myColor != null) { // Cannot set colors that were derived
|
||||
return null;
|
||||
}
|
||||
return new AnAction() {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
|
||||
if (editor != null) {
|
||||
// Need ARGB support in platform color chooser; see
|
||||
// https://youtrack.jetbrains.com/issue/IDEA-123498
|
||||
//final Color color =
|
||||
// ColorChooser.chooseColor(editor.getComponent(), AndroidBundle.message("android.choose.color"), getCurrentColor());
|
||||
final Color color = ColorPicker.showDialog(editor.getComponent(), "Choose Color", getCurrentColor(), true, null, false);
|
||||
if (color != null) {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (myElement instanceof XmlTag) {
|
||||
((XmlTag)myElement).getValue().setText(ResourceHelper.colorToString(color));
|
||||
} else if (myElement instanceof XmlAttributeValue) {
|
||||
XmlAttribute attribute = PsiTreeUtil.getParentOfType(myElement, XmlAttribute.class);
|
||||
if (attribute != null) {
|
||||
attribute.setValue(ResourceHelper.colorToString(color));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ColorRenderer that = (ColorRenderer)o;
|
||||
// TODO: Compare with modification count in app resources (if not framework)
|
||||
if (myColor != null ? !myColor.equals(that.myColor) : that.myColor != null) return false;
|
||||
if (!myElement.equals(that.myElement)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myElement.hashCode();
|
||||
result = 31 * result + (myColor != null ? myColor.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.intellij.openapi.roots.ProjectRootModificationTracker
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.android.sdk.AndroidSdkData
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.AndroidDexer
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
import java.io.File
|
||||
@@ -56,7 +57,7 @@ class AndroidDexerImpl(val project: Project) : AndroidDexer {
|
||||
private fun doGetAndroidDexFile(): File? {
|
||||
for (module in ModuleManager.getInstance(project).modules) {
|
||||
val androidFacet = AndroidFacet.getInstance(module) ?: continue
|
||||
val sdkData = androidFacet.sdkData ?: continue
|
||||
val sdkData = AndroidSdkData.getSdkData(androidFacet) ?: continue
|
||||
val latestBuildTool = sdkData.getLatestBuildTool(/* allowPreview = */ false)
|
||||
?: sdkData.getLatestBuildTool(/* allowPreview = */ true)
|
||||
?: continue
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.debugger
|
||||
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectRootModificationTracker
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.AndroidDexer
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.security.ProtectionDomain
|
||||
|
||||
class AndroidDexerImpl(val project: Project) : AndroidDexer {
|
||||
private val cachedDexWrapper = CachedValuesManager.getManager(project).createCachedValue({
|
||||
val dexWrapper = doGetAndroidDexFile()?.let { dexJarFile ->
|
||||
val androidDexWrapperName = AndroidDexWrapper::class.java.canonicalName
|
||||
val classBytes = this.javaClass.classLoader.getResource(
|
||||
androidDexWrapperName.replace('.', '/') + ".class").readBytes()
|
||||
|
||||
val dexClassLoader = object : URLClassLoader(arrayOf(dexJarFile.toURI().toURL()), this::class.java.classLoader) {
|
||||
init {
|
||||
defineClass(androidDexWrapperName, classBytes, 0, classBytes.size, null as ProtectionDomain?)
|
||||
}
|
||||
}
|
||||
|
||||
Class.forName(androidDexWrapperName, true, dexClassLoader).newInstance()
|
||||
}
|
||||
|
||||
CachedValueProvider.Result.createSingleDependency(dexWrapper, ProjectRootModificationTracker.getInstance(project))
|
||||
}, /* trackValue = */ false)
|
||||
|
||||
override fun dex(classes: Collection<ClassToLoad>): ByteArray? {
|
||||
val dexWrapper = cachedDexWrapper.value
|
||||
val dexMethod = dexWrapper::class.java.methods.firstOrNull { it.name == "dex" } ?: return null
|
||||
return dexMethod.invoke(dexWrapper, classes) as? ByteArray ?: return null
|
||||
}
|
||||
|
||||
private fun doGetAndroidDexFile(): File? {
|
||||
for (module in ModuleManager.getInstance(project).modules) {
|
||||
val androidFacet = AndroidFacet.getInstance(module) ?: continue
|
||||
val sdkData = androidFacet.sdkData ?: continue
|
||||
val latestBuildTool = sdkData.getLatestBuildTool(/* allowPreview = */ false)
|
||||
?: sdkData.getLatestBuildTool(/* allowPreview = */ true)
|
||||
?: continue
|
||||
|
||||
val dxJar = File(latestBuildTool.location, "lib/dx.jar")
|
||||
if (dxJar.exists()) {
|
||||
return dxJar
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -47,7 +47,6 @@ class ResourceFoldingBuilder : FoldingBuilderEx() {
|
||||
// See lint's StringFormatDetector
|
||||
private val FORMAT = Pattern.compile("%(\\d+\\$)?([-+#, 0(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])")
|
||||
private val FOLD_MAX_LENGTH = 60
|
||||
private val FORCE_PROJECT_RESOURCE_LOADING = true
|
||||
private val UNIT_TEST_MODE: Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
private val RESOURCE_TYPES = listOf(ResourceType.STRING,
|
||||
ResourceType.DIMEN,
|
||||
@@ -254,6 +253,6 @@ class ResourceFoldingBuilder : FoldingBuilderEx() {
|
||||
}
|
||||
|
||||
private fun getAppResources(element: PsiElement): LocalResourceRepository? = ModuleUtilCore.findModuleForPsiElement(element)?.let {
|
||||
AppResourceRepository.getAppResources(it, FORCE_PROJECT_RESOURCE_LOADING)
|
||||
AppResourceRepository.findExistingInstance(it)
|
||||
}
|
||||
}
|
||||
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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.folding
|
||||
|
||||
import com.android.SdkConstants.*
|
||||
import com.android.ide.common.resources.configuration.FolderConfiguration
|
||||
import com.android.ide.common.resources.configuration.LocaleQualifier
|
||||
import com.android.resources.ResourceType
|
||||
import com.android.tools.idea.folding.AndroidFoldingSettings
|
||||
import com.android.tools.idea.res.AppResourceRepository
|
||||
import com.android.tools.idea.res.LocalResourceRepository
|
||||
import com.intellij.lang.ASTNode
|
||||
import com.intellij.lang.folding.FoldingBuilderEx
|
||||
import com.intellij.lang.folding.FoldingDescriptor
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.impl.source.SourceTreeToPsiMap
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.uast.*
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
class ResourceFoldingBuilder : FoldingBuilderEx() {
|
||||
|
||||
companion object {
|
||||
// See lint's StringFormatDetector
|
||||
private val FORMAT = Pattern.compile("%(\\d+\\$)?([-+#, 0(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])")
|
||||
private val FOLD_MAX_LENGTH = 60
|
||||
private val FORCE_PROJECT_RESOURCE_LOADING = true
|
||||
private val UNIT_TEST_MODE: Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
private val RESOURCE_TYPES = listOf(ResourceType.STRING,
|
||||
ResourceType.DIMEN,
|
||||
ResourceType.INTEGER,
|
||||
ResourceType.PLURALS)
|
||||
}
|
||||
|
||||
private val isFoldingEnabled = AndroidFoldingSettings.getInstance().isCollapseAndroidStrings
|
||||
|
||||
override fun getPlaceholderText(node: ASTNode): String? {
|
||||
|
||||
tailrec fun UElement.unwrapReferenceAndGetValue(resources: LocalResourceRepository): String? = when (this) {
|
||||
is UQualifiedReferenceExpression -> selector.unwrapReferenceAndGetValue(resources)
|
||||
is UCallExpression -> (valueArguments.firstOrNull() as? UReferenceExpression)?.getAndroidResourceValue(resources, this)
|
||||
else -> (this as? UReferenceExpression)?.getAndroidResourceValue(resources)
|
||||
}
|
||||
|
||||
val element = SourceTreeToPsiMap.treeElementToPsi(node) ?: return null
|
||||
val appResources = getAppResources(element) ?: return null
|
||||
val uastContext = ServiceManager.getService(element.project, UastContext::class.java) ?: return null
|
||||
return uastContext.convertElement(element, null, null)?.unwrapReferenceAndGetValue(appResources)
|
||||
}
|
||||
|
||||
override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array<FoldingDescriptor> {
|
||||
if (root !is KtFile || quick && !UNIT_TEST_MODE || !isFoldingEnabled || AndroidFacet.getInstance(root) == null) {
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
val file = root.toUElement()
|
||||
val result = arrayListOf<FoldingDescriptor>()
|
||||
file?.accept(object : AbstractUastVisitor() {
|
||||
override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean {
|
||||
node.getFoldingDescriptor()?.let { result.add(it) }
|
||||
return super.visitSimpleNameReferenceExpression(node)
|
||||
}
|
||||
})
|
||||
|
||||
return result.toTypedArray()
|
||||
}
|
||||
|
||||
override fun isCollapsedByDefault(node: ASTNode): Boolean = isFoldingEnabled
|
||||
|
||||
private fun UReferenceExpression.getFoldingDescriptor(): FoldingDescriptor? {
|
||||
val resolved = resolve() ?: return null
|
||||
val resourceType = resolved.getAndroidResourceType() ?: return null
|
||||
if (resourceType !in RESOURCE_TYPES) return null
|
||||
|
||||
fun UElement.createFoldingDescriptor() = psi?.let { psi ->
|
||||
val dependencies: Set<PsiElement> = setOf(psi)
|
||||
FoldingDescriptor(psi.node, psi.textRange, null, dependencies)
|
||||
}
|
||||
|
||||
val element = uastParent as? UQualifiedReferenceExpression ?: this
|
||||
val getResourceValueCall = (element.uastParent as? UCallExpression)?.takeIf { it.isFoldableGetResourceValueCall() }
|
||||
if (getResourceValueCall != null) {
|
||||
val qualifiedCall = getResourceValueCall.uastParent as? UQualifiedReferenceExpression
|
||||
if (qualifiedCall?.selector == getResourceValueCall) {
|
||||
return qualifiedCall.createFoldingDescriptor()
|
||||
}
|
||||
|
||||
return getResourceValueCall.createFoldingDescriptor()
|
||||
}
|
||||
|
||||
return element.createFoldingDescriptor()
|
||||
}
|
||||
|
||||
private fun UCallExpression.isFoldableGetResourceValueCall(): Boolean {
|
||||
return methodName == "getString" ||
|
||||
methodName == "getText" ||
|
||||
methodName == "getInteger" ||
|
||||
methodName?.startsWith("getDimension") ?: false ||
|
||||
methodName?.startsWith("getQuantityString") ?: false
|
||||
}
|
||||
|
||||
private fun PsiElement.getAndroidResourceType(): ResourceType? {
|
||||
val elementType = parent as? PsiClass ?: return null
|
||||
val elementPackage = elementType.parent as? PsiClass ?: return null
|
||||
if (R_CLASS != elementPackage.name) return null
|
||||
if (elementPackage.qualifiedName != "$ANDROID_PKG.$R_CLASS") {
|
||||
return ResourceType.getEnum(elementType.name)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun UReferenceExpression.getAndroidResourceValue(resources: LocalResourceRepository, call: UCallExpression? = null): String? {
|
||||
val resourceType = resolve()?.getAndroidResourceType() ?: return null
|
||||
val referenceConfig = FolderConfiguration().apply { localeQualifier = LocaleQualifier("xx") }
|
||||
val key = resolvedName ?: return null
|
||||
val resourceValue = resources.getResourceValue(resourceType, key, referenceConfig) ?: return null
|
||||
val text = if (call != null) formatArguments(call, resourceValue) else resourceValue
|
||||
|
||||
if (resourceType == ResourceType.STRING || resourceType == ResourceType.PLURALS) {
|
||||
return '"' + StringUtil.shortenTextWithEllipsis(text, FOLD_MAX_LENGTH - 2, 0) + '"'
|
||||
}
|
||||
else if (text.length <= 1) {
|
||||
// Don't just inline empty or one-character replacements: they can't be expanded by a mouse click
|
||||
// so are hard to use without knowing about the folding keyboard shortcut to toggle folding.
|
||||
// This is similar to how IntelliJ 14 handles call parameters
|
||||
return key + ": " + text
|
||||
}
|
||||
|
||||
return StringUtil.shortenTextWithEllipsis(text, FOLD_MAX_LENGTH, 0)
|
||||
}
|
||||
|
||||
private tailrec fun LocalResourceRepository.getResourceValue(
|
||||
type: ResourceType,
|
||||
name: String,
|
||||
referenceConfig: FolderConfiguration): String? {
|
||||
val value = getConfiguredValue(type, name, referenceConfig)?.value ?: return null
|
||||
if (!value.startsWith('@')) {
|
||||
return value
|
||||
}
|
||||
|
||||
val (referencedTypeName, referencedName) = value.substring(1).split('/').takeIf { it.size == 2 } ?: return value
|
||||
val referencedType = ResourceType.getEnum(referencedTypeName) ?: return value
|
||||
return getResourceValue(referencedType, referencedName, referenceConfig)
|
||||
}
|
||||
|
||||
// Converted from com.android.tools.idea.folding.InlinedResource#insertArguments
|
||||
private fun formatArguments(callExpression: UCallExpression, formatString: String): String {
|
||||
if (!formatString.contains('%')) {
|
||||
return formatString
|
||||
}
|
||||
|
||||
val args = callExpression.valueArguments
|
||||
if (args.isEmpty() || !args.first().isPsiValid) {
|
||||
return formatString
|
||||
}
|
||||
|
||||
val matcher = FORMAT.matcher(formatString)
|
||||
var index = 0
|
||||
var prevIndex = 0
|
||||
var nextNumber = 1
|
||||
var start = 0
|
||||
val sb = StringBuilder(2 * formatString.length)
|
||||
while (true) {
|
||||
if (matcher.find(index)) {
|
||||
if ("%" == matcher.group(6)) {
|
||||
index = matcher.end()
|
||||
continue
|
||||
}
|
||||
val matchStart = matcher.start()
|
||||
// Make sure this is not an escaped '%'
|
||||
while (prevIndex < matchStart) {
|
||||
val c = formatString[prevIndex]
|
||||
if (c == '\\') {
|
||||
prevIndex++
|
||||
}
|
||||
prevIndex++
|
||||
}
|
||||
if (prevIndex > matchStart) {
|
||||
// We're in an escape, ignore this result
|
||||
index = prevIndex
|
||||
continue
|
||||
}
|
||||
|
||||
index = matcher.end()
|
||||
|
||||
// Shouldn't throw a number format exception since we've already
|
||||
// matched the pattern in the regexp
|
||||
val number: Int
|
||||
var numberString: String? = 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 > 0 && number < args.size) {
|
||||
val argExpression = args[number]
|
||||
var value: Any? = argExpression.evaluate()
|
||||
|
||||
if (value == null) {
|
||||
value = args[number].asSourceString()
|
||||
}
|
||||
|
||||
for (i in start..matchStart - 1) {
|
||||
sb.append(formatString[i])
|
||||
}
|
||||
|
||||
sb.append("{")
|
||||
sb.append(value)
|
||||
sb.append('}')
|
||||
start = index
|
||||
}
|
||||
}
|
||||
else {
|
||||
var i = start
|
||||
val n = formatString.length
|
||||
while (i < n) {
|
||||
sb.append(formatString[i])
|
||||
i++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun getAppResources(element: PsiElement): LocalResourceRepository? = ModuleUtilCore.findModuleForPsiElement(element)?.let {
|
||||
AppResourceRepository.getAppResources(it, FORCE_PROJECT_RESOURCE_LOADING)
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.inspection
|
||||
|
||||
import com.android.tools.idea.model.AndroidModuleInfo
|
||||
import com.intellij.codeInspection.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
@@ -28,7 +29,11 @@ import org.jetbrains.kotlin.psi.psiUtil.addTypeArgument
|
||||
|
||||
class TypeParameterFindViewByIdInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||
val compileSdk = AndroidFacet.getInstance(session.file)?.androidModuleInfo?.buildSdkVersion?.apiLevel
|
||||
val compileSdk = AndroidFacet.getInstance(session.file)
|
||||
?.let { facet -> AndroidModuleInfo.getInstance(facet) }
|
||||
?.buildSdkVersion
|
||||
?.apiLevel
|
||||
|
||||
if (compileSdk == null || compileSdk < 26) {
|
||||
return KtVisitorVoid()
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.inspection
|
||||
|
||||
import com.intellij.codeInspection.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.KtPsiUtil.isUnsafeCast
|
||||
import org.jetbrains.kotlin.psi.psiUtil.addTypeArgument
|
||||
|
||||
class TypeParameterFindViewByIdInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||
val compileSdk = AndroidFacet.getInstance(session.file)?.androidModuleInfo?.buildSdkVersion?.apiLevel
|
||||
if (compileSdk == null || compileSdk < 26) {
|
||||
return KtVisitorVoid()
|
||||
}
|
||||
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
super.visitCallExpression(expression)
|
||||
if (expression.calleeExpression?.text != "findViewById" || expression.typeArguments.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val parentCast = (expression.parent as? KtBinaryExpressionWithTypeRHS)?.takeIf { isUnsafeCast(it) } ?: return
|
||||
val typeText = parentCast.right?.getTypeTextWithoutQuestionMark() ?: return
|
||||
val callableDescriptor = expression.resolveToCall()?.resultingDescriptor ?: return
|
||||
if (callableDescriptor.name.asString() != "findViewById" || callableDescriptor.typeParameters.size != 1) {
|
||||
return
|
||||
}
|
||||
|
||||
holder.registerProblem(
|
||||
parentCast,
|
||||
"Can be converted to findViewById<$typeText>(...)",
|
||||
ConvertCastToFindViewByIdWithTypeParameter())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConvertCastToFindViewByIdWithTypeParameter : LocalQuickFix {
|
||||
override fun getFamilyName(): String = "Convert cast to findViewById with type parameter"
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val cast = descriptor.psiElement as? KtBinaryExpressionWithTypeRHS ?: return
|
||||
val typeText = cast.right?.getTypeTextWithoutQuestionMark() ?: return
|
||||
val call = cast.left as? KtCallExpression ?: return
|
||||
|
||||
val newCall = call.copy() as KtCallExpression
|
||||
val typeArgument = KtPsiFactory(call).createTypeArgument(typeText)
|
||||
newCall.addTypeArgument(typeArgument)
|
||||
|
||||
cast.replace(newCall)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun KtTypeReference.getTypeTextWithoutQuestionMark(): String? =
|
||||
(typeElement as? KtNullableType)?.innerType?.text ?: typeElement?.text
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -29,6 +29,7 @@ import org.jetbrains.android.dom.resources.Attr;
|
||||
import org.jetbrains.android.dom.resources.DeclareStyleable;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.resourceManagers.LocalResourceManager;
|
||||
import org.jetbrains.android.resourceManagers.ModuleResourceManagers;
|
||||
import org.jetbrains.android.resourceManagers.ResourceManager;
|
||||
import org.jetbrains.android.util.AndroidResourceUtil;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
@@ -68,9 +69,10 @@ public class KotlinAndroidGotoDeclarationHandler implements GotoDeclarationHandl
|
||||
collectManifestElements(nestedClassName, fieldName, facet, resourceList);
|
||||
}
|
||||
else {
|
||||
ModuleResourceManagers managers = ModuleResourceManagers.getInstance(facet);
|
||||
ResourceManager manager = info.isSystem()
|
||||
? facet.getSystemResourceManager(false)
|
||||
: facet.getLocalResourceManager();
|
||||
? managers.getSystemResourceManager(false)
|
||||
: managers.getLocalResourceManager();
|
||||
if (manager == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.navigation;
|
||||
|
||||
import com.android.resources.ResourceType;
|
||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.xml.XmlElement;
|
||||
import org.jetbrains.android.dom.AndroidAttributeValue;
|
||||
import org.jetbrains.android.dom.manifest.Manifest;
|
||||
import org.jetbrains.android.dom.manifest.ManifestElementWithRequiredName;
|
||||
import org.jetbrains.android.dom.resources.Attr;
|
||||
import org.jetbrains.android.dom.resources.DeclareStyleable;
|
||||
import org.jetbrains.android.facet.AndroidFacet;
|
||||
import org.jetbrains.android.resourceManagers.LocalResourceManager;
|
||||
import org.jetbrains.android.resourceManagers.ResourceManager;
|
||||
import org.jetbrains.android.util.AndroidResourceUtil;
|
||||
import org.jetbrains.android.util.AndroidUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.android.AndroidUtilKt;
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
// TODO: ask for extension point
|
||||
// this class is mostly copied from org.jetbrains.android.AndroidGotoDeclarationHandler
|
||||
public class KotlinAndroidGotoDeclarationHandler implements GotoDeclarationHandler {
|
||||
@Override
|
||||
public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement sourceElement, int offset, Editor editor) {
|
||||
KtSimpleNameExpression referenceExpression = GotoResourceHelperKt.getReferenceExpression(sourceElement);
|
||||
if (referenceExpression == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AndroidFacet facet = AndroidUtilKt.getAndroidFacetForFile(referenceExpression);
|
||||
if (facet == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AndroidResourceUtil.MyReferredResourceFieldInfo info = GotoResourceHelperKt.getInfo(referenceExpression, facet);
|
||||
|
||||
if (info == null) return null;
|
||||
|
||||
String nestedClassName = info.getClassName();
|
||||
String fieldName = info.getFieldName();
|
||||
List<PsiElement> resourceList = new ArrayList<PsiElement>();
|
||||
|
||||
if (info.isFromManifest()) {
|
||||
collectManifestElements(nestedClassName, fieldName, facet, resourceList);
|
||||
}
|
||||
else {
|
||||
ResourceManager manager = info.isSystem()
|
||||
? facet.getSystemResourceManager(false)
|
||||
: facet.getLocalResourceManager();
|
||||
if (manager == null) {
|
||||
return null;
|
||||
}
|
||||
manager.collectLazyResourceElements(nestedClassName, fieldName, false, referenceExpression, resourceList);
|
||||
|
||||
if (manager instanceof LocalResourceManager) {
|
||||
LocalResourceManager lrm = (LocalResourceManager) manager;
|
||||
|
||||
if (nestedClassName.equals(ResourceType.ATTR.getName())) {
|
||||
for (Attr attr : lrm.findAttrs(fieldName)) {
|
||||
resourceList.add(attr.getName().getXmlAttributeValue());
|
||||
}
|
||||
}
|
||||
else if (nestedClassName.equals(ResourceType.STYLEABLE.getName())) {
|
||||
for (DeclareStyleable styleable : lrm.findStyleables(fieldName)) {
|
||||
resourceList.add(styleable.getName().getXmlAttributeValue());
|
||||
}
|
||||
|
||||
for (Attr styleable : lrm.findStyleableAttributesByFieldName(fieldName)) {
|
||||
resourceList.add(styleable.getName().getXmlAttributeValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceList.size() > 1) {
|
||||
// Sort to ensure the output is stable, and to prefer the base folders
|
||||
Collections.sort(resourceList, AndroidResourceUtil.RESOURCE_ELEMENT_COMPARATOR);
|
||||
}
|
||||
|
||||
return resourceList.toArray(new PsiElement[resourceList.size()]);
|
||||
}
|
||||
|
||||
private static void collectManifestElements(
|
||||
@NotNull String nestedClassName,
|
||||
@NotNull String fieldName,
|
||||
@NotNull AndroidFacet facet,
|
||||
@NotNull List<PsiElement> result
|
||||
) {
|
||||
Manifest manifest = facet.getManifest();
|
||||
|
||||
if (manifest == null) {
|
||||
return;
|
||||
}
|
||||
List<? extends ManifestElementWithRequiredName> list;
|
||||
|
||||
if ("permission".equals(nestedClassName)) {
|
||||
list = manifest.getPermissions();
|
||||
}
|
||||
else if ("permission_group".equals(nestedClassName)) {
|
||||
list = manifest.getPermissionGroups();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
for (ManifestElementWithRequiredName domElement : list) {
|
||||
AndroidAttributeValue<String> nameAttribute = domElement.getName();
|
||||
String name = nameAttribute.getValue();
|
||||
|
||||
if (AndroidUtils.equal(name, fieldName, false)) {
|
||||
XmlElement psiElement = nameAttribute.getXmlAttributeValue();
|
||||
|
||||
if (psiElement != null) {
|
||||
result.add(psiElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionText(DataContext context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+94
@@ -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)[BindingContext.USED_AS_EXPRESSION, element] ?: false
|
||||
return if (used) {
|
||||
object : KotlinIfSurrounder() {
|
||||
override fun getCodeTemplate(): String = "if (a) { \n} else {\nTODO(${todoText ?: ""})\n}"
|
||||
}
|
||||
} else {
|
||||
KotlinIfSurrounder()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
+80
@@ -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 + "RequiresApi"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.android.lint
|
||||
import com.intellij.codeInspection.InspectionProfileEntry
|
||||
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
|
||||
import com.intellij.util.PathUtil
|
||||
import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
|
||||
import org.jetbrains.android.inspections.lint.AndroidLintInspectionBase
|
||||
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.lint
|
||||
|
||||
import com.intellij.codeInspection.InspectionProfileEntry
|
||||
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
|
||||
import com.intellij.util.PathUtil
|
||||
import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
|
||||
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
|
||||
(myFixture as CodeInsightTestFixtureImpl).setVirtualFileFilter { false } // Allow access to tree elements.
|
||||
ConfigLibraryUtil.configureKotlinRuntime(myModule)
|
||||
ConfigLibraryUtil.addLibrary(myModule, "androidExtensionsRuntime", "dist/kotlinc/lib", arrayOf("android-extensions-runtime.jar"))
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
ConfigLibraryUtil.unConfigureKotlinRuntime(myModule)
|
||||
ConfigLibraryUtil.removeLibrary(myModule, "androidExtensionsRuntime")
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
fun doTest(path: String) {
|
||||
val ktFile = File(path)
|
||||
val fileText = ktFile.readText()
|
||||
val mainInspectionClassName = findStringWithPrefixes(fileText, "// INSPECTION_CLASS: ") ?: error("Empty class name")
|
||||
val dependencies = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// DEPENDENCY: ")
|
||||
|
||||
val inspectionClassNames = mutableListOf(mainInspectionClassName)
|
||||
for (i in 2..100) {
|
||||
val className = findStringWithPrefixes(ktFile.readText(), "// INSPECTION_CLASS$i: ") ?: break
|
||||
inspectionClassNames += className
|
||||
}
|
||||
|
||||
myFixture.enableInspections(*inspectionClassNames.map { className ->
|
||||
val inspectionClass = Class.forName(className)
|
||||
inspectionClass.newInstance() as InspectionProfileEntry
|
||||
}.toTypedArray())
|
||||
|
||||
val additionalResourcesDir = File(ktFile.parentFile, getTestName(true))
|
||||
if (additionalResourcesDir.exists()) {
|
||||
for (file in additionalResourcesDir.listFiles()) {
|
||||
if (file.isFile) {
|
||||
myFixture.copyFileToProject(file.absolutePath, file.name)
|
||||
}
|
||||
else if (file.isDirectory) {
|
||||
myFixture.copyDirectoryToProject(file.absolutePath, file.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/${PathUtil.getFileName(path)}")
|
||||
myFixture.configureFromExistingVirtualFile(virtualFile)
|
||||
|
||||
dependencies.forEach { dependency ->
|
||||
val (dependencyFile, dependencyTargetPath) = dependency.split(" -> ").map(String::trim)
|
||||
myFixture.copyFileToProject("${PathUtil.getParentPath(path)}/$dependencyFile", "src/$dependencyTargetPath")
|
||||
}
|
||||
|
||||
myFixture.checkHighlighting(true, false, true)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ 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.klint.AndroidLintInspectionBase
|
||||
import org.jetbrains.android.inspections.lint.AndroidLintInspectionBase
|
||||
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.io.File
|
||||
|
||||
+62
@@ -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.klint.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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user