Remove as31 bunch files
This commit is contained in:
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android
|
||||
|
||||
import com.android.tools.idea.npw.template.ConvertJavaToKotlinProvider
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
import org.jetbrains.kotlin.android.configure.KotlinAndroidGradleModuleConfigurator
|
||||
import org.jetbrains.kotlin.idea.actions.JavaToKotlinAction
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinProjectConfigurator
|
||||
import org.jetbrains.kotlin.idea.configuration.getCanBeConfiguredModules
|
||||
import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion
|
||||
|
||||
class ConvertJavaToKotlinProviderImpl : ConvertJavaToKotlinProvider {
|
||||
override fun configureKotlin(project: Project) {
|
||||
val configurator = KotlinProjectConfigurator.EP_NAME.findExtension(KotlinAndroidGradleModuleConfigurator::class.java)
|
||||
val nonConfiguredModules = getCanBeConfiguredModules(project, configurator)
|
||||
configurator.configureSilently(project, nonConfiguredModules, bundledRuntimeVersion())
|
||||
}
|
||||
|
||||
override fun getKotlinVersion(): String {
|
||||
return bundledRuntimeVersion()
|
||||
}
|
||||
|
||||
override fun convertToKotlin(project: Project, files: List<PsiJavaFile>): List<PsiFile> {
|
||||
return JavaToKotlinAction.convertFiles(files, project, askExternalCodeProcessing = false)
|
||||
}
|
||||
}
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android
|
||||
|
||||
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,
|
||||
"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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
-270
@@ -1,270 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android;
|
||||
|
||||
|
||||
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.configurations.ConfigurationManager;
|
||||
import com.android.tools.idea.res.AppResourceRepository;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* Contains copied privates from AndroidColorAnnotator, so we could use them for Kotlin AndroidResourceReferenceAnnotator
|
||||
*/
|
||||
public class ResourceReferenceAnnotatorUtil {
|
||||
|
||||
private static final int ICON_SIZE = 8;
|
||||
|
||||
@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 {
|
||||
AppResourceRepository appResources = AppResourceRepository.getOrCreateInstance(module);
|
||||
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;
|
||||
} else {
|
||||
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 ConfigurationManager.getOrCreateInstance(module).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.configure
|
||||
|
||||
import com.android.tools.idea.gradle.project.model.JavaModuleModel
|
||||
import com.android.tools.idea.gradle.project.sync.idea.data.service.AndroidProjectKeys
|
||||
import com.android.tools.idea.io.FilePaths
|
||||
import com.intellij.openapi.externalSystem.model.DataNode
|
||||
import com.intellij.openapi.externalSystem.model.project.ModuleData
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData
|
||||
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.roots.impl.libraries.LibraryEx
|
||||
import com.intellij.openapi.roots.libraries.Library
|
||||
import org.jetbrains.kotlin.idea.configuration.detectPlatformKindByPlugin
|
||||
import org.jetbrains.kotlin.idea.framework.detectLibraryKind
|
||||
import org.jetbrains.kotlin.idea.platform.tooling
|
||||
import java.io.File
|
||||
|
||||
class KotlinAndroidGradleLibraryDataService : AbstractProjectDataService<JavaModuleModel, Void>() {
|
||||
override fun getTargetDataKey() = AndroidProjectKeys.JAVA_MODULE_MODEL
|
||||
|
||||
override fun postProcess(
|
||||
toImport: MutableCollection<DataNode<JavaModuleModel>>,
|
||||
projectData: ProjectData?,
|
||||
project: Project,
|
||||
modelsProvider: IdeModifiableModelsProvider
|
||||
) {
|
||||
for (dataNode in toImport) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val targetLibraryKind = detectPlatformKindByPlugin(dataNode.parent as DataNode<ModuleData>)?.tooling?.libraryKind
|
||||
if (targetLibraryKind != null) {
|
||||
for (dep in dataNode.data.jarLibraryDependencies) {
|
||||
val library = modelsProvider.findLibraryByBinaryPath(dep.binaryPath) as LibraryEx? ?: continue
|
||||
if (library.kind == null) {
|
||||
val model = modelsProvider.getModifiableLibraryModel(library) as LibraryEx.ModifiableModelEx
|
||||
detectLibraryKind(model.getFiles(OrderRootType.CLASSES))?.let { model.kind = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun IdeModifiableModelsProvider.findLibraryByBinaryPath(path: File?): Library? {
|
||||
if (path == null) return null
|
||||
val url = FilePaths.pathToIdeaUrl(path)
|
||||
return allLibraries.firstOrNull { url in getModifiableLibraryModel(it).getUrls(OrderRootType.CLASSES) }
|
||||
}
|
||||
}
|
||||
-236
@@ -1,236 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.configure
|
||||
|
||||
import com.android.tools.idea.gradle.project.sync.idea.data.service.AndroidProjectKeys
|
||||
import com.android.tools.idea.gradle.util.ContentEntries.findParentContentEntry
|
||||
import com.android.tools.idea.io.FilePaths
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager
|
||||
import com.intellij.openapi.externalSystem.model.DataNode
|
||||
import com.intellij.openapi.externalSystem.model.ProjectKeys
|
||||
import com.intellij.openapi.externalSystem.model.project.ContentRootData
|
||||
import com.intellij.openapi.externalSystem.model.project.ModuleData
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData
|
||||
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ContentRootDataService
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ContentRootDataService.CREATE_EMPTY_DIRECTORIES
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.DependencyScope
|
||||
import com.intellij.openapi.roots.ModifiableRootModel
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.stream
|
||||
import org.jetbrains.jps.model.java.JavaResourceRootType
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||
import org.jetbrains.jps.model.module.JpsModuleSourceRootType
|
||||
import org.jetbrains.kotlin.gradle.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.KotlinPlatform
|
||||
import org.jetbrains.kotlin.gradle.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.idea.addModuleDependencyIfNeeded
|
||||
import org.jetbrains.kotlin.idea.configuration.*
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class KotlinAndroidGradleMPPModuleDataService : AbstractProjectDataService<ModuleData, Void>() {
|
||||
override fun getTargetDataKey() = ProjectKeys.MODULE
|
||||
|
||||
private fun shouldCreateEmptySourceRoots(
|
||||
moduleDataNode: DataNode<ModuleData>,
|
||||
module: Module
|
||||
): Boolean {
|
||||
val projectDataNode = ExternalSystemApiUtil.findParent(moduleDataNode, ProjectKeys.PROJECT) ?: return false
|
||||
if (projectDataNode.getUserData<Boolean>(CREATE_EMPTY_DIRECTORIES) == true) return true
|
||||
|
||||
val projectSystemId = projectDataNode.data.owner
|
||||
val externalSystemSettings = ExternalSystemApiUtil.getSettings(module.project, projectSystemId)
|
||||
|
||||
val path = ExternalSystemModulePropertyManager.getInstance(module).getRootProjectPath() ?: return false
|
||||
return externalSystemSettings.getLinkedProjectSettings(path)?.isCreateEmptyContentRootDirectories ?: false
|
||||
}
|
||||
|
||||
override fun postProcess(
|
||||
toImport: MutableCollection<DataNode<ModuleData>>,
|
||||
projectData: ProjectData?,
|
||||
project: Project,
|
||||
modelsProvider: IdeModifiableModelsProvider
|
||||
) {
|
||||
val contentRootsToImport = ArrayList<DataNode<ContentRootData>>()
|
||||
|
||||
for (nodeToImport in toImport) {
|
||||
val projectNode = ExternalSystemApiUtil.findParent(nodeToImport, ProjectKeys.PROJECT) ?: continue
|
||||
|
||||
for (childNode in ExternalSystemApiUtil.getChildren(nodeToImport, GradleSourceSetData.KEY)) {
|
||||
val sourceSet = childNode.kotlinSourceSet?.kotlinModule ?: continue
|
||||
if (sourceSet.platform == KotlinPlatform.ANDROID) continue
|
||||
contentRootsToImport += ExternalSystemApiUtil.getChildren(childNode, ProjectKeys.CONTENT_ROOT)
|
||||
}
|
||||
|
||||
val moduleData = nodeToImport.data
|
||||
val module = modelsProvider.findIdeModule(moduleData) ?: continue
|
||||
val shouldCreateEmptySourceRoots = shouldCreateEmptySourceRoots(nodeToImport, module)
|
||||
val rootModel = modelsProvider.getModifiableRootModel(module)
|
||||
val kotlinAndroidSourceSets = nodeToImport.kotlinAndroidSourceSets ?: emptyList()
|
||||
for (sourceSetInfo in kotlinAndroidSourceSets) {
|
||||
val compilation = sourceSetInfo.kotlinModule as? KotlinCompilation ?: continue
|
||||
for (sourceSet in compilation.sourceSets) {
|
||||
if (sourceSet.platform == KotlinPlatform.ANDROID) {
|
||||
val sourceType = if (sourceSet.isTestModule) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE
|
||||
val resourceType = if (sourceSet.isTestModule) JavaResourceRootType.TEST_RESOURCE else JavaResourceRootType.RESOURCE
|
||||
sourceSet.sourceDirs.forEach { addSourceRoot(it, sourceType, rootModel, shouldCreateEmptySourceRoots) }
|
||||
sourceSet.resourceDirs.forEach { addSourceRoot(it, resourceType, rootModel, shouldCreateEmptySourceRoots) }
|
||||
}
|
||||
}
|
||||
}
|
||||
addExtraDependeeModules(nodeToImport, projectNode, modelsProvider, rootModel, false)
|
||||
addExtraDependeeModules(nodeToImport, projectNode, modelsProvider, rootModel, true)
|
||||
|
||||
if (nodeToImport.kotlinAndroidSourceSets == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
val androidModel = getAndroidModuleModel(nodeToImport) ?: continue
|
||||
val variantName = androidModel.selectedVariant.name
|
||||
val activeSourceSetInfos = nodeToImport.kotlinAndroidSourceSets?.filter { it.kotlinModule.name.startsWith(variantName) } ?: emptyList()
|
||||
for (activeSourceSetInfo in activeSourceSetInfos) {
|
||||
val activeCompilation = activeSourceSetInfo.kotlinModule as? KotlinCompilation ?: continue
|
||||
for (sourceSet in activeCompilation.sourceSets) {
|
||||
if (sourceSet.platform != KotlinPlatform.ANDROID) {
|
||||
val sourceSetId = activeSourceSetInfo.sourceSetIdsByName[sourceSet.name] ?: continue
|
||||
val sourceSetData = ExternalSystemApiUtil.findFirstRecursively(projectNode) {
|
||||
(it.data as? ModuleData)?.id == sourceSetId
|
||||
}?.data as? ModuleData ?: continue
|
||||
val sourceSetModule = modelsProvider.findIdeModule(sourceSetData) ?: continue
|
||||
addModuleDependencyIfNeeded(rootModel, sourceSetModule, activeSourceSetInfo.isTestModule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mainSourceSetInfo = activeSourceSetInfos.firstOrNull { it.kotlinModule.name == variantName }
|
||||
if (mainSourceSetInfo != null) {
|
||||
KotlinSourceSetDataService.configureFacet(moduleData, mainSourceSetInfo, nodeToImport, module, modelsProvider)
|
||||
}
|
||||
|
||||
val kotlinFacet = KotlinFacet.get(module)
|
||||
if (kotlinFacet != null) {
|
||||
GradleProjectImportHandler.getInstances(project).forEach { it.importByModule(kotlinFacet, nodeToImport) }
|
||||
}
|
||||
}
|
||||
|
||||
ContentRootDataService().importData(contentRootsToImport, projectData, project, modelsProvider)
|
||||
}
|
||||
|
||||
private fun getDependeeModuleNodes(
|
||||
moduleNode: DataNode<ModuleData>,
|
||||
projectNode: DataNode<ProjectData>,
|
||||
modelsProvider: IdeModifiableModelsProvider,
|
||||
testScope: Boolean
|
||||
): List<DataNode<out ModuleData>> {
|
||||
val androidModel = getAndroidModuleModel(moduleNode)
|
||||
if (androidModel != null) {
|
||||
val dependencies = if (testScope) {
|
||||
androidModel.selectedAndroidTestCompileDependencies
|
||||
} else {
|
||||
androidModel.selectedMainCompileLevel2Dependencies
|
||||
} ?: return emptyList()
|
||||
return dependencies
|
||||
.moduleDependencies
|
||||
.mapNotNull { projectNode.findChildModuleById(it.projectPath) }
|
||||
}
|
||||
|
||||
val javaModel = getJavaModuleModel(moduleNode)
|
||||
if (javaModel != null) {
|
||||
val scope = if (testScope) DependencyScope.TEST.name else DependencyScope.COMPILE.name
|
||||
val moduleNames = javaModel
|
||||
.javaModuleDependencies
|
||||
.filter { scope == it.scope ?: DependencyScope.COMPILE.name }
|
||||
.mapTo(HashSet()) { it.moduleName }
|
||||
return ExternalSystemApiUtil
|
||||
.getChildren(projectNode, ProjectKeys.MODULE)
|
||||
.filter { modelsProvider.findIdeModule(it.data)?.name in moduleNames }
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun List<DataNode<GradleSourceSetData>>.firstByPlatformOrNull(platform: KotlinPlatform) = firstOrNull {
|
||||
it.kotlinSourceSet?.platform == platform
|
||||
}
|
||||
|
||||
private fun addExtraDependeeModules(
|
||||
moduleNode: DataNode<ModuleData>,
|
||||
projectNode: DataNode<ProjectData>,
|
||||
modelsProvider: IdeModifiableModelsProvider,
|
||||
rootModel: ModifiableRootModel,
|
||||
testScope: Boolean
|
||||
) {
|
||||
val dependeeModuleNodes = getDependeeModuleNodes(moduleNode, projectNode, modelsProvider, testScope)
|
||||
val relevantNodes = dependeeModuleNodes
|
||||
.flatMap { ExternalSystemApiUtil.getChildren(it, GradleSourceSetData.KEY) }
|
||||
.filter {
|
||||
val ktModule = it.kotlinSourceSet?.kotlinModule
|
||||
ktModule != null && ktModule.isTestModule == testScope
|
||||
}
|
||||
val commonSourceSetName = KotlinSourceSet.commonName(testScope)
|
||||
val isAndroidModule = getAndroidModuleModel(moduleNode) != null
|
||||
SmartList<DataNode<GradleSourceSetData>>()
|
||||
.apply {
|
||||
addIfNotNull(
|
||||
(if (isAndroidModule) relevantNodes.firstByPlatformOrNull(KotlinPlatform.ANDROID) else null)
|
||||
?: relevantNodes.firstByPlatformOrNull(KotlinPlatform.JVM)
|
||||
)
|
||||
addIfNotNull(
|
||||
relevantNodes.firstOrNull {
|
||||
it.kotlinSourceSet?.platform == KotlinPlatform.COMMON && it.kotlinSourceSet?.kotlinModule?.name == commonSourceSetName
|
||||
}
|
||||
)
|
||||
}
|
||||
.mapNotNull { modelsProvider.findIdeModule(it.data) }
|
||||
.forEach {
|
||||
addModuleDependencyIfNeeded(rootModel, it, testScope)
|
||||
val dependeeRootModel = modelsProvider.getModifiableRootModel(it)
|
||||
dependeeRootModel.getModuleDependencies(testScope).forEach { transitiveDependee ->
|
||||
addModuleDependencyIfNeeded(rootModel, transitiveDependee, testScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAndroidModuleModel(moduleNode: DataNode<ModuleData>) =
|
||||
ExternalSystemApiUtil.getChildren(moduleNode, AndroidProjectKeys.ANDROID_MODEL).firstOrNull()?.data
|
||||
|
||||
private fun getJavaModuleModel(moduleNode: DataNode<ModuleData>) =
|
||||
ExternalSystemApiUtil.getChildren(moduleNode, AndroidProjectKeys.JAVA_MODULE_MODEL).firstOrNull()?.data
|
||||
|
||||
private fun addSourceRoot(
|
||||
sourceRoot: File,
|
||||
type: JpsModuleSourceRootType<*>,
|
||||
rootModel: ModifiableRootModel,
|
||||
shouldCreateEmptySourceRoots: Boolean
|
||||
) {
|
||||
val parent = findParentContentEntry(sourceRoot, rootModel.contentEntries.stream()) ?: return
|
||||
val url = FilePaths.pathToIdeaUrl(sourceRoot)
|
||||
parent.addSourceFolder(url, type)
|
||||
if (shouldCreateEmptySourceRoots) {
|
||||
ExternalSystemApiUtil.doWriteAction {
|
||||
try {
|
||||
VfsUtil.createDirectoryIfMissing(sourceRoot.path)
|
||||
} catch (e: IOException) {
|
||||
LOG.warn(String.format("Unable to create directory for the path: %s", sourceRoot.path), e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(KotlinAndroidGradleMPPModuleDataService::class.java)
|
||||
}
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.configure
|
||||
|
||||
import com.android.builder.model.AndroidProject
|
||||
import com.android.builder.model.NativeAndroidProject
|
||||
import com.android.tools.idea.IdeInfo
|
||||
import com.android.tools.idea.gradle.project.sync.idea.data.model.ImportedModule
|
||||
import com.android.tools.idea.gradle.project.sync.idea.data.service.AndroidProjectKeys.IMPORTED_MODULE
|
||||
import com.intellij.openapi.externalSystem.model.DataNode
|
||||
import com.intellij.openapi.externalSystem.model.project.ModuleData
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants
|
||||
import com.intellij.openapi.externalSystem.util.Order
|
||||
import org.gradle.tooling.model.idea.IdeaModule
|
||||
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
|
||||
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModelBuilder
|
||||
import org.jetbrains.kotlin.gradle.KotlinPlatform
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinAndroidSourceSetData
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver
|
||||
import org.jetbrains.kotlin.idea.configuration.kotlinSourceSet
|
||||
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
|
||||
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
|
||||
|
||||
@Order(ExternalSystemConstants.UNORDERED - 1)
|
||||
class KotlinAndroidMPPGradleProjectResolver : AbstractProjectResolverExtension() {
|
||||
private val isAndroidProject by lazy {
|
||||
resolverCtx.hasModulesWithModel(AndroidProject::class.java)
|
||||
|| resolverCtx.hasModulesWithModel(NativeAndroidProject::class.java)
|
||||
}
|
||||
|
||||
override fun getToolingExtensionsClasses(): Set<Class<out Any>> {
|
||||
return setOf(KotlinMPPGradleModelBuilder::class.java, Unit::class.java)
|
||||
}
|
||||
|
||||
override fun getExtraProjectModelClasses(): Set<Class<out Any>> {
|
||||
return setOf(KotlinMPPGradleModel::class.java)
|
||||
}
|
||||
|
||||
override fun createModule(gradleModule: IdeaModule, projectDataNode: DataNode<ProjectData>): DataNode<ModuleData> {
|
||||
return super.createModule(gradleModule, projectDataNode).also {
|
||||
initializeModuleData(gradleModule, it, projectDataNode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun populateModuleContentRoots(gradleModule: IdeaModule, ideModule: DataNode<ModuleData>) {
|
||||
super.populateModuleContentRoots(gradleModule, ideModule)
|
||||
if (IdeInfo.getInstance().isAndroidStudio || isAndroidProject) {
|
||||
KotlinMPPGradleProjectResolver.populateContentRoots(gradleModule, ideModule, resolverCtx)
|
||||
// Work around module disposal service which discards modules without accompanying ImportedModule instance
|
||||
for (childNode in ExternalSystemApiUtil.getChildren(ideModule, GradleSourceSetData.KEY)) {
|
||||
if (childNode.kotlinSourceSet == null) continue
|
||||
val moduleName = childNode.data.internalName
|
||||
val importedModule = ImportedModule(gradleModule)
|
||||
importedModuleNameField.set(importedModule, moduleName)
|
||||
ideModule.createChild(IMPORTED_MODULE, importedModule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun populateModuleDependencies(gradleModule: IdeaModule, ideModule: DataNode<ModuleData>, ideProject: DataNode<ProjectData>) {
|
||||
super.populateModuleDependencies(gradleModule, ideModule, ideProject)
|
||||
if (isAndroidProject) {
|
||||
KotlinMPPGradleProjectResolver.populateModuleDependencies(gradleModule, ideProject, ideModule, resolverCtx)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeModuleData(
|
||||
gradleModule: IdeaModule,
|
||||
mainModuleData: DataNode<ModuleData>,
|
||||
projectDataNode: DataNode<ProjectData>
|
||||
) {
|
||||
if (!isAndroidProject) return
|
||||
|
||||
KotlinMPPGradleProjectResolver.initializeModuleData(gradleModule, mainModuleData, projectDataNode, resolverCtx)
|
||||
|
||||
val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) ?: return
|
||||
|
||||
val androidSourceSets = mppModel
|
||||
.targets
|
||||
.asSequence()
|
||||
.flatMap { it.compilations.asSequence() }
|
||||
.filter { it.platform == KotlinPlatform.ANDROID }
|
||||
.mapNotNull { KotlinMPPGradleProjectResolver.createSourceSetInfo(it, gradleModule, resolverCtx) }
|
||||
.toList()
|
||||
mainModuleData.createChild(KotlinAndroidSourceSetData.KEY, KotlinAndroidSourceSetData(androidSourceSets))
|
||||
}
|
||||
|
||||
companion object {
|
||||
val importedModuleNameField by lazy {
|
||||
ImportedModule::class.java.getDeclaredField("myName").apply { isAccessible = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.configure
|
||||
|
||||
import com.android.tools.idea.gradle.project.model.AndroidModuleModel
|
||||
import com.android.tools.idea.gradle.project.sync.setup.post.ModuleSetupStep
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.idea.configuration.compilerArgumentsBySourceSet
|
||||
import org.jetbrains.kotlin.idea.configuration.configureFacetByCompilerArguments
|
||||
import org.jetbrains.kotlin.idea.configuration.sourceSetName
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet
|
||||
|
||||
class KotlinAndroidModuleSetupStep : ModuleSetupStep() {
|
||||
override fun setUpModule(module: Module, progressIndicator: ProgressIndicator?) {
|
||||
val facet = AndroidFacet.getInstance(module) ?: return
|
||||
val androidModel = AndroidModuleModel.get(facet) ?: return
|
||||
val sourceSetName = androidModel.selectedVariant.name
|
||||
if (module.sourceSetName == sourceSetName) return
|
||||
val argsInfo = module.compilerArgumentsBySourceSet?.get(sourceSetName) ?: return
|
||||
val kotlinFacet = KotlinFacet.get(module) ?: return
|
||||
module.sourceSetName = sourceSetName
|
||||
configureFacetByCompilerArguments(kotlinFacet, argsInfo, null)
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.configure
|
||||
|
||||
import com.android.tools.idea.gradle.project.model.AndroidModuleModel
|
||||
import com.intellij.openapi.externalSystem.model.DataNode
|
||||
import com.intellij.openapi.externalSystem.model.Key
|
||||
import com.intellij.openapi.externalSystem.model.ProjectKeys
|
||||
import com.intellij.openapi.externalSystem.model.project.ProjectData
|
||||
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.configuration.GradleProjectImportHandler
|
||||
import org.jetbrains.kotlin.idea.configuration.configureFacetByGradleModule
|
||||
|
||||
class KotlinGradleAndroidModuleModelProjectDataService : AbstractProjectDataService<AndroidModuleModel, Void>() {
|
||||
companion object {
|
||||
val KEY = Key<AndroidModuleModel>(AndroidModuleModel::class.qualifiedName!!, 0)
|
||||
}
|
||||
|
||||
override fun getTargetDataKey() = KEY
|
||||
|
||||
override fun postProcess(
|
||||
toImport: MutableCollection<DataNode<AndroidModuleModel>>,
|
||||
projectData: ProjectData?,
|
||||
project: Project,
|
||||
modelsProvider: IdeModifiableModelsProvider
|
||||
) {
|
||||
super.postProcess(toImport, projectData, project, modelsProvider)
|
||||
for (moduleModelNode in toImport) {
|
||||
val moduleNode = ExternalSystemApiUtil.findParent(moduleModelNode, ProjectKeys.MODULE) ?: continue
|
||||
val moduleData = moduleNode.data
|
||||
val ideModule = modelsProvider.findIdeModule(moduleData) ?: continue
|
||||
val sourceSetName = moduleModelNode.data.selectedVariant.name
|
||||
val kotlinFacet = configureFacetByGradleModule(moduleNode, sourceSetName, ideModule, modelsProvider) ?: continue
|
||||
GradleProjectImportHandler.getInstances(project).forEach { it.importByModule(kotlinFacet, moduleNode) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.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.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
|
||||
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 = AndroidSdkData.getSdkData(androidFacet) ?: 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
|
||||
}
|
||||
}
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.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 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.findExistingInstance(it)
|
||||
}
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.inspection
|
||||
|
||||
import com.android.tools.idea.model.AndroidModuleInfo
|
||||
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)
|
||||
?.let { facet -> AndroidModuleInfo.getInstance(facet) }
|
||||
?.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
|
||||
}
|
||||
}
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* 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.ModuleResourceManagers;
|
||||
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 {
|
||||
ModuleResourceManagers managers = ModuleResourceManagers.getInstance(facet);
|
||||
ResourceManager manager = info.isSystem()
|
||||
? managers.getSystemResourceManager(false)
|
||||
: managers.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;
|
||||
}
|
||||
}
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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 org.jetbrains.android.util.AndroidResourceUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.PsiClass
|
||||
import org.jetbrains.android.util.AndroidUtils
|
||||
import com.android.SdkConstants
|
||||
import org.jetbrains.android.augment.AndroidPsiElementFinder
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
|
||||
fun getReferenceExpression(element: PsiElement?): KtSimpleNameExpression? {
|
||||
return PsiTreeUtil.getParentOfType<KtSimpleNameExpression>(element, KtSimpleNameExpression::class.java)
|
||||
}
|
||||
|
||||
// given 'R.a.b' returns info for all three parts of the expression 'a', 'b', 'R'
|
||||
fun getInfo(
|
||||
referenceExpression: KtSimpleNameExpression,
|
||||
facet: AndroidFacet
|
||||
): AndroidResourceUtil.MyReferredResourceFieldInfo? {
|
||||
val info = getReferredInfo(referenceExpression, facet)
|
||||
if (info != null) return info
|
||||
|
||||
val topMostQualified = referenceExpression.getParentQualified().getParentQualified() ?: return null
|
||||
val selectorCandidate = topMostQualified.selectorExpression as? KtSimpleNameExpression ?: return null
|
||||
return getReferredInfo(selectorCandidate, facet)
|
||||
}
|
||||
|
||||
private fun KtExpression?.getParentQualified(): KtDotQualifiedExpression? {
|
||||
return this?.parent as? KtDotQualifiedExpression
|
||||
}
|
||||
|
||||
// returns info if passed expression is 'b' in 'R.a.b'
|
||||
private fun getReferredInfo(
|
||||
lastPart: KtSimpleNameExpression,
|
||||
facet: AndroidFacet
|
||||
): AndroidResourceUtil.MyReferredResourceFieldInfo? {
|
||||
val resFieldName = lastPart.getReferencedName()
|
||||
if (resFieldName.isEmpty()) return null
|
||||
|
||||
val middlePart = getReceiverAsSimpleNameExpression(lastPart) ?: return null
|
||||
|
||||
val resClassName = middlePart.getReferencedName()
|
||||
if (resClassName.isEmpty()) return null
|
||||
|
||||
val firstPart = getReceiverAsSimpleNameExpression(middlePart) ?: return null
|
||||
|
||||
val resolvedClass = firstPart.mainReference.resolve() as? PsiClass ?: return null
|
||||
|
||||
//the following code is copied from
|
||||
// org.jetbrains.android.util.AndroidResourceUtil.getReferredResourceOrManifestField
|
||||
// (org.jetbrains.android.facet.AndroidFacet, com.intellij.psi.PsiReferenceExpression, java.lang.String, boolean)
|
||||
val classShortName = resolvedClass.name
|
||||
|
||||
val fromManifest = AndroidUtils.MANIFEST_CLASS_NAME == classShortName
|
||||
|
||||
if (!fromManifest && AndroidUtils.R_CLASS_NAME != classShortName) {
|
||||
return null
|
||||
}
|
||||
val qName = resolvedClass.qualifiedName
|
||||
|
||||
if (SdkConstants.CLASS_R == qName || AndroidPsiElementFinder.INTERNAL_R_CLASS_QNAME == qName) {
|
||||
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, facet.module, true, false)
|
||||
}
|
||||
val containingFile = resolvedClass.containingFile ?: return null
|
||||
|
||||
val isFromCorrectFile =
|
||||
if (fromManifest) AndroidResourceUtil.isManifestJavaFile(facet, containingFile)
|
||||
else AndroidResourceUtil.isRJavaFile(facet, containingFile)
|
||||
|
||||
if (!isFromCorrectFile) {
|
||||
return null
|
||||
}
|
||||
|
||||
return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, facet.module, false, fromManifest)
|
||||
}
|
||||
|
||||
private fun getReceiverAsSimpleNameExpression(exp: KtSimpleNameExpression): KtSimpleNameExpression? {
|
||||
val receiver = exp.getReceiverExpression()
|
||||
return when (receiver) {
|
||||
is KtSimpleNameExpression -> {
|
||||
receiver
|
||||
}
|
||||
is KtDotQualifiedExpression -> {
|
||||
receiver.selectorExpression as? KtSimpleNameExpression
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.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
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.quickfix
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.android.inspections.lint.AndroidLintQuickFix
|
||||
import org.jetbrains.android.inspections.lint.AndroidQuickfixContexts
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.codeInsight.surroundWith.statement.KotlinIfSurrounder
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.inspections.findExistingEditor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
|
||||
class AddTargetVersionCheckQuickFix(val api: Int) : AndroidLintQuickFix {
|
||||
|
||||
override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) {
|
||||
val targetExpression = getTargetExpression(startElement)
|
||||
val project = targetExpression?.project ?: return
|
||||
val editor = targetExpression.findExistingEditor() ?: return
|
||||
|
||||
val file = targetExpression.containingFile
|
||||
val documentManager = PsiDocumentManager.getInstance(project)
|
||||
val document = documentManager.getDocument(file) ?: return
|
||||
|
||||
if (!FileModificationService.getInstance().prepareFileForWrite(file)) {
|
||||
return
|
||||
}
|
||||
|
||||
val surrounder = getSurrounder(targetExpression, "\"VERSION.SDK_INT < ${getVersionField(api, false)}\"")
|
||||
val conditionRange = surrounder.surroundElements(project, editor, arrayOf(targetExpression)) ?: return
|
||||
val conditionText = "android.os.Build.VERSION.SDK_INT >= ${getVersionField(api, true)}"
|
||||
document.replaceString(conditionRange.startOffset, conditionRange.endOffset, conditionText)
|
||||
documentManager.commitDocument(document)
|
||||
|
||||
ShortenReferences.DEFAULT.process(documentManager.getPsiFile(document) as KtFile,
|
||||
conditionRange.startOffset,
|
||||
conditionRange.startOffset + conditionText.length)
|
||||
}
|
||||
|
||||
override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean =
|
||||
getTargetExpression(startElement) != null
|
||||
|
||||
override fun getName(): String = "Surround with if (VERSION.SDK_INT >= VERSION_CODES.${getVersionField(api, false)}) { ... }"
|
||||
|
||||
private fun getTargetExpression(element: PsiElement): KtElement? {
|
||||
var current = PsiTreeUtil.getParentOfType(element, KtExpression::class.java)
|
||||
while (current != null) {
|
||||
if (current.parent is KtBlockExpression ||
|
||||
current.parent is KtContainerNode ||
|
||||
current.parent is KtWhenEntry ||
|
||||
current.parent is KtFunction ||
|
||||
current.parent is KtPropertyAccessor ||
|
||||
current.parent is KtProperty ||
|
||||
current.parent is KtReturnExpression ||
|
||||
current.parent is KtDestructuringDeclaration) {
|
||||
break
|
||||
}
|
||||
current = PsiTreeUtil.getParentOfType(current, KtExpression::class.java, true)
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
private fun getSurrounder(element: KtElement, todoText: String?): KotlinIfSurrounder {
|
||||
val used = element.analyze(BodyResolveMode.PARTIAL_WITH_CFA)[BindingContext.USED_AS_EXPRESSION, element] ?: false
|
||||
return if (used) {
|
||||
object : KotlinIfSurrounder() {
|
||||
override fun getCodeTemplate(): String = "if (a) { \n} else {\nTODO(${todoText ?: ""})\n}"
|
||||
}
|
||||
} else {
|
||||
KotlinIfSurrounder()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.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
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.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"
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.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)
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.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)
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.android.quickfix
|
||||
|
||||
import com.intellij.codeInspection.InspectionProfileEntry
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.PathUtil
|
||||
import org.jetbrains.android.inspections.lint.AndroidLintInspectionBase
|
||||
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.io.File
|
||||
|
||||
|
||||
abstract class AbstractAndroidLintQuickfixTest : KotlinAndroidTestCase() {
|
||||
|
||||
override fun setUp() {
|
||||
AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
fun doTest(path: String) {
|
||||
val fileText = FileUtil.loadFile(File(path), true)
|
||||
val intentionText = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// INTENTION_TEXT: ") ?: error("Empty intention text")
|
||||
val mainInspectionClassName = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// INSPECTION_CLASS: ") ?: error("Empty inspection class name")
|
||||
val dependency = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// DEPENDENCY: ")
|
||||
val intentionAvailable = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// INTENTION_NOT_AVAILABLE")
|
||||
|
||||
val inspection = Class.forName(mainInspectionClassName).newInstance() as InspectionProfileEntry
|
||||
myFixture.enableInspections(inspection)
|
||||
|
||||
val sourceFile = myFixture.copyFileToProject(path, "src/${PathUtil.getFileName(path)}")
|
||||
myFixture.configureFromExistingVirtualFile(sourceFile)
|
||||
|
||||
if (dependency != null) {
|
||||
val (dependencyFile, dependencyTargetPath) = dependency.split(" -> ").map(String::trim)
|
||||
myFixture.copyFileToProject("${PathUtil.getParentPath(path)}/$dependencyFile", "src/$dependencyTargetPath")
|
||||
}
|
||||
|
||||
if (intentionAvailable) {
|
||||
val intention = myFixture.getAvailableIntention(intentionText) ?: error("Failed to find intention")
|
||||
myFixture.launchAction(intention)
|
||||
myFixture.checkResultByFile(path + ".expected")
|
||||
}
|
||||
else {
|
||||
assertNull("Intention should not be available", myFixture.availableIntentions.find { it.text == intentionText })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user