Skip to content

Commit 2f859f6

Browse files
committed
fix KotlinAndroidAddStringResource: correct generic extension function processing + minor refactoring
1 parent 2ffb0ab commit 2f859f6

File tree

8 files changed

+56
-24
lines changed

8 files changed

+56
-24
lines changed

idea/idea-android/idea-android.iml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919
<orderEntry type="module" module-name="frontend" />
2020
<orderEntry type="module" module-name="frontend.java" />
2121
<orderEntry type="module" module-name="tests-common" scope="TEST" />
22+
<orderEntry type="module" module-name="idea-core" />
2223
</component>
2324
</module>

idea/idea-android/src/org/jetbrains/kotlin/android/intentions/KotlinAndroidAddStringResource.kt

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,22 @@ import com.intellij.CommonBundle
2121
import com.intellij.codeInsight.template.*
2222
import com.intellij.codeInsight.template.impl.*
2323
import com.intellij.codeInsight.template.macro.VariableOfTypeMacro
24-
import com.intellij.openapi.application.Application
2524
import com.intellij.openapi.application.ApplicationManager
2625
import com.intellij.openapi.command.undo.UndoUtil
2726
import com.intellij.openapi.editor.Editor
2827
import com.intellij.openapi.module.Module
2928
import com.intellij.openapi.ui.Messages
3029
import com.intellij.psi.*
31-
import com.intellij.psi.codeStyle.JavaCodeStyleManager
3230
import com.intellij.psi.util.PsiTreeUtil
3331
import org.jetbrains.android.actions.CreateXmlResourceDialog
3432
import org.jetbrains.android.facet.AndroidFacet
3533
import org.jetbrains.android.util.AndroidBundle
3634
import org.jetbrains.android.util.AndroidResourceUtil
3735
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
38-
import org.jetbrains.kotlin.descriptors.ClassDescriptor
39-
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
36+
import org.jetbrains.kotlin.descriptors.*
4037
import org.jetbrains.kotlin.idea.caches.resolve.analyze
4138
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
39+
import org.jetbrains.kotlin.idea.core.ShortenReferences
4240
import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention
4341
import org.jetbrains.kotlin.psi.*
4442
import org.jetbrains.kotlin.resolve.BindingContext
@@ -48,15 +46,24 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
4846

4947
class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTemplateEntry>(KtLiteralStringTemplateEntry::class.java,
5048
"Extract string resource") {
51-
private val GET_STRING_METHOD = "getString"
52-
private val EXTRACT_RESOURCE_DIALOG_TITLE = "Extract Resource"
53-
private val PACKAGE_NOT_FOUND_ERROR = "package.not.found.error"
49+
private companion object {
50+
private val CLASS_CONTEXT = "android.content.Context"
51+
private val CLASS_FRAGMENT = "android.app.Fragment"
52+
private val CLASS_SUPPORT_FRAGMENT = "android.support.v4.app.Fragment"
53+
private val CLASS_VIEW = "android.view.View"
54+
55+
private val GET_STRING_METHOD = "getString"
56+
private val EXTRACT_RESOURCE_DIALOG_TITLE = "Extract Resource"
57+
private val PACKAGE_NOT_FOUND_ERROR = "package.not.found.error"
58+
}
5459

5560
override fun isApplicableTo(element: KtLiteralStringTemplateEntry, caretOffset: Int): Boolean {
5661
if (AndroidFacet.getInstance(element.containingFile) == null) {
5762
return false
5863
}
5964

65+
// Should not be available to strings with template expressions
66+
// only to strings with single KtLiteralStringTemplateEntry inside
6067
return element.parent.children.size == 1
6168
}
6269

@@ -70,7 +77,7 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
7077
throw IllegalStateException("This intention requires android facet.")
7178
}
7279

73-
val file = element.containingFile
80+
val file = element.containingFile as KtFile
7481
val project = file.project
7582

7683
val manifestPackage = getManifestPackage(facet)
@@ -79,8 +86,7 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
7986
return
8087
}
8188

82-
val parameters = getCreateXmlResourceParameters(facet.module, element) ?:
83-
return
89+
val parameters = getCreateXmlResourceParameters(facet.module, element) ?: return
8490

8591
if (!AndroidResourceUtil.createValueResource(facet.module, parameters.name, ResourceType.STRING,
8692
parameters.fileName, parameters.directoryNames, parameters.value)) {
@@ -93,7 +99,6 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
9399
}
94100

95101
private fun getCreateXmlResourceParameters(module: Module, element: KtLiteralStringTemplateEntry): CreateXmlResourceParameters? {
96-
97102
val stringValue = element.text
98103

99104
val showDialog = !ApplicationManager.getApplication().isUnitTestMode
@@ -111,7 +116,7 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
111116
dialog.dirNames)
112117
}
113118

