Add intentions for registering Android components in manifest
Activity, Service, BroadcastReceiver #KT-17389 Fixed
This commit is contained in:
@@ -23,5 +23,6 @@
|
||||
<orderEntry type="module" module-name="lint-idea" scope="TEST" />
|
||||
<orderEntry type="library" name="uast-java" level="project" />
|
||||
<orderEntry type="library" name="kotlin-reflect" level="project" />
|
||||
<orderEntry type="module" module-name="light-classes" />
|
||||
</component>
|
||||
</module>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.intention
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import org.jetbrains.android.dom.manifest.Manifest
|
||||
import org.jetbrains.android.facet.AndroidFacet
|
||||
import org.jetbrains.kotlin.android.insideBody
|
||||
import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAbstract
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isPrivate
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isProtected
|
||||
|
||||
|
||||
abstract class AbstractRegisterComponentAction(text: String) : SelfTargetingIntention<KtClass>(KtClass::class.java, text) {
|
||||
|
||||
abstract fun isApplicableTo(element: KtClass, manifest: Manifest): Boolean
|
||||
|
||||
abstract fun applyTo(element: KtClass, manifest: Manifest)
|
||||
|
||||
final override fun isApplicableTo(element: KtClass, caretOffset: Int): Boolean {
|
||||
val androidFacet = AndroidFacet.getInstance(element.containingFile) ?: return false
|
||||
val manifest = androidFacet.manifest ?: return false
|
||||
return !element.isLocal &&
|
||||
!element.isAbstract() &&
|
||||
!element.isPrivate() &&
|
||||
!element.isProtected() &&
|
||||
!element.isInner() &&
|
||||
!element.name.isNullOrEmpty() &&
|
||||
!element.insideBody(caretOffset) &&
|
||||
isApplicableTo(element, manifest)
|
||||
}
|
||||
|
||||
final override fun applyTo(element: KtClass, editor: Editor?) {
|
||||
AndroidFacet.getInstance(element.containingFile)?.manifest?.let {
|
||||
applyTo(element, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.intention
|
||||
|
||||
import com.android.SdkConstants
|
||||
import org.jetbrains.android.dom.manifest.Manifest
|
||||
import org.jetbrains.kotlin.android.isSubclassOf
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
|
||||
|
||||
class AddActivityToManifest : AbstractRegisterComponentAction("Add activity to manifest") {
|
||||
override fun isApplicableTo(element: KtClass, manifest: Manifest): Boolean =
|
||||
element.isSubclassOfActivity() && !element.isRegisteredActivity(manifest)
|
||||
|
||||
override fun applyTo(element: KtClass, manifest: Manifest) = runWriteAction {
|
||||
val psiClass = element.toLightClass() ?: return@runWriteAction
|
||||
manifest.application.addActivity().activityClass.value = psiClass
|
||||
}
|
||||
|
||||
private fun KtClass.isRegisteredActivity(manifest: Manifest) = manifest.application.activities.any {
|
||||
it.activityClass.value?.qualifiedName == fqName?.asString()
|
||||
}
|
||||
|
||||
private fun KtClass.isSubclassOfActivity() =
|
||||
(descriptor as? ClassDescriptor)?.defaultType?.isSubclassOf(FqName(SdkConstants.CLASS_ACTIVITY), true) ?: false
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.intention
|
||||
|
||||
import com.android.SdkConstants
|
||||
import org.jetbrains.android.dom.manifest.Manifest
|
||||
import org.jetbrains.kotlin.android.isSubclassOf
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
|
||||
|
||||
class AddBroadcastReceiverToManifest : AbstractRegisterComponentAction("Add broadcast receiver to manifest") {
|
||||
override fun isApplicableTo(element: KtClass, manifest: Manifest): Boolean =
|
||||
element.isSubclassOfBroadcastReceiver() && !element.isRegisteredBroadcastReceiver(manifest)
|
||||
|
||||
override fun applyTo(element: KtClass, manifest: Manifest) = runWriteAction {
|
||||
val psiClass = element.toLightClass() ?: return@runWriteAction
|
||||
manifest.application.addReceiver().receiverClass.value = psiClass
|
||||
}
|
||||
|
||||
private fun KtClass.isRegisteredBroadcastReceiver(manifest: Manifest) = manifest.application.receivers.any {
|
||||
it.receiverClass.value?.qualifiedName == fqName?.asString()
|
||||
}
|
||||
|
||||
private fun KtClass.isSubclassOfBroadcastReceiver() =
|
||||
(descriptor as? ClassDescriptor)?.defaultType?.isSubclassOf(FqName(SdkConstants.CLASS_BROADCASTRECEIVER), true) ?: false
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.intention
|
||||
|
||||
import com.android.SdkConstants
|
||||
import org.jetbrains.android.dom.manifest.Manifest
|
||||
import org.jetbrains.kotlin.android.isSubclassOf
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
|
||||
|
||||
class AddServiceToManifest : AbstractRegisterComponentAction("Add service to manifest") {
|
||||
override fun isApplicableTo(element: KtClass, manifest: Manifest): Boolean =
|
||||
element.isSubclassOfService() && !element.isRegisteredService(manifest)
|
||||
|
||||
override fun applyTo(element: KtClass, manifest: Manifest) = runWriteAction {
|
||||
val psiClass = element.toLightClass() ?: return@runWriteAction
|
||||
manifest.application.addService().serviceClass.value = psiClass
|
||||
}
|
||||
|
||||
private fun KtClass.isRegisteredService(manifest: Manifest) = manifest.application.services.any {
|
||||
it.serviceClass.value?.qualifiedName == fqName?.asString()
|
||||
}
|
||||
|
||||
private fun KtClass.isSubclassOfService() =
|
||||
(descriptor as? ClassDescriptor)?.defaultType?.isSubclassOf(FqName(SdkConstants.CLASS_SERVICE), true) ?: false
|
||||
}
|
||||
+18
-1
@@ -37,12 +37,19 @@ abstract class AbstractAndroidIntentionTest : KotlinAndroidTestCase() {
|
||||
|
||||
val notAvailable = InTextDirectivesUtils.isDirectiveDefined(testFileText, "// NOT_AVAILABLE")
|
||||
val withRuntime = InTextDirectivesUtils.isDirectiveDefined(testFileText, "// WITH_RUNTIME")
|
||||
val checkManifest = InTextDirectivesUtils.isDirectiveDefined(testFileText, "// CHECK_MANIFEST")
|
||||
|
||||
try {
|
||||
if (withRuntime) {
|
||||
ConfigLibraryUtil.configureKotlinRuntime(myFixture.module)
|
||||
}
|
||||
|
||||
val customManifestPath = "${testFile.path}.AndroidManifest.xml"
|
||||
if (FileUtil.exists("$testDataPath/$customManifestPath")) {
|
||||
deleteManifest()
|
||||
myFixture.copyFileToProject(customManifestPath, SdkConstants.FN_ANDROID_MANIFEST_XML)
|
||||
}
|
||||
|
||||
val sourceFile = myFixture.copyFileToProject(path, "src/${PathUtil.getFileName(path)}")
|
||||
myFixture.configureFromExistingVirtualFile(sourceFile)
|
||||
|
||||
@@ -55,8 +62,18 @@ abstract class AbstractAndroidIntentionTest : KotlinAndroidTestCase() {
|
||||
error("Intention is not available!")
|
||||
}
|
||||
|
||||
if (notAvailable) {
|
||||
error("Intention should not be available!")
|
||||
}
|
||||
|
||||
myFixture.launchAction(intention)
|
||||
myFixture.checkResultByFile("$path.expected")
|
||||
|
||||
if (checkManifest) {
|
||||
myFixture.checkResultByFile("AndroidManifest.xml", "$customManifestPath.expected", true)
|
||||
}
|
||||
else {
|
||||
myFixture.checkResultByFile("$path.expected")
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (withRuntime) {
|
||||
|
||||
+207
@@ -36,6 +36,213 @@ public class AndroidIntentionTestGenerated extends AbstractAndroidIntentionTest
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/android/intention/addActivityToManifest")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddActivityToManifest extends AbstractAndroidIntentionTest {
|
||||
@TestMetadata("abstract.kt")
|
||||
public void testAbstract() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/abstract.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAddActivityToManifest() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention/addActivityToManifest"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("alreadyExists.kt")
|
||||
public void testAlreadyExists() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/alreadyExists.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner.kt")
|
||||
public void testInner() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/inner.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("insideBody.kt")
|
||||
public void testInsideBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/insideBody.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("local.kt")
|
||||
public void testLocal() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/local.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nested.kt")
|
||||
public void testNested() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/nested.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("notActivity.kt")
|
||||
public void testNotActivity() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/notActivity.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("private.kt")
|
||||
public void testPrivate() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/private.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("protected.kt")
|
||||
public void testProtected() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/protected.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addActivityToManifest/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddBroadcastReceiverToManifest extends AbstractAndroidIntentionTest {
|
||||
@TestMetadata("abstract.kt")
|
||||
public void testAbstract() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/abstract.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAddBroadcastReceiverToManifest() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention/addBroadcastReceiverToManifest"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("alreadyExists.kt")
|
||||
public void testAlreadyExists() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/alreadyExists.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner.kt")
|
||||
public void testInner() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/inner.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("insideBody.kt")
|
||||
public void testInsideBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/insideBody.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("local.kt")
|
||||
public void testLocal() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/local.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nested.kt")
|
||||
public void testNested() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/nested.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("notReceiver.kt")
|
||||
public void testNotReceiver() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/notReceiver.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("private.kt")
|
||||
public void testPrivate() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/private.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("protected.kt")
|
||||
public void testProtected() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/protected.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addBroadcastReceiverToManifest/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/android/intention/addServiceToManifest")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddServiceToManifest extends AbstractAndroidIntentionTest {
|
||||
@TestMetadata("abstract.kt")
|
||||
public void testAbstract() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/abstract.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAddServiceToManifest() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention/addServiceToManifest"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("alreadyExists.kt")
|
||||
public void testAlreadyExists() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/alreadyExists.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner.kt")
|
||||
public void testInner() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/inner.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("insideBody.kt")
|
||||
public void testInsideBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/insideBody.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("local.kt")
|
||||
public void testLocal() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/local.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nested.kt")
|
||||
public void testNested() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/nested.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("notService.kt")
|
||||
public void testNotService() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/notService.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("private.kt")
|
||||
public void testPrivate() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/private.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("protected.kt")
|
||||
public void testProtected() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/protected.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/addServiceToManifest/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/android/intention/implementParcelable")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user