Skip to content

Commit 6a51c63

Browse files
authored
[IJ plugin] Improve compat->operationBased migration (#5134)
1 parent f4bb618 commit 6a51c63

File tree

11 files changed

+154
-46
lines changed

11 files changed

+154
-46
lines changed

.idea/codeStyles/Project.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/compattooperationbased/CompatToOperationBasedCodegenMigrationProcessor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CompatToOperationBasedCodegenMigrationProcessor(project: Project) : Apollo
1717

1818
override val migrationItems = listOf(
1919
UpdateCodegenInBuildKts,
20-
RemoveFragmentsField,
2120
ReworkInlineFragmentFields,
21+
RemoveFragmentsField,
2222
)
2323
}
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
package com.apollographql.ijplugin.refactoring.migration.compattooperationbased.item
22

3-
import com.apollographql.ijplugin.refactoring.findInheritorsOfClass
43
import com.apollographql.ijplugin.refactoring.findReferences
54
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItem
65
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItemUsageInfo
76
import com.apollographql.ijplugin.refactoring.migration.item.toMigrationItemUsageInfo
87
import com.intellij.openapi.project.Project
98
import com.intellij.psi.PsiMigration
109
import com.intellij.psi.search.GlobalSearchScope
11-
import org.jetbrains.kotlin.asJava.classes.KtLightClassBase
10+
import com.intellij.psi.util.childrenOfType
11+
import org.jetbrains.kotlin.psi.KtCallExpression
1212
import org.jetbrains.kotlin.psi.KtClass
13-
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
13+
import org.jetbrains.kotlin.psi.KtQualifiedExpression
14+
import org.jetbrains.kotlin.psi.KtValueArgument
15+
import org.jetbrains.kotlin.psi.KtValueArgumentList
16+
import org.jetbrains.kotlin.psi.KtValueArgumentName
1417
import org.jetbrains.kotlin.psi.psiUtil.findPropertyByName
1518

