Skip to content

Commit

Permalink
[IJ Plugin] Add @link directives to extra.graphqls during v3->v4 migr…
Browse files Browse the repository at this point in the history
  • Loading branch information
BoD committed Jul 1, 2024
1 parent 0af757b commit 8c6a37e
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import com.apollographql.apollo3.ast.parseAsGQLDocument
import com.apollographql.apollo3.ast.toUtf8
import com.apollographql.apollo3.ast.transform
import com.apollographql.apollo3.ast.validateAsSchemaAndAddApolloDefinition
import com.apollographql.ijplugin.inspection.schemaFiles
import com.apollographql.ijplugin.util.logw
import com.apollographql.ijplugin.util.schemaFiles
import com.intellij.lang.jsgraphql.psi.GraphQLFile
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiDocumentManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ import com.apollographql.ijplugin.gradle.gradleToolingModelService
import com.apollographql.ijplugin.project.apolloProjectService
import com.apollographql.ijplugin.telemetry.TelemetryEvent
import com.apollographql.ijplugin.telemetry.telemetryService
import com.apollographql.ijplugin.util.KOTLIN_LABS_DEFINITIONS
import com.apollographql.ijplugin.util.KOTLIN_LABS_URL
import com.apollographql.ijplugin.util.NULLABILITY_DEFINITIONS
import com.apollographql.ijplugin.util.NULLABILITY_URL
import com.apollographql.ijplugin.util.cast
import com.apollographql.ijplugin.util.findChildrenOfType
import com.apollographql.ijplugin.util.quoted
import com.apollographql.ijplugin.util.createLinkDirective
import com.apollographql.ijplugin.util.createLinkDirectiveSchemaExtension
import com.apollographql.ijplugin.util.directives
import com.apollographql.ijplugin.util.isImported
import com.apollographql.ijplugin.util.linkDirectives
import com.apollographql.ijplugin.util.nameForImport
import com.apollographql.ijplugin.util.schemaFiles
import com.apollographql.ijplugin.util.unquoted
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils
Expand All @@ -26,7 +35,6 @@ import com.intellij.codeInspection.ProblemsHolder
import com.intellij.lang.jsgraphql.psi.GraphQLArrayValue
import com.intellij.lang.jsgraphql.psi.GraphQLDirective
import com.intellij.lang.jsgraphql.psi.GraphQLElementFactory
import com.intellij.lang.jsgraphql.psi.GraphQLSchemaExtension
import com.intellij.lang.jsgraphql.psi.GraphQLVisitor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
Expand Down Expand Up @@ -139,40 +147,3 @@ private class ImportDefinitionQuickFix(
}
}
}

private fun createLinkDirectiveSchemaExtension(
project: Project,
importedNames: Set<String>,
definitions: List<GQLDefinition>,
definitionsUrl: String,
): GraphQLSchemaExtension {
// If any of the imported name is a directive, add its argument types to the import list
val knownDefinitionNames = definitions.filterIsInstance<GQLNamed>().map { it.name }
val additionalNames = importedNames.flatMap { importedName ->
definitions.directives().firstOrNull { "@${it.name}" == importedName }
?.arguments
?.map { it.type.rawType().name }
?.filter { it in knownDefinitionNames }.orEmpty()
}.toSet()

return GraphQLElementFactory.createFile(
project,
"""
extend schema
@link(
url: "$definitionsUrl",
import: [${(importedNames + additionalNames).joinToString { it.quoted() }}]
)
""".trimIndent()
)
.findChildrenOfType<GraphQLSchemaExtension>().single()
}

