Skip to content

Commit eae588f

Browse files
authored
[IJ Plugin] v3 -> v4 migration: package name updates (#6025)
* v3 -> v4 migration: package name updates * Add UpdateScalarAdaptersInBuildKts * Don't assume artifacts always have versions in toml * Fix usages filter * Add debug logs and catch some exceptions * Remove no longer needed mavenLocal * Remove imports of methods that are updated * Disable the Optimize Imports Refactoring Helper. * Remove some imports
1 parent 28126d5 commit eae588f

27 files changed

+275
-87
lines changed

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/navigation/GraphQLNavigation.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.apollographql.ijplugin.util.capitalizeFirstLetter
77
import com.apollographql.ijplugin.util.containingKtFile
88
import com.apollographql.ijplugin.util.findChildrenOfType
99
import com.apollographql.ijplugin.util.resolveKtName
10+
import com.apollographql.ijplugin.util.shortName
1011
import com.apollographql.ijplugin.util.type
1112
import com.intellij.lang.jsgraphql.GraphQLFileType
1213
import com.intellij.lang.jsgraphql.psi.GraphQLDefinition
@@ -91,7 +92,7 @@ fun KtClass.isApolloFragment(): Boolean {
9192
} ||
9293
// Fallback for fragments in responseBased codegen: they are interfaces generated in a .fragment package.
9394
// This can lead to false positives, but consequences are not dire.
94-
isInterface() && kotlinFqName?.parent()?.shortName()?.asString() == "fragment" && hasGeneratedByApolloComment()
95+
isInterface() && kotlinFqName?.parent()?.shortName == "fragment" && hasGeneratedByApolloComment()
9596
}
9697

9798
fun KtClass.isApolloOperationOrFragment(): Boolean {
@@ -101,7 +102,7 @@ fun KtClass.isApolloOperationOrFragment(): Boolean {
101102
} ||
102103
// Fallback for fragments in responseBased codegen: they are interfaces generated in a .fragment package.
103104
// This can lead to false positives, but consequences are not dire.
104-
isInterface() && kotlinFqName?.parent()?.shortName()?.asString() == "fragment" && hasGeneratedByApolloComment()
105+
isInterface() && kotlinFqName?.parent()?.shortName == "fragment" && hasGeneratedByApolloComment()
105106
}
106107

