Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IJ Plugin] Rename related generated code when renaming an Operation/Fragment #6227

Merged
merged 3 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ data class ApolloKotlinService(

@XCollection
val upstreamServiceIds: List<Id> = emptyList(),

@Attribute
val useSemanticNaming: Boolean = true,
) {
data class Id(
@Attribute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ class GradleToolingModelService(
.distinct(),
endpointUrl = serviceInfo.endpointUrlCompat(toolingModel),
endpointHeaders = serviceInfo.endpointHeadersCompat(toolingModel),
upstreamServiceIds = upstreamApolloKotlinServices.map { it.id }
upstreamServiceIds = upstreamApolloKotlinServices.map { it.id },
useSemanticNaming = serviceInfo.useSemanticNamingCompat(toolingModel),
)
}
}
Expand Down Expand Up @@ -329,4 +330,7 @@ private fun ApolloGradleToolingModel.ServiceInfo.endpointUrlCompat(toolingModel:
private fun ApolloGradleToolingModel.ServiceInfo.endpointHeadersCompat(toolingModel: ApolloGradleToolingModel) =
if (toolingModel.versionMinor >= 1) endpointHeaders else null

private fun ApolloGradleToolingModel.ServiceInfo.useSemanticNamingCompat(toolingModel: ApolloGradleToolingModel) =
if (toolingModel.versionMinor >= 4) useSemanticNaming else true

val Project.gradleToolingModelService get() = service<GradleToolingModelService>()
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,22 @@ class ApolloGraphQLConfigContributor : GraphQLConfigContributor {
private fun ApolloKotlinService.toGraphQLRawProjectConfig() = GraphQLRawProjectConfig(
schema = schemaPaths.map { GraphQLRawSchemaPointer(it) },
include = operationPaths.map { "$it/**/*.graphql" },
extensions = endpointUrl?.let {
mapOf(
GraphQLConfigKeys.EXTENSION_ENDPOINTS to mapOf(
serviceName to buildMap {
put(GraphQLConfigKeys.EXTENSION_ENDPOINT_URL, endpointUrl)
if (endpointHeaders != null) {
put(GraphQLConfigKeys.HEADERS, endpointHeaders)
}
}
extensions = mapOf(EXTENSION_APOLLO_KOTLIN_SERVICE_ID to this.id.toString()) +
(endpointUrl?.let {
mapOf(
GraphQLConfigKeys.EXTENSION_ENDPOINTS to mapOf(
serviceName to buildMap {
put(GraphQLConfigKeys.EXTENSION_ENDPOINT_URL, endpointUrl)
if (endpointHeaders != null) {
put(GraphQLConfigKeys.HEADERS, endpointHeaders)
}
}
)
)
)
}
} ?: emptyMap())
)

companion object {
const val EXTENSION_APOLLO_KOTLIN_SERVICE_ID = "apolloKotlinServiceId"
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package com.apollographql.ijplugin.refactoring

import com.apollographql.ijplugin.navigation.compat.KotlinFindUsagesHandlerFactoryCompat
import com.apollographql.ijplugin.navigation.findKotlinFragmentClassDefinitions
import com.apollographql.ijplugin.navigation.findKotlinOperationDefinitions
import com.apollographql.ijplugin.util.isGenerated
import com.apollographql.ijplugin.util.apolloKotlinService
import com.apollographql.ijplugin.util.capitalizeFirstLetter
import com.apollographql.ijplugin.util.cast
import com.intellij.lang.jsgraphql.psi.GraphQLElement
import com.intellij.lang.jsgraphql.psi.GraphQLFragmentDefinition
import com.intellij.lang.jsgraphql.psi.GraphQLIdentifier
import com.intellij.lang.jsgraphql.psi.GraphQLTypedOperationDefinition
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.search.SearchScope
import com.intellij.refactoring.rename.RenameDialog
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import com.intellij.usageView.UsageInfo
import com.intellij.util.Processor

/**
* Allows to rename the corresponding usage in Kotlin code when renaming a GraphQL operation or fragment definition.
Expand Down Expand Up @@ -43,73 +41,61 @@ class GraphQLDefinitionRenameProcessor : RenamePsiElementProcessor() {
val type = definition.operationType.text
return "$type '${definition.name}'"
}


}
}

override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>) {
prepareRenamingFile(element, allRenames, newName)
prepareRenamingFile(element, newName, allRenames)
prepareRenamingKotlinClasses(element, newName, allRenames)
this.newName = newName
}

private fun prepareRenamingFile(
element: PsiElement,
allRenames: MutableMap<PsiElement, String>,
newName: String,
allRenames: MutableMap<PsiElement, String>,
) {
val file = element.containingFile ?: return
val virtualFile = file.virtualFile ?: return
val elementCurrentName = (element as GraphQLIdentifier).referenceName
// Only rename the file if it previously had the same name as the element
if (virtualFile.nameWithoutExtension == elementCurrentName) {
allRenames[file] = newName + "." + virtualFile.extension
}
}

override fun findReferences(
private fun prepareRenamingKotlinClasses(
element: PsiElement,
searchScope: SearchScope,
searchInCommentsAndStrings: Boolean,
): Collection<PsiReference> {
val references = mutableListOf<PsiReference>()
val kotlinDefinitions = when (val parent = element.parent) {
newName: String,
allRenames: MutableMap<PsiElement, String>,
) {
when (val parent = element.parent) {
is GraphQLTypedOperationDefinition -> {
if (!(newName.endsWith("Query") || newName.endsWith("Mutation") || newName.endsWith("Subscription"))) {
// When useSemanticNaming is true (the default), renaming e.g. FooQuery to FooQuery2 will generate FooQuery2Query.
// For now we'll only support the happy case, and won't try to rename references otherwise.
// TODO: We could support this by looking at the value of useSemanticNaming from the Gradle Tooling Model, and implementing
// the same naming logic as the Apollo compiler.
return super.findReferences(element, searchScope, searchInCommentsAndStrings)
val kotlinDefinitions = findKotlinOperationDefinitions(parent)
val useSemanticNaming = element.cast<GraphQLElement>()!!.apolloKotlinService()?.useSemanticNaming ?: true
for (kotlinDefinition in kotlinDefinitions) {
allRenames[kotlinDefinition] =
newName.capitalizeFirstLetter() + if (useSemanticNaming) {
val suffix = parent.operationType.text.capitalizeFirstLetter()
if (!newName.endsWith(suffix)) {
suffix
} else {
""
}
} else {
""
}
}

findKotlinOperationDefinitions(parent)
}

is GraphQLFragmentDefinition -> {
findKotlinFragmentClassDefinitions(parent)
}

else -> emptyList()
}.ifEmpty {
return super.findReferences(element, searchScope, searchInCommentsAndStrings)
}

val kotlinFindUsagesHandlerFactory = KotlinFindUsagesHandlerFactoryCompat(element.project)
val processor = object : Processor<UsageInfo> {
override fun process(t: UsageInfo): Boolean {
if (t.virtualFile?.isGenerated(t.project) != true) {
t.reference?.let { references.add(it) }
val kotlinDefinitions = findKotlinFragmentClassDefinitions(parent)
for (kotlinDefinition in kotlinDefinitions) {
allRenames[kotlinDefinition] = newName.capitalizeFirstLetter()
}
return true
}
}
for (kotlinDefinition in kotlinDefinitions) {
if (kotlinFindUsagesHandlerFactory.canFindUsages(kotlinDefinition)) {
val kotlinFindUsagesHandler = kotlinFindUsagesHandlerFactory.createFindUsagesHandler(kotlinDefinition, false)
?: break
val findUsageOptions = kotlinFindUsagesHandlerFactory.findClassOptions ?: break
kotlinFindUsagesHandler.processElementUsages(kotlinDefinition, processor, findUsageOptions)
}
}

return super.findReferences(element, searchScope, searchInCommentsAndStrings) + references
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.apollographql.ijplugin.util

import com.apollographql.ijplugin.gradle.ApolloKotlinService
import com.apollographql.ijplugin.gradle.gradleToolingModelService
import com.apollographql.ijplugin.graphql.ApolloGraphQLConfigContributor
import com.intellij.lang.jsgraphql.ide.config.GraphQLConfigProvider
import com.intellij.lang.jsgraphql.psi.GraphQLDirective
import com.intellij.lang.jsgraphql.psi.GraphQLElement
Expand Down Expand Up @@ -81,7 +84,7 @@ private fun matchingFieldCoordinates(
}

/**
* Return the schema files associated with the given element.
* Return the schema files associated with the given GraphQL element.
*/
fun GraphQLElement.schemaFiles(): List<GraphQLFile> {
val containingFile = containingFile ?: return emptyList()
Expand All @@ -91,6 +94,17 @@ fun GraphQLElement.schemaFiles(): List<GraphQLFile> {
}
}

/**
* Return the [ApolloKotlinService] associated with the given GraphQL element.
*/
fun GraphQLElement.apolloKotlinService(): ApolloKotlinService? {
val containingFile = containingFile ?: return null
val projectConfig = GraphQLConfigProvider.getInstance(project).resolveProjectConfig(containingFile) ?: return null
val apolloKotlinServiceId =
projectConfig.extensions[ApolloGraphQLConfigContributor.EXTENSION_APOLLO_KOTLIN_SERVICE_ID] as? String ?: return null
return project.gradleToolingModelService.apolloKotlinServices.firstOrNull { it.id.toString() == apolloKotlinServiceId }
}

fun GraphQLDirective.argumentValue(argumentName: String): GraphQLValue? =
arguments?.argumentList.orEmpty().firstOrNull { it.name == argumentName }?.value

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public abstract interface class com/apollographql/apollo/gradle/api/ApolloGradle
public abstract fun getSchemaFiles ()Ljava/util/Set;
public abstract fun getUpstreamProjectPaths ()Ljava/util/Set;
public abstract fun getUpstreamProjects ()Ljava/util/Set;
public abstract fun getUseSemanticNaming ()Z
}

public abstract interface class com/apollographql/apollo/gradle/api/ApolloGradleToolingModel$TelemetryData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ interface ApolloGradleToolingModel {

// Introduced in 1.1
val endpointHeaders: Map<String, String>?

// Introduced in 1.4
val useSemanticNaming: Boolean
}

interface TelemetryData {
Expand Down Expand Up @@ -87,6 +90,6 @@ interface ApolloGradleToolingModel {
* Current minor version of the tooling model.
* Increment this value when the model changes in compatible ways (additions).
*/
internal const val VERSION_MINOR = 3
internal const val VERSION_MINOR = 4
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ abstract class DefaultApolloExtension(
upstreamProjectPaths = service.upstreamDependencies.filterIsInstance<ProjectDependency>().map { it.dependencyProject.path }.toSet(),
endpointUrl = service.introspection?.endpointUrl?.orNull,
endpointHeaders = service.introspection?.headers?.orNull,
useSemanticNaming = service.useSemanticNaming.getOrElse(true),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal data class DefaultServiceInfo(
override val upstreamProjectPaths: Set<String>,
override val endpointUrl: String?,
override val endpointHeaders: Map<String, String>?,
override val useSemanticNaming: Boolean,
) : ApolloGradleToolingModel.ServiceInfo, Serializable

internal data class DefaultTelemetryData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class GradleToolingTests {
connection.getModel(ApolloGradleToolingModel::class.java)
}
Assert.assertEquals(ApolloGradleToolingModel.VERSION_MAJOR, toolingModel.versionMajor)
Assert.assertEquals(3, toolingModel.versionMinor)
Assert.assertEquals(4, toolingModel.versionMinor)
@Suppress("DEPRECATION")
Assert.assertEquals(emptyList<String>(), toolingModel.serviceInfos.flatMap { it.upstreamProjects })
Assert.assertEquals(emptyList<String>(), toolingModel.serviceInfos.flatMap { it.upstreamProjectPaths })
Expand Down Expand Up @@ -82,7 +82,7 @@ class GradleToolingTests {
connection.getModel(ApolloGradleToolingModel::class.java)
}
Assert.assertEquals(ApolloGradleToolingModel.VERSION_MAJOR, toolingModel.versionMajor)
Assert.assertEquals(3, toolingModel.versionMinor)
Assert.assertEquals(4, toolingModel.versionMinor)
@Suppress("DEPRECATION")
Assert.assertEquals(listOf("node1", "node2"), toolingModel.serviceInfos.flatMap { it.upstreamProjects }.sorted())
Assert.assertEquals(listOf(":node1", ":node2"), toolingModel.serviceInfos.flatMap { it.upstreamProjectPaths }.sorted())
Expand Down
Loading