Android: Add intention to generate View constructor convention
Fixes #KT-15150
This commit is contained in:
committed by
Vyacheslav Gerasimov
parent
f74c0950d2
commit
ca86dbee72
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.diagnostics.Errors.SUPERTYPE_NOT_INITIALIZED
|
||||
import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor
|
||||
import org.jetbrains.kotlin.idea.quickfix.QuickFixes
|
||||
|
||||
class AndroidQuickFixRegistrar : QuickFixContributor {
|
||||
override fun registerQuickFixes(quickFixes: QuickFixes) {
|
||||
quickFixes.register(SUPERTYPE_NOT_INITIALIZED, KotlinAndroidViewConstructorFix)
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.SUPERTYPE_NOT_INITIALIZED
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.idea.util.addAnnotation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClass
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
|
||||
class KotlinAndroidViewConstructorFix(element: KtSuperTypeEntry) : KotlinQuickFixAction<KtSuperTypeEntry>(element) {
|
||||
|
||||
override fun getText() = "Add Android View constructors using '@JvmOverloads'"
|
||||
override fun getFamilyName() = text
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
if (AndroidFacet.getInstance(file) == null) return false
|
||||
return super.isAvailable(project, editor, file)
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val element = element ?: return
|
||||
val ktClass = element.containingClass() ?: return
|
||||
|
||||
val factory = KtPsiFactory(element)
|
||||
|
||||
val newPrimaryConstructor = factory.createDeclarationByPattern<KtClass>(
|
||||
"class A constructor(\n $0, $1, $2\n)",
|
||||
factory.createParameter("context: android.content.Context"),
|
||||
factory.createParameter("attrs: android.util.AttributeSet? = null"),
|
||||
factory.createParameter("defStyleAttr: Int = 0")
|
||||
).primaryConstructor ?: return
|
||||
|
||||
val primaryConstructor = ktClass.createPrimaryConstructorIfAbsent().replaced(newPrimaryConstructor)
|
||||
primaryConstructor.valueParameterList?.let { ShortenReferences.DEFAULT.process(it) }
|
||||
|
||||
primaryConstructor.addAnnotation(fqNameAnnotation, whiteSpaceText = " ")
|
||||
|
||||
element.replace(factory.createSuperTypeCallEntry(element.text + "(context, attrs, defStyleAttr)"))
|
||||
}
|
||||
|
||||
companion object Factory : KotlinSingleIntentionActionFactory() {
|
||||
|
||||
private val fqNameAnnotation = FqName("kotlin.jvm.JvmOverloads")
|
||||
|
||||
private val requiredConstructorParameterTypes =
|
||||
listOf("android.content.Context", "android.util.AttributeSet", "kotlin.Int")
|
||||
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val superTypeEntry = SUPERTYPE_NOT_INITIALIZED.cast(diagnostic).psiElement
|
||||
|
||||
val ktClass = superTypeEntry.containingClass() ?: return null
|
||||
if (ktClass.primaryConstructor != null) return null
|
||||
|
||||
val context = superTypeEntry.analyze()
|
||||
val type = superTypeEntry.typeReference?.let { context[BindingContext.TYPE, it] } ?: return null
|
||||
|
||||
if (!type.isAndroidView() && type.supertypes().none { it.isAndroidView() }) return null
|
||||
|
||||
val names = type.constructorParameters() ?: return null
|
||||
if (requiredConstructorParameterTypes !in names) return null
|
||||
|
||||
return KotlinAndroidViewConstructorFix(superTypeEntry)
|
||||
}
|
||||
|
||||
private fun KotlinType.getFqNameAsString() = constructor.declarationDescriptor?.fqNameUnsafe?.asString()
|
||||
|
||||
private fun KotlinType.isAndroidView() = getFqNameAsString() == "android.view.View"
|
||||
|
||||
private fun KotlinType.constructorParameters(): List<List<String?>>? {
|
||||
val classDescriptor = constructor.declarationDescriptor as? ClassDescriptor ?: return null
|
||||
return classDescriptor.constructors.map {
|
||||
it.valueParameters.map { it.type.getFqNameAsString() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -50,4 +50,25 @@ public class AndroidQuickFixMultiFileTestGenerated extends AbstractAndroidQuickF
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/android/quickfix/viewConstructor")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ViewConstructor extends AbstractAndroidQuickFixMultiFileTest {
|
||||
public void testAllFilesPresentInViewConstructor() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/quickfix/viewConstructor"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("indirect.before.Main.kt")
|
||||
public void testIndirect() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/quickfix/viewConstructor/indirect.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.before.Main.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/quickfix/viewConstructor/simple.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<simpleNameReferenceExtension implementation="org.jetbrains.kotlin.android.synthetic.idea.AndroidSimpleNameReferenceExtension"/>
|
||||
<kotlinIndicesHelperExtension implementation="org.jetbrains.kotlin.android.synthetic.idea.AndroidIndicesHelperExtension"/>
|
||||
|
||||
<quickFixContributor implementation="org.jetbrains.kotlin.android.quickfix.AndroidQuickFixRegistrar"/>
|
||||
<projectConfigurator implementation="org.jetbrains.kotlin.android.configure.KotlinAndroidGradleModuleConfigurator"/>
|
||||
</extensions>
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: This type has a constructor, and thus must be initialized here
|
||||
// WITH_RUNTIME
|
||||
|
||||
package com.myapp.activity
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.TextView
|
||||
|
||||
class Foo @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : TextView<caret>(context, attrs, defStyleAttr)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: The type has a constructor, and thus must be initialized here
|
||||
|
||||
package android.view
|
||||
|
||||
import android.util.AttributeSet
|
||||
import android.content.Context
|
||||
|
||||
public open class View {
|
||||
constructor(context: Context)
|
||||
constructor(context: Context, attrs: AttributeSet?)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
|
||||
}
|
||||
|
||||
public open class TextView : View {
|
||||
constructor(context: Context) super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) super(context, attrs, defStyleAttr)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: The type has a constructor, and thus must be initialized here
|
||||
|
||||
package android.content
|
||||
|
||||
public class Context
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: The type has a constructor, and thus must be initialized here
|
||||
|
||||
package android.util
|
||||
|
||||
public class AttributeSet
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: This type has a constructor, and thus must be initialized here
|
||||
// WITH_RUNTIME
|
||||
|
||||
package com.myapp.activity
|
||||
|
||||
import android.view.TextView
|
||||
|
||||
class Foo : TextView<caret>
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: This type has a constructor, and thus must be initialized here
|
||||
// WITH_RUNTIME
|
||||
|
||||
package com.myapp.activity
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
|
||||
class Foo @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : View<caret>(context, attrs, defStyleAttr)
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: The type has a constructor, and thus must be initialized here
|
||||
|
||||
package android.view
|
||||
|
||||
import android.util.AttributeSet
|
||||
import android.content.Context
|
||||
|
||||
public open class View {
|
||||
constructor(context: Context)
|
||||
constructor(context: Context, attrs: AttributeSet?)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: The type has a constructor, and thus must be initialized here
|
||||
|
||||
package android.content
|
||||
|
||||
public class Context
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: The type has a constructor, and thus must be initialized here
|
||||
|
||||
package android.util
|
||||
|
||||
public class AttributeSet
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Add Android View constructors using '@JvmOverloads'" "true"
|
||||
// ERROR: This type has a constructor, and thus must be initialized here
|
||||
// WITH_RUNTIME
|
||||
|
||||
package com.myapp.activity
|
||||
|
||||
import android.view.View
|
||||
|
||||
class Foo : View<caret>
|
||||
Reference in New Issue
Block a user