107108
fun KtNameReferenceExpression.isApolloEnumClassReference(): Boolean {
@@ -131,7 +132,7 @@ fun KtClass.isApolloInputClass(): Boolean {
131132
// Apollo input classes are data classes, generated in a package named "type", and we also look at the header comment.
132133
// This can lead to false positives, but consequences are not dire.
133134
return isData() &&
134-
kotlinFqName?.parent()?.shortName()?.asString() == "type" &&
135+
kotlinFqName?.parent()?.shortName == "type" &&
135136
hasGeneratedByApolloComment()
136137
}
137138

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/Refactoring.kt

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ fun PsiElement.bindReferencesToElement(element: PsiElement): PsiElement? {
4545
}
4646

4747
fun findReferences(psiElement: PsiElement, project: Project): Collection<PsiReference> {
48-
return ReferencesSearch.search(psiElement, GlobalSearchScope.projectScope(project), false).findAll()
48+
return runCatching {
49+
// We sometimes get KotlinExceptionWithAttachments: Unsupported reference
50+
ReferencesSearch.search(psiElement, GlobalSearchScope.projectScope(project), false).findAll()
51+
}
52+
.onFailure { e ->
53+
logw(e, "findReferences failed")
54+
}
55+
.getOrDefault(emptyList())
4956
}
5057

5158
fun findMethodReferences(
@@ -57,18 +64,18 @@ fun findMethodReferences(
5764
): Collection<PsiReference> {
5865
val psiLookupClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) ?: return emptyList()
5966
val methods =
60-
// Try Kotlin first
61-
(psiLookupClass as? KtLightClass)?.kotlinOrigin?.findFunctionsByName(methodName)?.takeIf { it.isNotEmpty() }
62-
// Fallback to Java
63-
?: psiLookupClass.findMethodsByName(methodName, false)
64-
.filter { method ->
65-
if (extensionTargetClassName == null) return@filter methodPredicate(method)
66-
// In Kotlin extensions, the target is passed to the first parameter
67-
if (method.parameterList.parametersCount < 1) return@filter false
68-
val firstParameter = method.parameterList.parameters.first()
69-
val firstParameterType = (firstParameter.type as? PsiClassType)?.rawType()?.canonicalText
70-
firstParameterType == extensionTargetClassName && methodPredicate(method)
71-
}
67+
// Try Kotlin first
68+
(psiLookupClass as? KtLightClass)?.kotlinOrigin?.findFunctionsByName(methodName)?.takeIf { it.isNotEmpty() }
69+
// Fallback to Java
70+
?: psiLookupClass.findMethodsByName(methodName, false)
71+
.filter { method ->
72+
if (extensionTargetClassName == null) return@filter methodPredicate(method)
73+
// In Kotlin extensions, the target is passed to the first parameter
74+
if (method.parameterList.parametersCount < 1) return@filter false
75+
val firstParameter = method.parameterList.parameters.first()
76+
val firstParameterType = (firstParameter.type as? PsiClassType)?.rawType()?.canonicalText
77+
firstParameterType == extensionTargetClassName && methodPredicate(method)
78+
}
7279
return methods.flatMap { method ->
7380
findReferences(method, project)
7481
}

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

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.apollographql.ijplugin.ApolloBundle
44
import com.apollographql.ijplugin.refactoring.migration.item.DeletesElements
55
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItem
66
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItemUsageInfo
7+
import com.apollographql.ijplugin.util.containingKtFile
78
import com.apollographql.ijplugin.util.containingKtFileImportList
89
import com.apollographql.ijplugin.util.isGenerated
910
import com.apollographql.ijplugin.util.logd
@@ -27,6 +28,7 @@ import com.intellij.refactoring.BaseRefactoringProcessor
2728
import com.intellij.refactoring.ui.UsageViewDescriptorAdapter
2829
import com.intellij.usageView.UsageInfo
2930
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
31+
import org.jetbrains.kotlin.psi.KtFile
3032
import org.jetbrains.kotlin.psi.KtPsiFactory
3133
import org.jetbrains.kotlin.resolve.ImportPath
3234
import org.jetbrains.plugins.gradle.util.GradleConstants
@@ -80,14 +82,23 @@ abstract class ApolloMigrationRefactoringProcessor(project: Project) : BaseRefac
8082
try {
8183
val usageInfos = migrationItems
8284
.flatMap { migrationItem ->
83-
migrationItem.findUsages(myProject, migration!!, searchScope)
84-
.filter { usageInfo ->
85-
// Filter out all generated code usages. We don't want generated code to come up in findUsages.
86-
usageInfo.virtualFile?.isGenerated(myProject) != true //&&
87-
88-
// Also filter out usages outside of projects (see https://youtrack.jetbrains.com/issue/KTIJ-26411)
89-
usageInfo.virtualFile?.let { searchScope.contains(it) } == true
90-
}
85+
logd("Finding usages for $migrationItem")
86+
try {
87+
migrationItem.findUsages(myProject, migration!!, searchScope)
88+
.filter { usageInfo ->
89+
// Filter out all generated code usages. We don't want generated code to come up in findUsages.
90+
usageInfo.virtualFile?.isGenerated(myProject) != true &&
91+
92+
// Also filter out usages outside of projects (see https://youtrack.jetbrains.com/issue/KTIJ-26411)
93+
usageInfo.virtualFile?.let { searchScope.contains(it) } == true
94+
}
95+
.also {
96+
logd("Found ${it.size} usages for $migrationItem")
97+
}
98+
} catch (t: Throwable) {
99+
logw(t, "Error while finding usages for $migrationItem")
100+
emptyList()
101+
}
91102
}
92103
.toMutableList()
93104
// If an element must be deleted, make sure we keep the UsageInfo and remove any other pointing to the same element.
@@ -131,8 +142,10 @@ abstract class ApolloMigrationRefactoringProcessor(project: Project) : BaseRefac
131142
val migrationItem = (usage as MigrationItemUsageInfo).migrationItem
132143
try {
133144
if (!usage.isValid) continue
145+
val containingKtFile = usage.element.containingKtFile()
134146
maybeAddImports(usage, migrationItem)
135147
migrationItem.performRefactoring(myProject, migration!!, usage)
148+
if (containingKtFile != null) removeDuplicateImports(containingKtFile)
136149
} catch (t: Throwable) {
137150
logw(t, "Error while performing refactoring for $migrationItem")
138151
}
@@ -161,6 +174,22 @@ abstract class ApolloMigrationRefactoringProcessor(project: Project) : BaseRefac
161174
}
162175
}
163176

177+
/**
178+
* Imports are automatically optimized in most cases, but some duplications are sometimes missed.
179+
*/
180+
private fun removeDuplicateImports(ktFile: KtFile) {
181+
val importList = ktFile.importList ?: return
182+
val seenImports = mutableSetOf<String>()
183+
for (importDirective in importList.imports) {
184+
val importPath = importDirective.importPath?.pathStr ?: continue
185+
if (seenImports.contains(importPath)) {
186+
importDirective.delete()
187+
} else {
188+
seenImports.add(importPath)
189+
}
190+
}
191+
}
192+
164193
private fun postRefactoring() {
165194
logd()
166195
PsiDocumentManager.getInstance(myProject).performLaterWhenAllCommitted {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package com.apollographql.ijplugin.refactoring.migration.compattooperationbased.
22

33
import com.apollographql.ijplugin.refactoring.findInheritorsOfClass
44
import com.apollographql.ijplugin.util.apollo3
5+
import com.apollographql.ijplugin.util.logd
56
import com.intellij.openapi.project.Project
67
import org.jetbrains.kotlin.asJava.classes.KtLightClassBase
78
import org.jetbrains.kotlin.psi.KtClass
89

910
fun findAllModels(project: Project): List<KtClass> {
11+
logd()
1012
val operationInheritors = findInheritorsOfClass(project, "$apollo3.api.Operation").filterIsInstance<KtLightClassBase>()
1113
val fragmentDataInheritors = findInheritorsOfClass(project, "$apollo3.api.Fragment.Data").filterIsInstance<KtLightClassBase>()
1214
val allModels: List<KtClass> = (operationInheritors + fragmentDataInheritors).flatMap {
1315
it.kotlinOrigin?.body?.declarations.orEmpty().filterIsInstance<KtClass>()
1416
} + fragmentDataInheritors.map { it.kotlinOrigin }.filterIsInstance<KtClass>()
17+
logd("size=${allModels.size}")
1518
return allModels
1619
}

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

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

33
import com.intellij.psi.PsiElement
4+
import com.intellij.psi.PsiFile
45
import com.intellij.psi.PsiReference
56
import com.intellij.usageView.UsageInfo
67

@@ -39,6 +40,25 @@ open class MigrationItemUsageInfo : UsageInfo {
3940
override fun isValid(): Boolean {
4041
return super.getElement() != null && super.isValid()
4142
}
43+
44+
/**
45+
* Taken from https://github.com/JetBrains/android/blob/b9cc41149734794ca6746711b1fb5a2fb6d06ec4/project-system-gradle/src/org/jetbrains/android/refactoring/MigrateToAndroidxProcessor.kt
46+
*
47+
* Verifies if one of the calls on the stack comes from the Optimize Imports Refactoring Helper.
48+
* We check the last 5 elements to allow for some future flow changes.
49+
*
50+
* This avoids the helper kicking as it can remove imports we're trying to add.
51+
*/
52+
private fun isKotlinOptimizerCall(): Boolean = Thread.currentThread().stackTrace
53+
.take(5)
54+
.map { it.className }
55+
.any { it.contains("OptimizeImports", ignoreCase = true) }
56+
57+
override fun getFile(): PsiFile? = if (isKotlinOptimizerCall()) {
58+
null
59+
} else {
60+
super.getFile()
61+
}
4262
}
4363

4464
context(MigrationItem)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.apollographql.ijplugin.refactoring.migration.item
2+
3+
import com.apollographql.ijplugin.refactoring.findClassReferences
4+
import com.apollographql.ijplugin.refactoring.findOrCreateClass
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.psi.PsiMigration
7+
import com.intellij.psi.search.GlobalSearchScope
8+
import com.intellij.psi.util.parentOfType
9+
import org.jetbrains.kotlin.psi.KtImportDirective
10+
11+
class RemoveImport(
12+
private val import: String,
13+
) : MigrationItem(), DeletesElements {
14+
15+
override fun prepare(project: Project, migration: PsiMigration) {
16+
findOrCreateClass(project, migration, import)
17+
}
18+
19+
override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List<MigrationItemUsageInfo> {
20+
return findClassReferences(project, import)
21+
.mapNotNull {
22+
it.element.parentOfType<KtImportDirective>()?.toMigrationItemUsageInfo()
23+
}
24+
}
25+
26+
override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) {
27+
usage.element.delete()
28+
}
29+
}

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,19 @@ class UpdateGradleDependenciesBuildKts(
3939
}
4040

4141
override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) {
42-
val argument = (usage.element as KtCallExpression).valueArguments.first()
43-
val entries = ((argument as KtValueArgument).getArgumentExpression() as? KtStringTemplateExpression)?.entries ?: return
44-
val firstEntry = entries.firstOrNull() ?: return
45-
val artifactId = firstEntry.text.unquoted().split(":")[1]
46-
firstEntry.replace(KtPsiFactory(project).createLiteralStringTemplateEntry("$newGroupId:$artifactId"))
47-
// Remove other entries (with recent versions of the Apollo Gradle plugin, there is no need to specify the version)
48-
entries.drop(1).forEach { it.delete() }
42+
val callExpression = usage.element as KtCallExpression
43+
val arguments = callExpression.valueArguments
44+
val psiFactory = KtPsiFactory(project)
45+
if (arguments.size == 1) {
46+
// implementation("oldGroupId:artifactId:version"), implementation("oldGroupId:artifactId") -> implementation("newGroupId:artifactId")
47+
val argument = arguments.first()
48+
val entries = ((argument as KtValueArgument).getArgumentExpression() as? KtStringTemplateExpression)?.entries ?: return
49+
val entry = entries.firstOrNull() ?: return
50+
val artifactId = entry.text.unquoted().split(":")[1]
51+
argument.replace(psiFactory.createStringTemplate("$newGroupId:$artifactId"))
52+
} else if (arguments.size > 1) {
53+
// implementation("oldGroupId", "artifactId", "version"), implementation("oldGroupId", "artifactId") -> implementation("newGroupId", "artifactId")
54+
callExpression.replace(psiFactory.createExpression("""${callExpression.calleeExpression!!.text}("$newGroupId", ${arguments[1].text})"""))
55+
}
4956
}
5057
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class UpdateGradleDependenciesInToml(
3535
usages.add(MigrationItemUsageInfo(this@UpdateGradleDependenciesInToml, element.firstChild, Kind.SHORT_MODULE_OR_GROUP))
3636
// Find the associated version
3737
val versionEntry = (element.parent.parent as? TomlInlineTable)?.entries
38-
?.first { it.key.text == "version" || it.key.text == "version.ref" }
38+
?.firstOrNull { it.key.text == "version" || it.key.text == "version.ref" }
3939
if (versionEntry != null) {
4040
if (versionEntry.key.text == "version") {
4141
versionEntry.value?.let {

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class UpdateMethodCall(
1515
private val replacementExpression: String,
1616
private val extensionTargetClassName: String? = null,
1717
private vararg val importsToAdd: String,
18-
) : MigrationItem() {
18+
) : MigrationItem(), DeletesElements {
1919
override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List<MigrationItemUsageInfo> {
2020
return findMethodReferences(
2121
project = project,
@@ -30,9 +30,10 @@ class UpdateMethodCall(
3030
val importDirective = element.parentOfType<KtImportDirective>()
3131
if (importDirective != null) {
3232
// Reference is an import
33-
return
33+
importDirective.delete()
34+
} else {
35+
element.parentOfType<KtDotQualifiedExpression>()?.selectorExpression?.replace(KtPsiFactory(project).createExpression(replacementExpression))
3436
}
35-
element.parentOfType<KtDotQualifiedExpression>()?.selectorExpression?.replace(KtPsiFactory(project).createExpression(replacementExpression))
3637
}
3738

3839
override fun importsToAdd() = importsToAdd.toSet()

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/v2tov3/ApolloV2ToV3MigrationProcessor.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.apollographql.ijplugin.refactoring.migration.v2tov3
33
import com.apollographql.ijplugin.ApolloBundle
44
import com.apollographql.ijplugin.refactoring.migration.ApolloMigrationRefactoringProcessor
55
import com.apollographql.ijplugin.refactoring.migration.item.CommentDependenciesInToml
6+
import com.apollographql.ijplugin.refactoring.migration.item.RemoveImport
67
import com.apollographql.ijplugin.refactoring.migration.item.RemoveMethodCall
78
import com.apollographql.ijplugin.refactoring.migration.item.RemoveMethodImport
89
import com.apollographql.ijplugin.refactoring.migration.item.UpdateClassName
@@ -114,10 +115,15 @@ class ApolloV2ToV3MigrationProcessor(project: Project) : ApolloMigrationRefactor
114115
),
115116

116117
RemoveMethodCall("$apollo2.ApolloQueryCall", "toBuilder"),
117-
RemoveMethodCall("$apollo2.ApolloQueryCall.Builder", "build"),
118118

119+
RemoveMethodCall("$apollo2.ApolloQueryCall.Builder", "build"),
119120
UpdatePackageName(apollo2, apollo3),
120121

122+
RemoveImport("$apollo2.cache.normalized.lru.EvictionPolicy"),
123+
RemoveImport("$apollo2.cache.http.DiskLruHttpCacheStore"),
124+
RemoveImport("$apollo2.cache.http.internal.FileSystem"),
125+
RemoveImport("$apollo2.coroutines.await"),
126+
121127
// Gradle
122128
UpdateGradlePluginInBuildKts(apollo2, apollo3, apollo3LatestVersion),
123129
CommentDependenciesInToml("apollo-coroutines-support", "apollo-android-support"),

0 commit comments

Comments
 (0)