181: Fix compatibility after AS 3.0 was merged to 181

This commit is contained in:
Vyacheslav Gerasimov
2017-12-06 22:59:16 +03:00
committed by Nikolay Krasko
parent e322c2d70b
commit f31b56a936
162 changed files with 979 additions and 0 deletions
@@ -0,0 +1,137 @@
/*
* 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) }
}
}
}
@@ -0,0 +1,270 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.android;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ResourceResolver;
import com.android.resources.ResourceType;
import com.android.tools.idea.configurations.Configuration;
import com.android.tools.idea.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 {
public 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;
}
}
}
@@ -0,0 +1,73 @@
/*
* 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
}
}
@@ -0,0 +1,258 @@
/*
* 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)
}
}
@@ -0,0 +1,83 @@
/*
* 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
}
}
@@ -0,0 +1,149 @@
/*
* 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;
}
}
+9
View File
@@ -0,0 +1,9 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<externalAnnotator language="kotlin" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintExternalAnnotator"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.uast">
<uastLanguagePlugin implementation="org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin"/>
</extensions>
</idea-plugin>

Some files were not shown because too many files have changed in this diff Show More