1619
object RemoveFragmentsField : MigrationItem() {
1720
override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List<MigrationItemUsageInfo> {
18-
val operationInheritors = findInheritorsOfClass(project, "com.apollographql.apollo3.api.Operation").filterIsInstance<KtLightClassBase>()
19-
val fragmentDataInheritors = findInheritorsOfClass(project, "com.apollographql.apollo3.api.Fragment.Data").filterIsInstance<KtLightClassBase>()
20-
val allModels = (operationInheritors + fragmentDataInheritors).flatMap {
21-
it.kotlinOrigin?.body?.declarations.orEmpty().filterIsInstance<KtClass>()
22-
}
21+
val allModels: List<KtClass> = findAllModels(project)
2322
val fragmentsProperties = allModels.mapNotNull { model ->
2423
model.findPropertyByName("fragments")
2524
}
@@ -28,15 +27,25 @@ object RemoveFragmentsField : MigrationItem() {
2827
}
2928
return references
3029
.mapNotNull {
31-
val parent = it.parent as? KtDotQualifiedExpression ?: return@mapNotNull null
32-
when {
33-
// fragments.x
34-
parent.receiverExpression.text == "fragments" -> {
35-
parent.toMigrationItemUsageInfo(true)
30+
when (val parent = it.parent) {
31+
is KtQualifiedExpression -> {
32+
when {
33+
// fragments.x
34+
parent.receiverExpression.text == "fragments" -> {
35+
parent.toMigrationItemUsageInfo(true)
36+
}
37+
// x.fragments
38+
parent.selectorExpression?.text == "fragments" -> {
39+
parent.toMigrationItemUsageInfo(false)
40+
}
41+
42+
else -> null
43+
}
3644
}
37-
// x.fragments
38-
parent.selectorExpression?.text == "fragments" -> {
39-
parent.toMigrationItemUsageInfo(false)
45+
46+
is KtValueArgumentName -> {
47+
// fragments = ...
48+
(parent.parent as? KtValueArgument)?.toMigrationItemUsageInfo()
4049
}
4150

4251
else -> null
@@ -45,13 +54,31 @@ object RemoveFragmentsField : MigrationItem() {
4554
}
4655

4756
override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) {
48-
val element = usage.element as KtDotQualifiedExpression
49-
if (usage.attachedData()) {
50-
// fragments.x -> x
51-
element.replace(element.selectorExpression!!)
52-
} else {
53-
// x.fragments -> x
54-
element.replace(element.receiverExpression)
57+
when (val element = usage.element) {
58+
is KtQualifiedExpression -> {
59+
if (usage.attachedData()) {
60+
// fragments.x -> x
61+
element.replace(element.selectorExpression!!)
62+
} else {
63+
// x.fragments -> x
64+
element.replace(element.receiverExpression)
65+
}
66+
}
67+
68+
// fragments = Xxx.Fragments(yyy = ..., xxx = ...) -> yyy = ..., xxx = ...
69+
is KtValueArgument -> {
70+
// Xxx.Fragments(yyy = ..., xxx = ...)
71+
val callExpression = element.getArgumentExpression()?.childrenOfType<KtCallExpression>()?.firstOrNull()
72+
// yyy = ..., xxx = ...
73+
val enclosedArguments = callExpression?.valueArgumentList ?: return
74+
val parentArgumentList = element.parent as KtValueArgumentList
75+
for (enclosedArgument in enclosedArguments.arguments) {
76+
parentArgumentList.addArgument(enclosedArgument)
77+
}
78+
parentArgumentList.removeArgument(element)
79+
}
80+
81+
else -> {}
5582
}
5683
}
5784
}

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/compattooperationbased/item/ReworkInlineFragmentFields.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.apollographql.ijplugin.refactoring.migration.compattooperationbased.item
22

33
import com.apollographql.ijplugin.refactoring.findClassReferences
4-
import com.apollographql.ijplugin.refactoring.findInheritorsOfClass
54
import com.apollographql.ijplugin.refactoring.findReferences
65
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItem
76
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItemUsageInfo
@@ -10,7 +9,6 @@ import com.intellij.openapi.project.Project
109
import com.intellij.psi.PsiMigration
1110
import com.intellij.psi.PsiReference
1211
import com.intellij.psi.search.GlobalSearchScope
13-
import org.jetbrains.kotlin.asJava.classes.KtLightClassBase
1412
import org.jetbrains.kotlin.idea.base.utils.fqname.fqName
1513
import org.jetbrains.kotlin.nj2k.postProcessing.type
1614
import org.jetbrains.kotlin.psi.KtClass
@@ -21,12 +19,7 @@ import org.jetbrains.kotlin.psi.KtPsiFactory
2119
object ReworkInlineFragmentFields : MigrationItem() {
2220
override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List<MigrationItemUsageInfo> {
2321
val usageInfo = mutableListOf<MigrationItemUsageInfo>()
24-
25-
val operationInheritors = findInheritorsOfClass(project, "com.apollographql.apollo3.api.Operation").filterIsInstance<KtLightClassBase>()
26-
val fragmentDataInheritors = findInheritorsOfClass(project, "com.apollographql.apollo3.api.Fragment.Data").filterIsInstance<KtLightClassBase>()
27-
val allModels = (operationInheritors + fragmentDataInheritors).flatMap {
28-
it.kotlinOrigin?.body?.declarations.orEmpty().filterIsInstance<KtClass>()
29-
}
22+
val allModels: List<KtClass> = findAllModels(project)
3023
val inlineFragmentProperties = allModels.flatMap { model ->
3124
model.inlineFragmentProperties()
3225
}

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/compattooperationbased/item/UpdateCodegenInBuildKts.kt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,39 @@ import com.apollographql.ijplugin.refactoring.migration.item.DeletesElements
44
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItem
55
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItemUsageInfo
66
import com.apollographql.ijplugin.refactoring.migration.item.toMigrationItemUsageInfo
7+
import com.apollographql.ijplugin.util.cast
78
import com.apollographql.ijplugin.util.findPsiFilesByName
9+
import com.apollographql.ijplugin.util.getMethodName
10+
import com.apollographql.ijplugin.util.unquoted
811
import com.intellij.openapi.project.Project
9-
import com.intellij.psi.PsiFile
1012
import com.intellij.psi.PsiMigration
1113
import com.intellij.psi.search.GlobalSearchScope
12-
import com.intellij.psi.util.parentOfType
14+
import org.jetbrains.kotlin.psi.KtCallExpression
1315
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
1416
import org.jetbrains.kotlin.psi.KtFile
15-
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
1617
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
1718
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
1819

1920
object UpdateCodegenInBuildKts : MigrationItem(), DeletesElements {
2021
override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List<MigrationItemUsageInfo> {
21-
val buildGradleKtsFiles: List<PsiFile> = project.findPsiFilesByName("build.gradle.kts", searchScope)
2222
val usages = mutableListOf<MigrationItemUsageInfo>()
23+
val buildGradleKtsFiles: List<KtFile> = project.findPsiFilesByName("build.gradle.kts", searchScope).filterIsInstance<KtFile>()
2324
for (file in buildGradleKtsFiles) {
24-
if (file !is KtFile) continue
2525
file.accept(object : KtTreeVisitorVoid() {
26-
override fun visitLiteralStringTemplateEntry(entry: KtLiteralStringTemplateEntry) {
27-
super.visitLiteralStringTemplateEntry(entry)
28-
// codegenModels.set("compat")
29-
if (entry.text == "compat") {
30-
val dotQualifiedExpression = entry.parentOfType<KtDotQualifiedExpression>() ?: return
31-
if ((dotQualifiedExpression.receiverExpression as? KtNameReferenceExpression)?.getReferencedName() == "codegenModels") {
32-
usages.add(dotQualifiedExpression.toMigrationItemUsageInfo())
33-
}
26+
override fun visitCallExpression(expression: KtCallExpression) {
27+
super.visitCallExpression(expression)
28+
if (expression.getMethodName() == "apollo") {
29+
expression.accept(object : KtTreeVisitorVoid() {
30+
override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) {
31+
super.visitDotQualifiedExpression(expression)
32+
if (expression.receiverExpression.cast<KtNameReferenceExpression>()?.getReferencedName() == "codegenModels") {
33+
val argumentText = expression.selectorExpression.cast<KtCallExpression>()?.valueArguments?.firstOrNull()?.text ?: return
34+
if (argumentText.unquoted() == "compat" || argumentText.contains("MODELS_COMPAT")) {
35+
usages.add(expression.toMigrationItemUsageInfo())
36+
}
37+
}
38+
}
39+
})
3440
}
3541
}
3642
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.apollographql.ijplugin.refactoring.migration.compattooperationbased.item
2+
3+
import com.apollographql.ijplugin.refactoring.findInheritorsOfClass
4+
import com.intellij.openapi.project.Project
5+
import org.jetbrains.kotlin.asJava.classes.KtLightClassBase
6+
import org.jetbrains.kotlin.psi.KtClass
7+
8+
fun findAllModels(project: Project): List<KtClass> {
9+
val operationInheritors = findInheritorsOfClass(project, "com.apollographql.apollo3.api.Operation").filterIsInstance<KtLightClassBase>()
10+
val fragmentDataInheritors = findInheritorsOfClass(project, "com.apollographql.apollo3.api.Fragment.Data").filterIsInstance<KtLightClassBase>()
11+
val allModels: List<KtClass> = (operationInheritors + fragmentDataInheritors).flatMap {
12+
it.kotlinOrigin?.body?.declarations.orEmpty().filterIsInstance<KtClass>()
13+
} + fragmentDataInheritors.map { it.kotlinOrigin }.filterIsInstance<KtClass>()
14+
return allModels
15+
}

intellij-plugin/src/test/testData/migration/compat-to-operationbased/removeFragmentsField.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,20 @@ suspend fun main() {
5555

5656
val id = data.launches.launches[0]!!.fragments.launchFields.id
5757
val id2 = data.launches.launches[0]!!.apply { fragments.launchFields.id }
58+
val id3 = data.launches.launches[0]?.fragments?.launchFields.id
59+
60+
//@formatter:off
61+
val launch = MyQuery.Launch(
62+
__typename = "Launch",
63+
fragments = Launch.Fragments(
64+
launchFields = LaunchFields(
65+
id = "id",
66+
site = "site",
67+
mission = LaunchFields.Mission(
68+
name = "name",
69+
missionPatch = "missionPatch",
70+
),
71+
),
72+
),
73+
)
5874
}

intellij-plugin/src/test/testData/migration/compat-to-operationbased/removeFragmentsField_after.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,18 @@ suspend fun main() {
5555

5656
val id = data.launches.launches[0]!!.launchFields.id
5757
val id2 = data.launches.launches[0]!!.apply { launchFields.id }
58+
val id3 = data.launches.launches[0]?.launchFields.id
59+
60+
//@formatter:off
61+
val launch = MyQuery.Launch(
62+
__typename = "Launch",
63+
launchFields = LaunchFields(
64+
id = "id",
65+
site = "site",
66+
mission = LaunchFields.Mission(
67+
name = "name",
68+
missionPatch = "missionPatch",
69+
),
70+
),
71+
)
5872
}

intellij-plugin/src/test/testData/migration/compat-to-operationbased/reworkInlineFragmentFields.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,22 @@ suspend fun main() {
9292
val asGranny: AsGranny = data.fruitList.list[0].asApple!!.asGranny!!
9393
val isGranny = asGranny.isGranny
9494
val id2 = data.fruitList.list[0].asApple!!.asGranny!!.id
95+
96+
val newAsGranny: FruitListQuery.AsApple = FruitListQuery.AsApple(
97+
__typename = "Apple",
98+
id = "id",
99+
color = "color",
100+
asGolden = FruitListQuery.AsGolden(
101+
__typename = "Golden",
102+
id = "id",
103+
color = "color",
104+
isGolden = true,
105+
),
106+
asGranny = FruitListQuery.AsGranny(
107+
__typename = "Granny",
108+
id = "id",
109+
color = "color",
110+
isGranny = true,
111+
),
112+
)
95113
}

intellij-plugin/src/test/testData/migration/compat-to-operationbased/reworkInlineFragmentFields_after.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,22 @@ suspend fun main() {
9292
val asGranny: OnGranny = data.fruitList.list[0].onApple!!.onGranny!!
9393
val isGranny = asGranny.isGranny
9494
val id2 = data.fruitList.list[0].onApple!!.onGranny!!.id
95+
96+
val newAsGranny: FruitListQuery.OnApple = FruitListQuery.OnApple(
97+
__typename = "Apple",
98+
id = "id",
99+
color = "color",
100+
onGolden = FruitListQuery.OnGolden(
101+
__typename = "Golden",
102+
id = "id",
103+
color = "color",
104+
isGolden = true,
105+
),
106+
onGranny = FruitListQuery.OnGranny(
107+
__typename = "Granny",
108+
id = "id",
109+
color = "color",
110+
isGranny = true,
111+
),
112+
)
95113
}

0 commit comments

Comments
 (0)