private fun createLinkDirective(
project: Project,
importedNames: Set<String>,
definitions: List<GQLDefinition>,
definitionsUrl: String,
): GraphQLDirective {
return createLinkDirectiveSchemaExtension(project, importedNames, definitions, definitionsUrl).directives.single()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.apollographql.ijplugin.project.apolloProjectService
import com.apollographql.ijplugin.telemetry.TelemetryEvent
import com.apollographql.ijplugin.telemetry.telemetryService
import com.apollographql.ijplugin.util.isProcessCanceled
import com.apollographql.ijplugin.util.matchingFieldCoordinates
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils
import com.intellij.codeInspection.LocalInspectionTool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ package com.apollographql.ijplugin.inspection

import com.apollographql.apollo3.ast.GQLDirectiveDefinition
import com.apollographql.apollo3.ast.linkDefinitions
import com.apollographql.ijplugin.util.NULLABILITY_DEFINITIONS
import com.apollographql.ijplugin.util.NULLABILITY_URL
import com.apollographql.ijplugin.util.directives
import com.apollographql.ijplugin.util.isImported
import com.apollographql.ijplugin.util.nameWithoutPrefix
import com.intellij.codeInspection.InspectionSuppressor
import com.intellij.codeInspection.SuppressQuickFix
import com.intellij.lang.jsgraphql.psi.GraphQLArgument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.apollographql.ijplugin.refactoring.migration.item.UpdateGradleDepende
import com.apollographql.ijplugin.refactoring.migration.item.UpdateGradlePluginInBuildKts
import com.apollographql.ijplugin.refactoring.migration.item.UpdateMethodCall
import com.apollographql.ijplugin.refactoring.migration.item.UpdateMethodName
import com.apollographql.ijplugin.refactoring.migration.v3tov4.item.AddLinkDirective
import com.apollographql.ijplugin.refactoring.migration.v3tov4.item.EncloseInService
import com.apollographql.ijplugin.refactoring.migration.v3tov4.item.RemoveFieldInService
import com.apollographql.ijplugin.refactoring.migration.v3tov4.item.RemoveMethodInService
Expand Down Expand Up @@ -95,5 +96,8 @@ class ApolloV3ToV4MigrationProcessor(project: Project) : ApolloMigrationRefactor
UpdateCustomTypeMappingInBuildKts,
UpdateMultiModuleConfiguration,
EncloseInService,

// Add @link to extra.graphqls
AddLinkDirective,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.apollographql.ijplugin.refactoring.migration.v3tov4.item

import com.apollographql.ijplugin.refactoring.migration.item.MigrationItem
import com.apollographql.ijplugin.refactoring.migration.item.MigrationItemUsageInfo
import com.apollographql.ijplugin.refactoring.migration.item.toMigrationItemUsageInfo
import com.apollographql.ijplugin.util.KOTLIN_LABS_DEFINITIONS
import com.apollographql.ijplugin.util.KOTLIN_LABS_URL
import com.apollographql.ijplugin.util.NULLABILITY_DEFINITIONS
import com.apollographql.ijplugin.util.NULLABILITY_URL
import com.apollographql.ijplugin.util.cast
import com.apollographql.ijplugin.util.createLinkDirective
import com.apollographql.ijplugin.util.createLinkDirectiveSchemaExtension
import com.apollographql.ijplugin.util.directives
import com.apollographql.ijplugin.util.findPsiFilesByName
import com.apollographql.ijplugin.util.isImported
import com.apollographql.ijplugin.util.linkDirectives
import com.apollographql.ijplugin.util.nameForImport
import com.apollographql.ijplugin.util.unquoted
import com.intellij.lang.jsgraphql.psi.GraphQLArrayValue
import com.intellij.lang.jsgraphql.psi.GraphQLDirective
import com.intellij.lang.jsgraphql.psi.GraphQLElementFactory
import com.intellij.lang.jsgraphql.psi.GraphQLFile
import com.intellij.lang.jsgraphql.psi.GraphQLRecursiveVisitor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiMigration
import com.intellij.psi.search.GlobalSearchScope

object AddLinkDirective : MigrationItem() {
override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List<MigrationItemUsageInfo> {
val usages = mutableListOf<MigrationItemUsageInfo>()
val extraGraphqlsFiles: List<GraphQLFile> = project.findPsiFilesByName("extra.graphqls", searchScope).filterIsInstance<GraphQLFile>()
for (file in extraGraphqlsFiles) {
file.accept(object : GraphQLRecursiveVisitor() {
override fun visitDirective(o: GraphQLDirective) {
super.visitDirective(o)
if (o.name in NULLABILITY_DEFINITIONS.directives().map { it.name }) {
if (!o.isImported(NULLABILITY_URL)) {
usages.add(o.toMigrationItemUsageInfo(true))
}
}
if (o.name in KOTLIN_LABS_DEFINITIONS.directives().map { it.name }) {
if (!o.isImported(KOTLIN_LABS_URL)) {
usages.add(o.toMigrationItemUsageInfo(false))
}
}
}
})
}
return usages
}

override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) {
val directive = usage.element as GraphQLDirective
val extraSchemaFile = directive.containingFile as GraphQLFile
val definitions = if (usage.attachedData()) NULLABILITY_DEFINITIONS else KOTLIN_LABS_DEFINITIONS
val definitionsUrl = if (usage.attachedData()) NULLABILITY_URL else KOTLIN_LABS_URL
val linkDirective = extraSchemaFile.linkDirectives(definitionsUrl).firstOrNull()
if (linkDirective == null) {
val linkDirectiveSchemaExtension =
createLinkDirectiveSchemaExtension(project, setOf(directive.nameForImport), definitions, definitionsUrl)
val addedElement = extraSchemaFile.addBefore(linkDirectiveSchemaExtension, extraSchemaFile.firstChild)
extraSchemaFile.addAfter(GraphQLElementFactory.createWhiteSpace(project, "\n\n"), addedElement)
} else {
val importedNames = buildSet {
addAll(linkDirective.arguments!!.argumentList.firstOrNull { it.name == "import" }?.value?.cast<GraphQLArrayValue>()?.valueList.orEmpty()
.map { it.text.unquoted() })
add(directive.nameForImport)
}
linkDirective.replace(createLinkDirective(project, importedNames, definitions, definitionsUrl))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.apollographql.ijplugin.inspection
package com.apollographql.ijplugin.util

import com.apollographql.ijplugin.util.findPsiFileByUrl
import com.intellij.lang.jsgraphql.ide.config.GraphQLConfigProvider
import com.intellij.lang.jsgraphql.psi.GraphQLDirective
import com.intellij.lang.jsgraphql.psi.GraphQLElement
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
@file:OptIn(ApolloInternal::class)

package com.apollographql.ijplugin.inspection
package com.apollographql.ijplugin.util

import com.apollographql.apollo3.annotations.ApolloInternal
import com.apollographql.apollo3.ast.GQLDefinition
import com.apollographql.apollo3.ast.GQLDirectiveDefinition
import com.apollographql.apollo3.ast.GQLNamed
import com.apollographql.apollo3.ast.KOTLIN_LABS_VERSION
import com.apollographql.apollo3.ast.NULLABILITY_VERSION
import com.apollographql.apollo3.ast.kotlinLabsDefinitions
import com.apollographql.apollo3.ast.nullabilityDefinitions
import com.apollographql.ijplugin.util.quoted
import com.apollographql.ijplugin.util.unquoted
import com.apollographql.apollo3.ast.rawType
import com.intellij.lang.jsgraphql.psi.GraphQLArrayValue
import com.intellij.lang.jsgraphql.psi.GraphQLDirective
import com.intellij.lang.jsgraphql.psi.GraphQLElement
import com.intellij.lang.jsgraphql.psi.GraphQLElementFactory
import com.intellij.lang.jsgraphql.psi.GraphQLFile
import com.intellij.lang.jsgraphql.psi.GraphQLNamedElement
import com.intellij.lang.jsgraphql.psi.GraphQLSchemaDefinition
import com.intellij.lang.jsgraphql.psi.GraphQLSchemaExtension
import com.intellij.lang.jsgraphql.psi.GraphQLStringValue
import com.intellij.openapi.project.Project

const val NULLABILITY_URL = "https://specs.apollo.dev/nullability/$NULLABILITY_VERSION"

Expand Down Expand Up @@ -79,10 +81,48 @@ private fun GraphQLFile.hasImportFor(name: String, isDirective: Boolean, definit
return false
}

val String.nameWithoutPrefix get() = substringAfter("__")
private val String.nameWithoutPrefix get() = substringAfter("__")

val GraphQLNamedElement.nameWithoutPrefix get() = name!!.nameWithoutPrefix

fun String.nameForImport(isDirective: Boolean) = "${if (isDirective) "@" else ""}${this.nameWithoutPrefix}"

val GraphQLNamedElement.nameForImport get() = if (this is GraphQLDirective) "@$nameWithoutPrefix" else nameWithoutPrefix


fun createLinkDirectiveSchemaExtension(
project: Project,
importedNames: Set<String>,
definitions: List<GQLDefinition>,
definitionsUrl: String,
): GraphQLSchemaExtension {
// If any of the imported name is a directive, add its argument types to the import list
val knownDefinitionNames = definitions.filterIsInstance<GQLNamed>().map { it.name }
val additionalNames = importedNames.flatMap { importedName ->
definitions.directives().firstOrNull { "@${it.name}" == importedName }
?.arguments
?.map { it.type.rawType().name }
?.filter { it in knownDefinitionNames }.orEmpty()
}.toSet()

return GraphQLElementFactory.createFile(
project,
"""
extend schema
@link(
url: "$definitionsUrl",
import: [${(importedNames + additionalNames).joinToString { it.quoted() }}]
)
""".trimIndent()
)
.findChildrenOfType<GraphQLSchemaExtension>().single()
}

fun createLinkDirective(
project: Project,
importedNames: Set<String>,
definitions: List<GQLDefinition>,
definitionsUrl: String,
): GraphQLDirective {
return createLinkDirectiveSchemaExtension(project, importedNames, definitions, definitionsUrl).directives.single()
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class ApolloV3ToV4MigrationTest : ApolloTestCase() {
@Test
fun testUpdateEnumClassUpperCase() = runMigration()

@Test
fun testAddLinkDirective() = runMigration(extension = "graphqls", fileNameInProject = "extra.graphqls")

private fun runMigration(extension: String = "kt", fileNameInProject: String? = null) {
val fileBaseName = getTestName(true)
if (fileNameInProject != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
extend type dog @typePolicy(keyFields: "id")
extend type Cat @typePolicy(keyFields: "id")

extend type Query @fieldPolicy(forField: "animal" keyArgs: "id")
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
extend schema
@link(
url: "https://specs.apollo.dev/kotlin_labs/v0.3",
import: ["@typePolicy", "@fieldPolicy"]
)

extend type dog @typePolicy(keyFields: "id")
extend type Cat @typePolicy(keyFields: "id")

extend type Query @fieldPolicy(forField: "animal" keyArgs: "id")

0 comments on commit 8c6a37e

Please sign in to comment.