114-
private fun createResourceReference(module: Module, editor: Editor, file: PsiFile, element: PsiElement, aPackage: String,
119+
private fun createResourceReference(module: Module, editor: Editor, file: KtFile, element: PsiElement, aPackage: String,
115120
resName: String, resType: ResourceType) {
116121
val rFieldName = AndroidResourceUtil.getRJavaFieldName(resName)
117122
val fieldName = "$aPackage.R.$resType.$rFieldName"
@@ -123,7 +128,7 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
123128
else {
124129
template = TemplateImpl("", "\$context\$.$GET_STRING_METHOD($fieldName)", "")
125130
val marker = MacroCallNode(VariableOfTypeMacro())
126-
marker.addParameter(ConstantNode("android.content.Context"))
131+
marker.addParameter(ConstantNode(CLASS_CONTEXT))
127132
template.addVariable("context", marker, ConstantNode("context"), true)
128133
}
129134

@@ -136,18 +141,18 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
136141

137142
TemplateManager.getInstance(module.project).startTemplate(editor, template, false, null, object : TemplateEditingAdapter() {
138143
override fun waitingForInput(template: Template?) {
139-
JavaCodeStyleManager.getInstance(module.project).shortenClassReferences(file, marker.startOffset, marker.endOffset)
144+
ShortenReferences.DEFAULT.process(file, marker.startOffset, marker.endOffset)
140145
}
141146

142147
override fun beforeTemplateFinished(state: TemplateState?, template: Template?) {
143-
JavaCodeStyleManager.getInstance(module.project).shortenClassReferences(file, marker.startOffset, marker.endOffset)
148+
ShortenReferences.DEFAULT.process(file, marker.startOffset, marker.endOffset)
144149
}
145150
})
146151
}
147152

148153
private fun needContextReceiver(element: PsiElement): Boolean {
149-
val classesWithGetSting = listOf("android.content.Context", "android.app.Fragment", "android.support.v4.app.Fragment")
150-
val viewClass = listOf("android.view.View")
154+
val classesWithGetSting = listOf(CLASS_CONTEXT, CLASS_FRAGMENT, CLASS_SUPPORT_FRAGMENT)
155+
val viewClass = listOf(CLASS_VIEW)
151156
var parent = PsiTreeUtil.findFirstParent(element, true) { it is KtClassOrObject || it is KtFunction || it is KtLambdaExpression }
152157

153158
while (parent != null) {
@@ -184,7 +189,7 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
184189

185190
private fun KtFunction.isSubclassExtensionOfAny(baseClasses: Collection<String>): Boolean {
186191
val descriptor = resolveToDescriptor() as FunctionDescriptor
187-
val extendedTypeDescriptor = descriptor.extensionReceiverParameter?.type?.constructor?.declarationDescriptor as? ClassDescriptor
192+
val extendedTypeDescriptor = descriptor.extensionReceiverParameter?.type?.constructor?.declarationDescriptor
188193
return extendedTypeDescriptor != null && baseClasses.any { extendedTypeDescriptor.isSubclassOf(it) }
189194
}
190195

@@ -197,7 +202,7 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
197202
}
198203

199204
val extendedTypeDescriptor = type.arguments.first().type.constructor.declarationDescriptor
200-
if (extendedTypeDescriptor is ClassDescriptor) {
205+
if (extendedTypeDescriptor != null) {
201206
return baseClasses.any { extendedTypeDescriptor.isSubclassOf(it) }
202207
}
203208

@@ -210,9 +215,11 @@ class KotlinAndroidAddStringResource : SelfTargetingIntention<KtLiteralStringTem
210215
return baseClasses.any { declarationDescriptor?.isSubclassOf(it) ?: false }
211216
}
212217

213-
private fun ClassDescriptor.isSubclassOf(className: String): Boolean {
214-
return fqNameSafe.asString() == className || defaultType.constructor.supertypes.any {
215-
(it.constructor.declarationDescriptor as? ClassDescriptor)?.isSubclassOf(className) ?: false
216-
}
218+
private fun ClassifierDescriptor.isSubclassOf(className: String): Boolean {
219+
return fqNameSafe.asString() == className || isStrictSubclassOf(className)
220+
}
221+
222+
private fun ClassifierDescriptor.isStrictSubclassOf(className: String) = defaultType.constructor.supertypes.any {
223+
it.constructor.declarationDescriptor?.isSubclassOf(className) ?: false
217224
}
218225
}

idea/idea-android/tests/org/jetbrains/kotlin/android/intentions/AndroidResourceIntentionTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ public void testKotlinAndroidAddStringResource_function_Function() throws Except
6565
doTest(fileName);
6666
}
6767

68+
@TestMetadata("kotlinAndroidAddStringResource/genericContextExtensionFunction/genericContextExtensionFunction.test")
69+
public void testKotlinAndroidAddStringResource_genericContextExtensionFunction_GenericContextExtensionFunction() throws Exception {
70+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/resourceIntentions/kotlinAndroidAddStringResource/genericContextExtensionFunction/genericContextExtensionFunction.test");
71+
doTest(fileName);
72+
}
73+
6874
@TestMetadata("kotlinAndroidAddStringResource/innerClassInActivity/innerClassInActivity.test")
6975
public void testKotlinAndroidAddStringResource_innerClassInActivity_InnerClassInActivity() throws Exception {
7076
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/resourceIntentions/kotlinAndroidAddStringResource/innerClassInActivity/innerClassInActivity.test");
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<html>
22
<body>
3-
This intention extracts string literal to android resources.
3+
This intention extracts string literal to Android resources.
44
</body>
55
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.myapp
2+
3+
import android.content.Context
4+
5+
fun <T: Context> T.getText(): String? = getString(R.string.resource_id)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<resources>
2+
<string name="resource_id">some text</string>
3+
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"rFile": "../../R.kt",
3+
"resDirectory": "../../res",
4+
"intentionClass": "org.jetbrains.kotlin.android.intentions.KotlinAndroidAddStringResource"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.myapp
2+
3+
import android.content.Context
4+
5+
fun <T: Context> T.getText(): String? = "some <caret>text"

0 commit comments

Comments
 (0)