From d173f5eacadefb228b7641f2bfc73e0564929df2 Mon Sep 17 00:00:00 2001 From: Benoit Lubek Date: Thu, 12 Jan 2023 12:17:06 +0100 Subject: [PATCH] Import wip IntelliJ plugin module (#4615) --- .github/workflows/benchmarks.yml | 8 +- .github/workflows/defer-with-router-tests.yml | 6 +- .github/workflows/pr.yml | 118 ++++++- .github/workflows/push.yml | 10 +- .github/workflows/tag.yml | 28 +- .gitignore | 5 +- .../runConfigurations/Run_IntelliJ_plugin.xml | 29 ++ CONTRIBUTING.md | 10 +- build.gradle.kts | 3 +- gradle/repositories.gradle.kts | 9 + intellij-plugin/CHANGELOG.md | 28 ++ intellij-plugin/README.md | 27 ++ intellij-plugin/build.gradle.kts | 150 +++++++++ intellij-plugin/gradle.properties | 29 ++ .../apollographql/ijplugin/ApolloBundle.kt | 19 ++ .../listeners/ApolloProjectManagerListener.kt | 12 + .../OperationUsageMarkerProvider.kt | 58 ++++ .../ijplugin/refactoring/Refactoring.kt | 86 +++++ .../migration/ApolloV2ToV3MigrationAction.kt | 33 ++ .../ApolloV2ToV3MigrationProcessor.kt | 314 ++++++++++++++++++ .../migration/item/AddUseVersion2Compat.kt | 50 +++ .../item/CommentDependenciesInToml.kt | 55 +++ .../migration/item/DeletesElements.kt | 6 + .../migration/item/MigrationItem.kt | 12 + .../migration/item/MigrationItemUsageInfo.kt | 62 ++++ .../item/RemoveDependenciesInBuildKts.kt | 39 +++ .../migration/item/RemoveMethodCall.kt | 50 +++ .../migration/item/RemoveMethodImport.kt | 10 + .../item/UpdateAddCustomTypeAdapter.kt | 50 +++ .../migration/item/UpdateClassName.kt | 53 +++ .../item/UpdateCustomTypeMappingInBuildKts.kt | 82 +++++ .../item/UpdateEnumValueUpperCase.kt | 68 ++++ .../migration/item/UpdateFieldName.kt | 22 ++ .../migration/item/UpdateFileUpload.kt | 34 ++ .../item/UpdateGradleDependenciesBuildKts.kt | 50 +++ .../item/UpdateGradleDependenciesInToml.kt | 105 ++++++ .../item/UpdateGradlePluginInBuildKts.kt | 97 ++++++ .../item/UpdateGraphqlSourceDirectorySet.kt | 57 ++++ .../migration/item/UpdateHttpCache.kt | 145 ++++++++ .../migration/item/UpdateIdlingResource.kt | 30 ++ .../migration/item/UpdateInputAbsent.kt | 20 ++ .../item/UpdateLruNormalizedCacheFactory.kt | 96 ++++++ .../migration/item/UpdateMethodName.kt | 25 ++ .../item/UpdateOkHttpExecutionContext.kt | 59 ++++ .../migration/item/UpdatePackageName.kt | 26 ++ .../item/UpdateSqlNormalizedCacheFactory.kt | 48 +++ .../services/ApolloApplicationService.kt | 9 + .../ijplugin/services/ApolloProjectService.kt | 11 + .../services/impl/ApolloProjectServiceImpl.kt | 197 +++++++++++ .../com/apollographql/ijplugin/util/Apollo.kt | 25 ++ .../com/apollographql/ijplugin/util/Gradle.kt | 12 + .../apollographql/ijplugin/util/Logging.kt | 70 ++++ .../com/apollographql/ijplugin/util/Psi.kt | 18 + .../com/apollographql/ijplugin/util/String.kt | 9 + .../apollographql/ijplugin/util/Threading.kt | 9 + .../src/main/resources/META-INF/plugin.xml | 42 +++ .../main/resources/META-INF/pluginIcon.svg | 18 + .../main/resources/icons/gutter-operation.svg | 18 + .../messages/ApolloBundle.properties | 18 + .../ijplugin/ApolloV2ToV3MigrationTest.kt | 131 ++++++++ .../com/apollographql/ijplugin/MavenUtil.kt | 49 +++ .../v2-to-v3/addCustomTypeAdapter.kt | 47 +++ .../v2-to-v3/addCustomTypeAdapter_after.kt | 49 +++ .../v2-to-v3/addUseVersion2Compat.gradle.kts | 3 + .../addUseVersion2Compat_after.gradle.kts | 7 + .../testData/migration/v2-to-v3/httpCache.kt | 43 +++ .../migration/v2-to-v3/httpCache_after.kt | 32 ++ .../v2-to-v3/inMemoryNormalizedCache.kt | 69 ++++ .../v2-to-v3/inMemoryNormalizedCache_after.kt | 47 +++ ...dleDependenciesInBuildGradleKts.gradle.kts | 7 + ...endenciesInBuildGradleKts_after.gradle.kts | 3 + ...pendenciesInLibsVersionsToml.versions.toml | 14 + ...ciesInLibsVersionsToml_after.versions.toml | 20 ++ .../migration/v2-to-v3/sqlNormalizedCache.kt | 25 ++ .../v2-to-v3/sqlNormalizedCache_after.kt | 26 ++ .../migration/v2-to-v3/updateClassName.kt | 7 + .../v2-to-v3/updateClassName_after.kt | 7 + .../updateCustomTypeMapping.gradle.kts | 14 + .../updateCustomTypeMapping_after.gradle.kts | 14 + .../v2-to-v3/updateEnumValueUpperCase.kt | 63 ++++ .../updateEnumValueUpperCase_after.kt | 63 ++++ .../migration/v2-to-v3/updateFileUpload.kt | 7 + .../v2-to-v3/updateFileUpload_after.kt | 9 + ...dleDependenciesInBuildGradleKts.gradle.kts | 7 + ...endenciesInBuildGradleKts_after.gradle.kts | 7 + ...pendenciesInLibsVersionsToml.versions.toml | 17 + ...ciesInLibsVersionsToml_after.versions.toml | 17 + ...updateGraphqlSourceDirectorySet.gradle.kts | 5 + ...GraphqlSourceDirectorySet_after.gradle.kts | 9 + .../migration/v2-to-v3/updateInput.kt | 9 + .../migration/v2-to-v3/updateInput_after.kt | 9 + .../migration/v2-to-v3/updateMethodName.kt | 19 ++ .../v2-to-v3/updateMethodName_after.kt | 17 + .../v2-to-v3/updateOkHttpExecutionContext.kt | 10 + .../updateOkHttpExecutionContext_after.kt | 10 + .../migration/v2-to-v3/updateOperationName.kt | 10 + .../v2-to-v3/updateOperationName_after.kt | 10 + .../migration/v2-to-v3/updatePackageName.kt | 9 + .../v2-to-v3/updatePackageName_after.kt | 9 + ...adeGradlePluginInBuildGradleKts.gradle.kts | 14 + ...dlePluginInBuildGradleKts_after.gradle.kts | 18 + .../test/testData/migration/v2-to-v3/watch.kt | 13 + .../migration/v2-to-v3/watch_after.kt | 12 + settings.gradle.kts | 2 + 104 files changed, 3744 insertions(+), 34 deletions(-) create mode 100644 .idea/runConfigurations/Run_IntelliJ_plugin.xml create mode 100644 intellij-plugin/CHANGELOG.md create mode 100644 intellij-plugin/README.md create mode 100644 intellij-plugin/build.gradle.kts create mode 100644 intellij-plugin/gradle.properties create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/ApolloBundle.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/listeners/ApolloProjectManagerListener.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/navigation/OperationUsageMarkerProvider.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/Refactoring.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationAction.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationProcessor.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/AddUseVersion2Compat.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/CommentDependenciesInToml.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/DeletesElements.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItem.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItemUsageInfo.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveDependenciesInBuildKts.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodCall.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodImport.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateAddCustomTypeAdapter.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateClassName.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateCustomTypeMappingInBuildKts.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateEnumValueUpperCase.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFieldName.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFileUpload.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesBuildKts.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesInToml.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradlePluginInBuildKts.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGraphqlSourceDirectorySet.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateHttpCache.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateIdlingResource.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateInputAbsent.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateLruNormalizedCacheFactory.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateMethodName.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateOkHttpExecutionContext.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdatePackageName.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateSqlNormalizedCacheFactory.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloApplicationService.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloProjectService.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/impl/ApolloProjectServiceImpl.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Apollo.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Gradle.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Logging.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Psi.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt create mode 100644 intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Threading.kt create mode 100644 intellij-plugin/src/main/resources/META-INF/plugin.xml create mode 100644 intellij-plugin/src/main/resources/META-INF/pluginIcon.svg create mode 100644 intellij-plugin/src/main/resources/icons/gutter-operation.svg create mode 100644 intellij-plugin/src/main/resources/messages/ApolloBundle.properties create mode 100644 intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/ApolloV2ToV3MigrationTest.kt create mode 100644 intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/MavenUtil.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat_after.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts_after.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml.versions.toml create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml_after.versions.toml create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping_after.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts_after.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml.versions.toml create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml_after.versions.toml create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet_after.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName_after.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts_after.gradle.kts create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/watch.kt create mode 100644 intellij-plugin/src/test/testData/migration/v2-to-v3/watch_after.kt diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 170e36776e9..ab3250946fb 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -11,12 +11,12 @@ jobs: runs-on: macos-11 if: github.repository == 'apollographql/apollo-kotlin' steps: - - uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28 #v3 - - uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 #v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 with: distribution: 'temurin' java-version: '11' - - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee #v2.1.4 + - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef #v2.3.3 - run: | ulimit -c unlimited # Workaround an issue where kotlinNpmInstall outputs @@ -31,7 +31,7 @@ jobs: ./gradlew -p benchmark assembleRelease assembleReleaseAndroidTest # Step can be removed if/when gcloud is added to the macos image - See https://github.com/actions/virtual-environments/issues/4639 - name: Setup gcloud - uses: google-github-actions/setup-gcloud@877d4953d2c70a0ba7ef3290ae968eb24af233bb #v0.6.0 + uses: google-github-actions/setup-gcloud@d51b5346f85640ec2aa2fa057354d2b82c2fcbce #v1.0.1 - name: microbenchmarks uses: martinbonnin/run-benchmarks@ef9043b9a646a109f7381a4bf20f82ead5cbd382 #main with: diff --git a/.github/workflows/defer-with-router-tests.yml b/.github/workflows/defer-with-router-tests.yml index 0583c97d3fb..221fce976bd 100644 --- a/.github/workflows/defer-with-router-tests.yml +++ b/.github/workflows/defer-with-router-tests.yml @@ -10,7 +10,7 @@ jobs: if: github.repository == 'apollographql/apollo-kotlin' steps: - name: Checkout project - uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28 #v3 + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 - name: Install and run subgraph working-directory: tests/defer/router/subgraphs/computers @@ -27,13 +27,13 @@ jobs: ./router --supergraph tests/defer/router/simple-supergraph.graphqls & - name: Setup Java - uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 #v3 + uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 with: distribution: 'temurin' java-version: 11 - name: Setup Gradle - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee #v2.1.4 + uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef #v2.3.3 - name: Run Apollo Kotlin @defer tests env: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ab2e6605931..c59310bad59 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -15,12 +15,12 @@ jobs: tests-gradle: runs-on: macos-11 steps: - - uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28 #v3 - - uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 #v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 with: distribution: 'temurin' java-version: 11 - - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee #v2.1.4 + - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef #v2.3.3 - name: Build with Gradle run: | ulimit -c unlimited @@ -32,7 +32,7 @@ jobs: - name: Collect Diagnostics if: always() run: ./scripts/collect-diagnostics.main.kts - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 #v3 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 if: always() with: name: tests-gradle.zip @@ -41,12 +41,12 @@ jobs: tests-no-gradle: runs-on: macos-11 steps: - - uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28 #v3 - - uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 #v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 with: distribution: 'temurin' java-version: 11 - - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee #v2.1.4 + - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef #v2.3.3 - name: Build with Gradle run: | ulimit -c unlimited @@ -58,7 +58,7 @@ jobs: - name: Collect Diagnostics if: always() run: ./scripts/collect-diagnostics.main.kts - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 #v3 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 if: always() with: name: tests-no-gradle.zip @@ -67,12 +67,12 @@ jobs: tests-integration: runs-on: macos-11 steps: - - uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28 #v3 - - uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 #v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 with: distribution: 'temurin' java-version: 11 - - uses: gradle/gradle-build-action@aab26ac684526c7cb10f96e3c3734bbc51749736 #v2 + - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef #v2.3.3 - name: Build with Gradle run: | ulimit -c unlimited @@ -84,8 +84,102 @@ jobs: - name: Collect Diagnostics if: always() run: ./scripts/collect-diagnostics.main.kts - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 #v3 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 if: always() with: name: tests-integration.zip path: diagnostics.zip + + intellij-plugin: + name: Build IntelliJ Plugin + runs-on: ubuntu-latest + outputs: + version: ${{ steps.properties.outputs.version }} + changelog: ${{ steps.properties.outputs.changelog }} + steps: + # Free GitHub Actions Environment Disk Space + - name: Maximize Build Space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + + # Setup Java 11 environment for the next steps + - name: Setup Java + uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 + with: + distribution: 'temurin' + java-version: 11 + + # Set environment variables + - name: Export Properties + id: properties + shell: bash + run: | + PROPERTIES="$(./gradlew :intellij-plugin:properties --console=plain -q)" + VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" + NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')" + CHANGELOG="$(./gradlew :intellij-plugin:getChangelog --unreleased --no-header --console=plain -q)" + CHANGELOG="${CHANGELOG//'%'/'%25'}" + CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" + CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" + + echo "::set-output name=version::$VERSION" + echo "::set-output name=name::$NAME" + echo "::set-output name=changelog::$CHANGELOG" + echo "::set-output name=pluginVerifierHomeDir::~/.pluginVerifier" + + ./gradlew :intellij-plugin:listProductsReleases # prepare list of IDEs for Plugin Verifier + + # Run tests + - name: Run Tests + run: ./gradlew :intellij-plugin:check + + # Collect Tests Result of failed tests + - name: Collect Tests Result + if: ${{ failure() }} + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 + with: + name: tests-result + path: ${{ github.workspace }}/intellij-plugin/build/reports/tests + + # Cache Plugin Verifier IDEs + - name: Setup Plugin Verifier IDEs Cache + uses: actions/cache@58c146cc91c5b9e778e71775dfe9bf1442ad9a12 #v3.2.3 + with: + path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides + key: plugin-verifier-${{ hashFiles('intellij-plugin/build/listProductsReleases.txt') }} + + # Run Verify Plugin task and IntelliJ Plugin Verifier tool + - name: Run Plugin Verification tasks + run: ./gradlew :intellij-plugin:runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} + + # Collect Plugin Verifier Result + - name: Collect Plugin Verifier Result + if: ${{ always() }} + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 + with: + name: pluginVerifier-result + path: ${{ github.workspace }}/intellij-plugin/build/reports/pluginVerifier + + # Prepare plugin archive content for creating artifact + - name: Prepare Plugin Artifact + id: artifact + shell: bash + run: | + cd ${{ github.workspace }}/intellij-plugin/build/distributions + FILENAME=`ls *.zip` + unzip "$FILENAME" -d content + + echo "::set-output name=filename::${FILENAME:0:-4}" + + # Store already-built plugin as an artifact for downloading + - name: Upload artifact + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 + with: + name: ${{ steps.artifact.outputs.filename }} + path: ./intellij-plugin/build/distributions/content/*/* diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 4a6266c8e80..cc5a3479194 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -12,12 +12,12 @@ jobs: runs-on: macos-11 if: github.repository == 'apollographql/apollo-kotlin' steps: - - uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28 #v3 - - uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 #v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 with: distribution: 'temurin' java-version: '11' - - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee #v2.1.4 + - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef #v2.3.3 - name: Build with Gradle run: | ulimit -c unlimited @@ -37,13 +37,13 @@ jobs: - name: Collect Diagnostics if: always() run: ./scripts/collect-diagnostics.main.kts - - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 #v3 + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce #v3.1.2 if: always() with: name: push.zip path: push.zip - name: Deploy Kdoc to github pages - uses: JamesIves/github-pages-deploy-action@830e6a4f7c81743c52f3fed0ac67428feff9620a #v4.2.5 + uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6 #v4.4.1 with: branch: gh-pages # The branch the action should deploy to. folder: build/dokkaHtml # The folder the action should deploy. diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 745542ff928..b45214d721a 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -6,17 +6,18 @@ on: - '*' jobs: - publish: + publish-libraries: + name: Publish libraries runs-on: macos-11 if: github.repository == 'apollographql/apollo-kotlin' steps: - - uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28 #v3 - - uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 #v3 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 with: distribution: 'temurin' java-version: 11 - - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee #v2.1.4 - - name: Build with Gradle + - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef #v2.3.3 + - name: Publish to Maven Central run: | ./gradlew --no-build-cache ciPublishRelease -Pgradle.publish.key="${{ secrets.GRADLE_PUBLISH_KEY }}" -Pgradle.publish.secret="${{ secrets.GRADLE_PUBLISH_SECRET }}" env: @@ -25,3 +26,20 @@ jobs: COM_APOLLOGRAPHQL_PROFILE_ID: ${{ secrets.COM_APOLLOGRAPHQL_PROFILE_ID }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY_PASSWORD: ${{ secrets.GPG_PRIVATE_KEY_PASSWORD }} + + publish-intellij-plugin: + name: Publish IntelliJ plugin + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - uses: actions/setup-java@1df8dbefe2a8cbc99770194893dd902763bee34b #v3.9.0 + with: + distribution: 'temurin' + java-version: 11 + - name: Publish to JetBrains marketplace + run: ./gradlew --no-build-cache :intellij-plugin:publishPlugin + env: + PUBLISH_TOKEN: ${{ secrets.IJ_PLUGIN_PUBLISH_TOKEN }} + CERTIFICATE_CHAIN: ${{ secrets.IJ_PLUGIN_CERTIFICATE_CHAIN }} + PRIVATE_KEY: ${{ secrets.IJ_PLUGIN_PRIVATE_KEY }} + PRIVATE_KEY_PASSWORD: ${{ secrets.IJ_PLUGIN_PRIVATE_KEY_PASSWORD }} diff --git a/.gitignore b/.gitignore index a9b15db4032..59e2df93e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ package-lock.json .sqldelight # Local Netlify folder -.netlify \ No newline at end of file +.netlify + +# IntelliJ plugin +intellij-plugin/mockJDK diff --git a/.idea/runConfigurations/Run_IntelliJ_plugin.xml b/.idea/runConfigurations/Run_IntelliJ_plugin.xml new file mode 100644 index 00000000000..afdce5b4984 --- /dev/null +++ b/.idea/runConfigurations/Run_IntelliJ_plugin.xml @@ -0,0 +1,29 @@ + + + + + + + + + true + true + false + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32d4cc57bbc..ec2a6b61e0a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -168,7 +168,7 @@ Please note that we will not accept pull requests for style changes. Apollo Kotlin observes [semantic versioning](https://semver.org/). Between major releases, breaking changes are not allowed and any public API change will fail the build. -If that happens, you will need to run `./gradlew apiDump` and check for any incompatible changes before commiting these +If that happens, you will need to run `./gradlew apiDump` and check for any incompatible changes before committing these files. ## Deprecation @@ -251,6 +251,8 @@ following events: - All apiCheck - `tests-integration` - All integration tests (except Java 9+ ones) +- `intellij-plugin` + - IntelliJ plugin build and tests ### On pushes to `main` branch @@ -271,5 +273,7 @@ following events: **Job:** -- `publish` - - Publish to Maven Central +- `publish-libraries` + - Publish libraries to Maven Central +- `publish-intellij-plugin` + - Publish IntelliJ plugin to Jetbrains Marketplace diff --git a/build.gradle.kts b/build.gradle.kts index f7a34652d85..3b8a42fd70d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -136,6 +136,7 @@ configure { "apollo-normalized-cache-api-incubating", "apollo-normalized-cache-incubating", "apollo-normalized-cache-sqlite-incubating", + "intellij-plugin", ) ) nonPublicMarkers.addAll( @@ -158,4 +159,4 @@ tasks.register("rmbuild") { } }.count() } -} \ No newline at end of file +} diff --git a/gradle/repositories.gradle.kts b/gradle/repositories.gradle.kts index e394db7da5f..2dc187226d9 100644 --- a/gradle/repositories.gradle.kts +++ b/gradle/repositories.gradle.kts @@ -21,6 +21,15 @@ listOf(pluginManagement.repositories, dependencyResolutionManagement.repositorie includeModule("com.github.ben-manes", "gradle-versions-plugin") // Because we use 1.6.10 during sync and this version is not on mavenCentral includeVersion("org.jetbrains.kotlin.plugin.serialization", "org.jetbrains.kotlin.plugin.serialization.gradle.plugin", "1.6.10") + + // For org.jetbrains.intellij + includeModule("org.jetbrains.intellij", "org.jetbrains.intellij.gradle.plugin") + includeModule("org.jetbrains.intellij.plugins", "gradle-intellij-plugin") + includeModule("gradle.plugin.org.jetbrains.gradle.plugin.idea-ext", "gradle-idea-ext") + + // For org.jetbrains.changelog + includeModule("org.jetbrains.changelog", "org.jetbrains.changelog.gradle.plugin") + includeModule("org.jetbrains.intellij.plugins", "gradle-changelog-plugin") } } diff --git a/intellij-plugin/CHANGELOG.md b/intellij-plugin/CHANGELOG.md new file mode 100644 index 00000000000..7690671b17b --- /dev/null +++ b/intellij-plugin/CHANGELOG.md @@ -0,0 +1,28 @@ + + +# Apollo IntelliJ Plugin Changelog + +## [Unreleased] + +### Changed + +- GraphQL file monitoring and automatic codegen is more responsive, and doesn't focus a tool window if codegen fails. + +## [1.0.0-preview.4] - 2022-12-22 + +### Added + +- Migration helpers to upgrade a project from Apollo Android v2 to Apollo Kotlin v3 + +## [1.0.0-preview.1] - 2022-12-12 + +### Added + +- Initial scaffold created + from [IntelliJ Platform Plugin Template](https://github.com/JetBrains/intellij-platform-plugin-template) + +[Unreleased]: https://github.com/apollographql/apollo-intellij-plugin/compare/v1.0.0-preview.4...HEAD + +[1.0.0-preview.1]: https://github.com/apollographql/apollo-intellij-plugin/commits/v1.0.0-preview.1 + +[1.0.0-preview.4]: https://github.com/apollographql/apollo-intellij-plugin/compare/v1.0.0-preview.1...v1.0.0-preview.4 diff --git a/intellij-plugin/README.md b/intellij-plugin/README.md new file mode 100644 index 00000000000..b8fce212352 --- /dev/null +++ b/intellij-plugin/README.md @@ -0,0 +1,27 @@ +# Apollo IntelliJ Plugin + +> Work in progress! + +![Build](https://github.com/apollographql/apollo-intellij-plugin/workflows/Build/badge.svg) +[![Version](https://img.shields.io/jetbrains/plugin/v/20645.svg)](https://plugins.jetbrains.com/plugin/20645) +[![Downloads](https://img.shields.io/jetbrains/plugin/d/20645.svg)](https://plugins.jetbrains.com/plugin/20645) + + + +This plugin for Android Studio and IntelliJ helps you work with the +[Apollo Kotlin](https://github.com/apollographql/apollo-kotlin) GraphQL library. + +Note: this plugin is still in early development and is not yet ready for production use. + + + +## Installation + +In Android Studio or IntelliJ, add the custom plugin repository: + +Settings/Preferences > Plugins > ⚙️ > Manage Plugin +Repositories > + > https://plugins.jetbrains.com/plugins/preview/20645 + +Then: + +Marketplace > Search for "Apollo GraphQL" > Install Plugin diff --git a/intellij-plugin/build.gradle.kts b/intellij-plugin/build.gradle.kts new file mode 100644 index 00000000000..13ab72f289b --- /dev/null +++ b/intellij-plugin/build.gradle.kts @@ -0,0 +1,150 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.changelog.markdownToHTML +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.net.URL + +fun properties(key: String) = project.findProperty(key).toString() + +plugins { + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.intellij").version("1.8.0") + id("org.jetbrains.changelog").version("1.3.1") +} + +group = properties("pluginGroup") + +// Use the global version defined in the root project +version = project.findProperty("VERSION_NAME").toString() + +repositories { + mavenCentral() +} + +// Set the JVM language level used to build project. Use Java 11 for 2020.3+, and Java 17 for 2022.2+. +kotlin { + jvmToolchain { + (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(properties("javaVersion").toInt())) + } +} + +// Configure Gradle IntelliJ Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html +intellij { + pluginName.set(properties("pluginName")) + version.set(properties("platformVersion")) + type.set(properties("platformType")) + + // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. + plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) +} + +// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin +changelog { + version.set(properties("VERSION_NAME")) + groups.set(emptyList()) +} + +tasks { + withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xcontext-receivers") + } + } + + patchPluginXml { + version.set(properties("VERSION_NAME")) + sinceBuild.set(properties("pluginSinceBuild")) + untilBuild.set(properties("pluginUntilBuild")) + + // Extract the section from README.md and provide for the plugin's manifest + pluginDescription.set( + projectDir.resolve("README.md").readText().lines().run { + val start = "" + val end = "" + + if (!containsAll(listOf(start, end))) { + throw GradleException("Plugin description section not found in README.md:\n$start ... $end") + } + subList(indexOf(start) + 1, indexOf(end)) + }.joinToString("\n").run { markdownToHTML(this) } + ) + + // Get the latest available change notes from the changelog file + changeNotes.set(provider { + changelog.run { + getOrNull(properties("VERSION_NAME")) ?: getUnreleased() + }.toHTML() + }) + } + + // Configure UI tests plugin + // Read more: https://github.com/JetBrains/intellij-ui-test-robot + runIdeForUiTests { + systemProperty("robot-server.port", "8082") + systemProperty("ide.mac.message.dialogs.as.sheets", "false") + systemProperty("jb.privacy.policy.text", "") + systemProperty("jb.consents.confirmation.enabled", "false") + + // Enables debug logging for the plugin + systemProperty("idea.log.debug.categories", "Apollo") + } + + runIde { + // Enables debug logging for the plugin + systemProperty("idea.log.debug.categories", "Apollo") + + // Use a custom IntelliJ installation. Set this property in your local ~/.gradle/gradle.properties file. + // (for AS, it should be something like '/Applications/Android Studio.app/Contents') + // See https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-gradle-build-script + if (project.hasProperty("apolloIntellijPlugin.ideDir")) { + ideDir.set(file(project.property("apolloIntellijPlugin.ideDir")!!)) + } + } + + signPlugin { + certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) + privateKey.set(System.getenv("PRIVATE_KEY")) + password.set(System.getenv("PRIVATE_KEY_PASSWORD")) + } + + publishPlugin { + dependsOn("patchChangelog") + token.set(System.getenv("PUBLISH_TOKEN")) + // Currently we release to a specific "preview" release channel so the plugin is not listed on the Marketplace + // Change to "default" to release to the main channel. + // Read more: https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel + channels.set(listOf("preview")) + } + + // Log tests + withType { + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events.add(TestLogEvent.PASSED) + events.add(TestLogEvent.FAILED) + showStandardStreams = true + } + } + + test { + // Setup fake JDK for maven dependencies to work + // See https://jetbrains-platform.slack.com/archives/CPL5291JP/p1664105522154139 and https://youtrack.jetbrains.com/issue/IJSDK-321 + systemProperty("idea.home.path", file("mockJDK").absolutePath) + } +} + +// Setup fake JDK for maven dependencies to work +// See https://jetbrains-platform.slack.com/archives/CPL5291JP/p1664105522154139 and https://youtrack.jetbrains.com/issue/IJSDK-321 +tasks.register("downloadMockJdk") { + doLast { + val rtJar = file("mockJDK/java/mockJDK-1.7/jre/lib/rt.jar") + if (!rtJar.exists()) { + rtJar.parentFile.mkdirs() + rtJar.writeBytes(URL("https://github.com/JetBrains/intellij-community/raw/master/java/mockJDK-1.7/jre/lib/rt.jar").openStream().readBytes()) + } + } +} + +tasks.named("test").configure { + dependsOn("downloadMockJdk") +} diff --git a/intellij-plugin/gradle.properties b/intellij-plugin/gradle.properties new file mode 100644 index 00000000000..412836f7424 --- /dev/null +++ b/intellij-plugin/gradle.properties @@ -0,0 +1,29 @@ +# IntelliJ Platform Artifacts Repositories +# -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html +pluginGroup=com.apollographql +pluginName=apollo-intellij-plugin +pluginRepositoryUrl=https://github.com/apollographql/apollo-kotlin + +# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html +# for insight into build numbers and IntelliJ Platform versions. +pluginSinceBuild=212 +# This can be kept empty to mean no upper bound, but it's recommended to set it to whatever was tested +pluginUntilBuild=223.* +# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension +platformType=IC +# Corresponds to AS Chipmunk (2021.2.1) -> https://plugins.jetbrains.com/docs/intellij/android-studio-releases-list.html +# See also https://plugins.jetbrains.com/docs/intellij/android-studio.html +platformVersion=212.5712.43 + +# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html +# To find the version of a plugin relative to the platoform version, see the plugin's page on the Marketplace, +# e.g. for the toml plugin: https://plugins.jetbrains.com/plugin/8195-toml/versions/stable +platformPlugins=com.intellij.java, org.jetbrains.kotlin, com.intellij.gradle, com.intellij.lang.jsgraphql:3.3.0, org.toml.lang:0.2.155.4114-212 + +# Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 +javaVersion=11 + +# Opt-out flag for bundling Kotlin standard library. +# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. +# suppress inspection "UnusedProperty" +kotlin.stdlib.default.dependency=false diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/ApolloBundle.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/ApolloBundle.kt new file mode 100644 index 00000000000..9ca19abf7ed --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/ApolloBundle.kt @@ -0,0 +1,19 @@ +package com.apollographql.ijplugin + +import com.intellij.DynamicBundle +import org.jetbrains.annotations.NonNls +import org.jetbrains.annotations.PropertyKey + +@NonNls +private const val BUNDLE = "messages.ApolloBundle" + +@Suppress("SpreadOperator", "unused") +object ApolloBundle : DynamicBundle(BUNDLE) { + @JvmStatic + fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = + getMessage(key, *params) + + @JvmStatic + fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = + getLazyMessage(key, *params) +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/listeners/ApolloProjectManagerListener.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/listeners/ApolloProjectManagerListener.kt new file mode 100644 index 00000000000..7c5e3d633a0 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/listeners/ApolloProjectManagerListener.kt @@ -0,0 +1,12 @@ +package com.apollographql.ijplugin.listeners + +import com.apollographql.ijplugin.services.apolloProjectService +import com.apollographql.ijplugin.util.logd +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManagerListener + +internal class ApolloProjectManagerListener : ProjectManagerListener { + override fun projectOpened(project: Project) { + logd("isApolloKotlin3Project=" + project.apolloProjectService().isApolloKotlin3Project) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/navigation/OperationUsageMarkerProvider.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/navigation/OperationUsageMarkerProvider.kt new file mode 100644 index 00000000000..fca5b0884df --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/navigation/OperationUsageMarkerProvider.kt @@ -0,0 +1,58 @@ +package com.apollographql.ijplugin.navigation + +import com.apollographql.ijplugin.ApolloBundle +import com.apollographql.ijplugin.services.apolloProjectService +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder +import com.intellij.openapi.util.IconLoader +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMember +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UastCallKind +import org.jetbrains.uast.toUElement + +private val APOLLO_OPERATION_TYPES = arrayOf( + "com.apollographql.apollo3.api.Query", + "com.apollographql.apollo3.api.Mutation", + "com.apollographql.apollo3.api.Subscription" +) + +/** + * Adds a gutter icon to Apollo operation usage in Kotlin/Java, allowing to navigate to the corresponding operation GraphQL definition. + * + * TODO: for now the icon appears but navigation to the definition is not yet implemented. + */ +class OperationUsageMarkerProvider : RelatedItemLineMarkerProvider() { + private val gutterIcon by lazy { IconLoader.getIcon("/icons/gutter-operation.svg", this::class.java) } + + override fun collectNavigationMarkers( + element: PsiElement, + result: MutableCollection>, + ) { + val apolloProjectService = element.project.apolloProjectService() + if (!apolloProjectService.isApolloKotlin3Project) return + + val uElement = element.toUElement() + if (uElement !is UCallExpression || uElement.kind != UastCallKind.CONSTRUCTOR_CALL) return + + val isApolloOperation = (uElement.resolve() as? PsiMember) + ?.containingClass + ?.implementsList + ?.referencedTypes + ?.any { classType -> + classType.resolve()?.qualifiedName in APOLLO_OPERATION_TYPES + } == true + + if (isApolloOperation) { + val psiLeaf = PsiTreeUtil.getDeepestFirst(element) + val builder = + NavigationGutterIconBuilder.create(gutterIcon) + .setTargets(element) + .setTooltipText(ApolloBundle.message("navigation.operation.tooltip")) + .createLineMarkerInfo(psiLeaf) + result.add(builder) + } + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/Refactoring.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/Refactoring.kt new file mode 100644 index 00000000000..358f466291c --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/Refactoring.kt @@ -0,0 +1,86 @@ +package com.apollographql.ijplugin.refactoring + +import com.apollographql.ijplugin.util.logw +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.project.Project +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMigration +import com.intellij.psi.PsiPackage +import com.intellij.psi.PsiReference +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.searches.ClassInheritorsSearch +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.refactoring.rename.RenamePsiElementProcessor + +fun findOrCreatePackage(project: Project, migration: PsiMigration, qName: String): PsiPackage { + val aPackage = JavaPsiFacade.getInstance(project).findPackage(qName) + return aPackage ?: WriteAction.compute { + migration.createPackage(qName) + } +} + +fun findOrCreateClass(project: Project, migration: PsiMigration, qName: String): PsiClass { + val classes = JavaPsiFacade.getInstance(project).findClasses(qName, GlobalSearchScope.allScope(project)) + return classes.firstOrNull() ?: WriteAction.compute { + migration.createClass(qName) + } +} + +fun PsiElement.bindReferencesToElement(element: PsiElement): PsiElement? { + for (reference in references) { + try { + return reference.bindToElement(element) + } catch (t: Throwable) { + logw(t, "bindToElement failed: ignoring") + } + } + return null +} + +fun findMethodReferences( + project: Project, + className: String, + methodName: String, + extensionTargetClassName: String? = null, +): Collection { + val psiLookupClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) ?: return emptyList() + val methods = psiLookupClass.findMethodsByName(methodName, false) + .filter { method -> + if (extensionTargetClassName == null) return@filter true + // In Kotlin extensions, the target is passed to the first parameter + if (method.parameterList.parametersCount < 1) return@filter false + val firstParameter = method.parameterList.parameters.first() + val firstParameterType = (firstParameter.type as? PsiClassType)?.rawType()?.canonicalText + firstParameterType == extensionTargetClassName + } + return methods.flatMap { method -> + val processor = RenamePsiElementProcessor.forElement(method) + processor.findReferences(method, GlobalSearchScope.projectScope(project), false) + } +} + +fun findFieldReferences(project: Project, className: String, fieldName: String): Collection { + val psiLookupClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) ?: return emptyList() + val field = psiLookupClass.findFieldByName(fieldName, true) ?: return emptyList() + val processor = RenamePsiElementProcessor.forElement(field) + return processor.findReferences(field, GlobalSearchScope.projectScope(project), false) +} + +fun findClassReferences(project: Project, className: String): Collection { + val clazz = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) ?: return emptyList() + val processor = RenamePsiElementProcessor.forElement(clazz) + return processor.findReferences(clazz, GlobalSearchScope.projectScope(project), false) +} + +fun findPackageReferences(project: Project, packageName: String): Collection { + val psiPackage = JavaPsiFacade.getInstance(project).findPackage(packageName) ?: return emptyList() + return ReferencesSearch.search(psiPackage, GlobalSearchScope.projectScope(project), false).toList() +} + +fun findInheritorsOfClass(project: Project, className: String): Collection { + val psiLookupClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) ?: return emptyList() + return ClassInheritorsSearch.search(psiLookupClass, GlobalSearchScope.projectScope(project), true).toList() +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationAction.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationAction.kt new file mode 100644 index 00000000000..6acadbc4b1c --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationAction.kt @@ -0,0 +1,33 @@ +package com.apollographql.ijplugin.refactoring.migration + +import com.apollographql.ijplugin.ApolloBundle +import com.apollographql.ijplugin.util.logd +import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.ui.Messages + +class ApolloV2ToV3MigrationAction : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + logd() + val okCancelResult = Messages.showOkCancelDialog( + e.project, + ApolloBundle.message("action.ApolloV2ToV3MigrationAction.confirmDialog.message"), + ApolloBundle.message("action.ApolloV2ToV3MigrationAction.confirmDialog.title"), + ApolloBundle.message("action.ApolloV2ToV3MigrationAction.confirmDialog.ok"), + ApolloBundle.message("action.ApolloV2ToV3MigrationAction.confirmDialog.cancel"), + Messages.getQuestionIcon() + ) + + if (okCancelResult == Messages.OK) { + ApolloV2ToV3MigrationProcessor(e.project ?: return).run() + } + } + + override fun update(event: AnActionEvent) { + val presentation = event.presentation + val project = event.project + presentation.isEnabled = project != null + presentation.isVisible = !ActionPlaces.isPopupPlace(event.place) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationProcessor.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationProcessor.kt new file mode 100644 index 00000000000..d2b74f45d89 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/ApolloV2ToV3MigrationProcessor.kt @@ -0,0 +1,314 @@ +package com.apollographql.ijplugin.refactoring.migration + +import com.apollographql.ijplugin.ApolloBundle +import com.apollographql.ijplugin.refactoring.migration.item.AddUseVersion2Compat +import com.apollographql.ijplugin.refactoring.migration.item.CommentDependenciesInToml +import com.apollographql.ijplugin.refactoring.migration.item.DeletesElements +import com.apollographql.ijplugin.refactoring.migration.item.MigrationItem +import com.apollographql.ijplugin.refactoring.migration.item.MigrationItemUsageInfo +import com.apollographql.ijplugin.refactoring.migration.item.RemoveDependenciesInBuildKts +import com.apollographql.ijplugin.refactoring.migration.item.RemoveMethodCall +import com.apollographql.ijplugin.refactoring.migration.item.RemoveMethodImport +import com.apollographql.ijplugin.refactoring.migration.item.UpdateAddCustomTypeAdapter +import com.apollographql.ijplugin.refactoring.migration.item.UpdateClassName +import com.apollographql.ijplugin.refactoring.migration.item.UpdateCustomTypeMappingInBuildKts +import com.apollographql.ijplugin.refactoring.migration.item.UpdateEnumValueUpperCase +import com.apollographql.ijplugin.refactoring.migration.item.UpdateFieldName +import com.apollographql.ijplugin.refactoring.migration.item.UpdateFileUpload +import com.apollographql.ijplugin.refactoring.migration.item.UpdateGradleDependenciesBuildKts +import com.apollographql.ijplugin.refactoring.migration.item.UpdateGradleDependenciesInToml +import com.apollographql.ijplugin.refactoring.migration.item.UpdateGradlePluginInBuildKts +import com.apollographql.ijplugin.refactoring.migration.item.UpdateGraphqlSourceDirectorySet +import com.apollographql.ijplugin.refactoring.migration.item.UpdateHttpCache +import com.apollographql.ijplugin.refactoring.migration.item.UpdateIdlingResource +import com.apollographql.ijplugin.refactoring.migration.item.UpdateInputAbsent +import com.apollographql.ijplugin.refactoring.migration.item.UpdateLruNormalizedCacheFactory +import com.apollographql.ijplugin.refactoring.migration.item.UpdateMethodName +import com.apollographql.ijplugin.refactoring.migration.item.UpdateOkHttpExecutionContext +import com.apollographql.ijplugin.refactoring.migration.item.UpdatePackageName +import com.apollographql.ijplugin.refactoring.migration.item.UpdateSqlNormalizedCacheFactory +import com.apollographql.ijplugin.util.containingKtFileImportList +import com.apollographql.ijplugin.util.logd +import com.apollographql.ijplugin.util.logw +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.history.LocalHistory +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode +import com.intellij.openapi.externalSystem.util.ExternalSystemUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.GeneratedSourcesFilter +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiMigration +import com.intellij.psi.impl.migration.PsiMigrationManager +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.refactoring.BaseRefactoringProcessor +import com.intellij.refactoring.ui.UsageViewDescriptorAdapter +import com.intellij.usageView.UsageInfo +import org.jetbrains.kotlin.idea.util.application.isUnitTestMode +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.ImportPath +import org.jetbrains.plugins.gradle.util.GradleConstants + +/** + * Migrations of Apollo Android v2 to Apollo Kotlin v3. + * + * Implementation is based on [com.intellij.refactoring.migration.MigrationProcessor] and + * [org.jetbrains.android.refactoring.MigrateToAndroidxProcessor]. + */ +class ApolloV2ToV3MigrationProcessor(project: Project) : BaseRefactoringProcessor(project) { + private companion object { + private const val apollo2 = "com.apollographql.apollo" + private const val apollo3 = "com.apollographql.apollo3" + private const val apollo3LatestVersion = "3.7.1" + + private val migrationItems = arrayOf( + // Apollo API / Runtime + RemoveMethodImport("$apollo2.coroutines.CoroutinesExtensionsKt", "toFlow"), + UpdateClassName("$apollo2.api.Response", "$apollo3.api.ApolloResponse"), + UpdateClassName("$apollo2.ApolloQueryCall", "$apollo3.ApolloCall"), + UpdateClassName("$apollo2.ApolloMutationCall", "$apollo3.ApolloCall"), + UpdateClassName("$apollo2.ApolloSubscriptionCall", "$apollo3.ApolloCall"), + UpdateMethodName("$apollo2.ApolloClient", "mutate", "mutation"), + UpdateMethodName("$apollo2.ApolloClient", "subscribe", "subscription"), + UpdateMethodName("$apollo2.ApolloClient", "builder", "Builder"), + UpdateMethodName("$apollo2.coroutines.CoroutinesExtensionsKt", "await", "execute"), + RemoveMethodImport("$apollo2.coroutines.CoroutinesExtensionsKt", "await"), + UpdateMethodName("$apollo2.ApolloQueryCall", "watcher", "watch", importToAdd = "$apollo3.cache.normalized.watch"), + RemoveMethodCall("$apollo2.coroutines.CoroutinesExtensionsKt", "toFlow", extensionTargetClassName = "$apollo2.ApolloQueryWatcher"), + UpdateClassName("$apollo2.api.Input", "$apollo3.api.Optional"), + UpdateMethodName("$apollo2.api.Input.Companion", "fromNullable", "Present"), + UpdateMethodName("$apollo2.api.Input.Companion", "optional", "presentIfNotNull"), + UpdateOkHttpExecutionContext, + UpdateInputAbsent, + RemoveMethodCall("$apollo2.api.OperationName", "name"), + UpdateClassName("$apollo2.api.FileUpload", "$apollo3.api.Upload"), + + // Http cache + UpdateMethodName( + "$apollo2.ApolloQueryCall.Builder", + "httpCachePolicy", + "httpFetchPolicy", + importToAdd = "$apollo3.cache.http.httpFetchPolicy" + ), + UpdateClassName("$apollo2.api.cache.http.HttpCachePolicy", "$apollo3.cache.http.HttpFetchPolicy"), + UpdateFieldName("$apollo2.api.cache.http.HttpCachePolicy", "CACHE_ONLY", "CacheOnly"), + UpdateFieldName("$apollo2.api.cache.http.HttpCachePolicy", "NETWORK_ONLY", "NetworkOnly"), + UpdateFieldName("$apollo2.api.cache.http.HttpCachePolicy", "CACHE_FIRST", "CacheFirst"), + UpdateFieldName("$apollo2.api.cache.http.HttpCachePolicy", "NETWORK_FIRST", "NetworkFirst"), + UpdateHttpCache, + UpdateMethodName("$apollo2.ApolloClient", "clearHttpCache", "httpCache.clearAll", importToAdd = "$apollo3.cache.http.httpCache"), + + // Normalized cache + UpdateMethodName( + "$apollo2.ApolloQueryCall.Builder", + "responseFetcher", + "fetchPolicy", + importToAdd = "$apollo3.cache.normalized.fetchPolicy" + ), + UpdateClassName("$apollo2.fetcher.ApolloResponseFetchers", "$apollo3.cache.normalized.FetchPolicy"), + UpdateFieldName("$apollo2.fetcher.ApolloResponseFetchers", "CACHE_ONLY", "CacheOnly"), + UpdateFieldName("$apollo2.fetcher.ApolloResponseFetchers", "NETWORK_ONLY", "NetworkOnly"), + UpdateFieldName("$apollo2.fetcher.ApolloResponseFetchers", "CACHE_FIRST", "CacheFirst"), + UpdateFieldName("$apollo2.fetcher.ApolloResponseFetchers", "NETWORK_FIRST", "NetworkFirst"), + UpdateMethodName( + "$apollo2.cache.normalized.ApolloStore", + "read", + "readOperation", + importToAdd = "$apollo3.cache.normalized.apolloStore" + ), + UpdateMethodName( + "$apollo2.cache.normalized.ApolloStore", + "writeAndPublish", + "writeOperation", + importToAdd = "$apollo3.cache.normalized.apolloStore" + ), + RemoveMethodCall("$apollo2.cache.normalized.ApolloStoreOperation", "execute"), + UpdateLruNormalizedCacheFactory, + UpdateSqlNormalizedCacheFactory, + UpdateMethodName( + "$apollo2.ApolloClient", + "clearNormalizedCache", + "apolloStore.clearAll", + importToAdd = "$apollo3.cache.normalized.apolloStore" + ), + + RemoveMethodCall("$apollo2.ApolloQueryCall", "toBuilder"), + RemoveMethodCall("$apollo2.ApolloQueryCall.Builder", "build"), + + UpdatePackageName(apollo2, apollo3), + + // Gradle + UpdateGradlePluginInBuildKts(apollo2, apollo3, apollo3LatestVersion), + CommentDependenciesInToml("apollo-coroutines-support", "apollo-android-support"), + UpdateGradleDependenciesInToml(apollo2, apollo3, apollo3LatestVersion), + UpdateGradleDependenciesBuildKts(apollo2, apollo3), + RemoveDependenciesInBuildKts("$apollo2:apollo-coroutines-support", "$apollo2:apollo-android-support"), + AddUseVersion2Compat, + UpdateGraphqlSourceDirectorySet, + + // Custom scalars + UpdateCustomTypeMappingInBuildKts, + UpdateAddCustomTypeAdapter, + + // Enums + UpdateEnumValueUpperCase, + + // Upload + UpdateFileUpload, + + // Idling resource + UpdateClassName("$apollo2.test.espresso.ApolloIdlingResource", "$apollo3.android.ApolloIdlingResource"), + UpdateIdlingResource, + ) + + private fun getRefactoringName() = ApolloBundle.message("ApolloV2ToV3MigrationProcessor.title") + } + + private val migrationManager = PsiMigrationManager.getInstance(myProject) + private var migration: PsiMigration? = null + private val searchScope = GlobalSearchScope.projectScope(project) + + override fun getCommandName() = getRefactoringName() + + private val usageViewDescriptor = object : UsageViewDescriptorAdapter() { + override fun getElements(): Array = PsiElement.EMPTY_ARRAY + + override fun getProcessedElementsHeader() = ApolloBundle.message("ApolloV2ToV3MigrationProcessor.codeReferences") + } + + override fun createUsageViewDescriptor(usages: Array) = usageViewDescriptor + + private fun startMigration(): PsiMigration { + return migrationManager.startMigration() + } + + private fun finishMigration() { + migrationManager?.currentMigration?.finish() + } + + override fun doRun() { + logd() + migration = startMigration() + // This will create classes / packages that we're finding references to in case they don't exist. + // It must be done in doRun() as this is called from the EDT whereas findUsages() is not. + for (migrationItem in migrationItems) { + migrationItem.prepare(myProject, migration!!) + } + super.doRun() + } + + override fun findUsages(): Array { + logd() + try { + val usageInfos = migrationItems + .flatMap { migrationItem -> + migrationItem.findUsages(myProject, migration!!, searchScope) + .filterNot { usageInfo -> + // Filter out all generated code usages. We don't want generated code to come up in findUsages. + // TODO: how to mark Apollo generated code as generated per this method? + usageInfo.virtualFile?.let { + GeneratedSourcesFilter.isGeneratedSourceByAnyFilter(it, myProject) + } == true + } + } + .toMutableList() + // If an element must be deleted, make sure we keep the UsageInfo and remove any other pointing to the same element. + val iterator = usageInfos.listIterator() + while (iterator.hasNext()) { + val usageInfo = iterator.next() + if (usageInfo.migrationItem !is DeletesElements) { + if (usageInfos.any { it !== usageInfo && it.migrationItem is DeletesElements && it.smartPointer == usageInfo.smartPointer }) { + iterator.remove() + } + } + } + return usageInfos.toTypedArray() + } finally { + ApplicationManager.getApplication().invokeLater({ WriteAction.run(::finishMigration) }, myProject.disposed) + } + } + + override fun preprocessUsages(refUsages: Ref>): Boolean { + logd() + if (refUsages.get().isEmpty()) { + Messages.showInfoMessage( + myProject, + ApolloBundle.message("ApolloV2ToV3MigrationProcessor.noUsage"), + getRefactoringName() + ) + return false + } + // Set to true to see the "preview usages" UI prior to refactoring. + // isPreviewUsages = true + return true + } + + override fun performRefactoring(usages: Array) { + logd() + finishMigration() + migration = startMigration() + val action = LocalHistory.getInstance().startAction(commandName) + try { + for (usage in usages) { + val migrationItem = (usage as MigrationItemUsageInfo).migrationItem + try { + if (!usage.isValid) continue + maybeAddImports(usage, migrationItem) + migrationItem.performRefactoring(myProject, migration!!, usage) + } catch (t: Throwable) { + logw(t, "Error while performing refactoring for $migrationItem") + } + } + postRefactoring() + } finally { + action.finish() + finishMigration() + } + } + + private fun maybeAddImports( + usage: MigrationItemUsageInfo, + migrationItem: MigrationItem, + ) { + val importsToAdd = migrationItem.importsToAdd() + if (importsToAdd.isNotEmpty()) { + val psiFactory = KtPsiFactory(myProject) + usage.element.containingKtFileImportList()?.let { importList -> + importsToAdd.forEach { importToAdd -> + if (importList.imports.none { it.importPath?.pathStr == importToAdd }) { + importList.add(psiFactory.createImportDirective(ImportPath.fromString(importToAdd))) + } + } + } + } + } + + private fun postRefactoring() { + logd() + PsiDocumentManager.getInstance(myProject).performLaterWhenAllCommitted { + // Not sure if this is actually useful but IJ's editor sometimes has a hard time after the files have been touched + PsiManager.getInstance(myProject).apply { + dropResolveCaches() + dropPsiCaches() + } + DaemonCodeAnalyzer.getInstance(myProject).restart() + + // Sync gradle + if (!isUnitTestMode()) { + ExternalSystemUtil.refreshProject( + myProject, + GradleConstants.SYSTEM_ID, + myProject.basePath!!, + false, + ProgressExecutionMode.IN_BACKGROUND_ASYNC + ) + } + } + } +} + diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/AddUseVersion2Compat.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/AddUseVersion2Compat.kt new file mode 100644 index 00000000000..2cdff8cf968 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/AddUseVersion2Compat.kt @@ -0,0 +1,50 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.util.addSiblingAfter +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid + +object AddUseVersion2Compat : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val buildGradleKtsFiles: Array = FilenameIndex.getFilesByName(project, "build.gradle.kts", searchScope) + val usages = mutableListOf() + for (file in buildGradleKtsFiles) { + if (file !is KtFile) continue + file.accept(object : KtTreeVisitorVoid() { + override fun visitCallExpression(expression: KtCallExpression) { + super.visitCallExpression(expression) + if (expression.calleeExpression?.text != "apollo") return + val firstChild = (expression.lambdaArguments.firstOrNull()?.getLambdaExpression())?.functionLiteral?.lBrace + firstChild?.let { usages.add(it.toMigrationItemUsageInfo()) } + } + }) + } + return usages + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val psiFactory = KtPsiFactory(project) + val indent = (usage.element.nextSibling as? PsiWhiteSpace)?.text?.drop(1)?.length ?: 4 + val block = psiFactory.createBlockCodeFragment( + """ + | + |// TODO: This shortcut jumpstarts the migration from v2 to v3, but it is recommended to use settings idiomatic to v3 instead. + |// See https://www.apollographql.com/docs/kotlin/migration/3.0/ + |useVersion2Compat() + | + |""" + .trimMargin() + .prependIndent(" ".repeat(indent)), + usage.element.parent + ) + usage.element.addSiblingAfter(block) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/CommentDependenciesInToml.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/CommentDependenciesInToml.kt new file mode 100644 index 00000000000..c8b60672907 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/CommentDependenciesInToml.kt @@ -0,0 +1,55 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.util.unquoted +import com.intellij.codeInspection.SuppressionUtil.createComment +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.toml.lang.psi.TomlFile +import org.toml.lang.psi.TomlLiteral +import org.toml.lang.psi.TomlPsiFactory +import org.toml.lang.psi.TomlRecursiveVisitor +import org.toml.lang.psi.TomlTable +import org.toml.lang.psi.ext.TomlLiteralKind +import org.toml.lang.psi.ext.kind + +class CommentDependenciesInToml( + private vararg val artifactId: String, +) : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val libsVersionTomlFiles: Array = FilenameIndex.getFilesByName(project, "libs.versions.toml", searchScope) + val usages = mutableListOf() + for (file in libsVersionTomlFiles) { + if (file !is TomlFile) continue + file.accept(object : TomlRecursiveVisitor() { + override fun visitLiteral(element: TomlLiteral) { + super.visitLiteral(element) + if (element.kind is TomlLiteralKind.String) { + val dependencyText = element.text.unquoted() + if (artifactId.any { dependencyText.contains(it) }) { + var elementToReplace: PsiElement = element + while (elementToReplace.parent != null && elementToReplace.parent !is TomlTable) { + elementToReplace = elementToReplace.parent + } + usages.add(elementToReplace.toMigrationItemUsageInfo()) + } + } + } + }) + } + return usages + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + element.parent.addBefore( + createComment(project, " TODO: remove this declaration and its uses in your gradle files", element.language), + element + ) + element.parent.addBefore(TomlPsiFactory(project).createWhitespace("\n"), element) + element.replace(createComment(project, " " + element.text, element.language)) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/DeletesElements.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/DeletesElements.kt new file mode 100644 index 00000000000..35f4ee3f725 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/DeletesElements.kt @@ -0,0 +1,6 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +/** + * Marker interface for [MigrationItem]s that delete elements. Useful to sort them so they are executed at the end. + */ +interface DeletesElements diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItem.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItem.kt new file mode 100644 index 00000000000..714313c9ea7 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItem.kt @@ -0,0 +1,12 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope + +abstract class MigrationItem { + open fun prepare(project: Project, migration: PsiMigration) {} + abstract fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List + abstract fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) + open fun importsToAdd(): Set = emptySet() +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItemUsageInfo.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItemUsageInfo.kt new file mode 100644 index 00000000000..af4da018784 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/MigrationItemUsageInfo.kt @@ -0,0 +1,62 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.usageView.UsageInfo + +open class MigrationItemUsageInfo : UsageInfo { + val migrationItem: MigrationItem + private val attachedData: Any? + + constructor(migrationItem: MigrationItem, reference: PsiReference, attachedData: Any? = null) : super(reference) { + this.migrationItem = migrationItem + this.attachedData = attachedData + } + + constructor(migrationItem: MigrationItem, element: PsiElement, attachedData: Any? = null) : super(element) { + this.migrationItem = migrationItem + this.attachedData = attachedData + } + + constructor(migrationItem: MigrationItem, source: UsageInfo, attachedData: Any? = null) : super( + source.element!!, + source.rangeInElement!!.startOffset, + source.rangeInElement!!.endOffset + ) { + this.migrationItem = migrationItem + this.attachedData = attachedData + } + + fun attachedData(): T { + @Suppress("UNCHECKED_CAST") + return attachedData as T + } + + override fun getElement(): PsiElement { + return super.getElement()!! + } + + override fun isValid(): Boolean { + return super.getElement() != null && super.isValid() + } +} + +context(MigrationItem) +fun UsageInfo.toMigrationItemUsageInfo() = MigrationItemUsageInfo(migrationItem = this@MigrationItem, source = this) + +context(MigrationItem) +fun Array.toMigrationItemUsageInfo(): List { + return map { it.toMigrationItemUsageInfo() } +} + +context(MigrationItem) +fun PsiReference.toMigrationItemUsageInfo() = MigrationItemUsageInfo(migrationItem = this@MigrationItem, reference = this) + +context(MigrationItem) +fun Collection.toMigrationItemUsageInfo(): List { + return map { it.toMigrationItemUsageInfo() } +} + +context(MigrationItem) +fun PsiElement.toMigrationItemUsageInfo(attachedData: Any? = null) = + MigrationItemUsageInfo(migrationItem = this@MigrationItem, element = this, attachedData = attachedData) diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveDependenciesInBuildKts.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveDependenciesInBuildKts.kt new file mode 100644 index 00000000000..9febeb211a7 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveDependenciesInBuildKts.kt @@ -0,0 +1,39 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid + +class RemoveDependenciesInBuildKts( + private vararg val groupAndArtifact: String, +) : MigrationItem(), DeletesElements { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val buildGradleKtsFiles: Array = FilenameIndex.getFilesByName(project, "build.gradle.kts", searchScope) + val usages = mutableListOf() + for (file in buildGradleKtsFiles) { + if (file !is KtFile) continue + file.accept(object : KtTreeVisitorVoid() { + override fun visitLiteralStringTemplateEntry(entry: KtLiteralStringTemplateEntry) { + super.visitLiteralStringTemplateEntry(entry) + if (groupAndArtifact.any { entry.text.contains(it) }) { + val callExpression = entry.parent.parent.parent.parent as? KtCallExpression + if (callExpression?.calleeExpression?.text in listOf("implementation", "api", "testImplementation", "testApi")) { + usages.add(callExpression!!.toMigrationItemUsageInfo()) + } + } + } + }) + } + return usages + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + usage.element.delete() + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodCall.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodCall.kt new file mode 100644 index 00000000000..866a1e122ae --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodCall.kt @@ -0,0 +1,50 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findMethodReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtImportDirective + +open class RemoveMethodCall( + private val containingDeclarationName: String, + private val methodName: String, + private val extensionTargetClassName: String? = null, + private val removeImportsOnly: Boolean = false, +) : MigrationItem(), DeletesElements { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findMethodReferences( + project = project, + className = containingDeclarationName, + methodName = methodName, + extensionTargetClassName = extensionTargetClassName, + ) + .filter { + if (removeImportsOnly) { + it.element.parentOfType() != null + } else { + true + } + } + .toMigrationItemUsageInfo() + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + val importDirective = element.parentOfType() + if (importDirective != null) { + // Reference is an import + importDirective.delete() + } else { + if (removeImportsOnly) return + val dotQualifiedExpression = element.parentOfType() + if (dotQualifiedExpression != null) { + // Reference is a method call + // . -> + dotQualifiedExpression.replace(dotQualifiedExpression.receiverExpression) + } + } + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodImport.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodImport.kt new file mode 100644 index 00000000000..5ca04010efc --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/RemoveMethodImport.kt @@ -0,0 +1,10 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +class RemoveMethodImport( + containingDeclarationName: String, + methodName: String, +) : RemoveMethodCall( + containingDeclarationName, + methodName, + removeImportsOnly = true, +) diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateAddCustomTypeAdapter.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateAddCustomTypeAdapter.kt new file mode 100644 index 00000000000..a4f9f55e724 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateAddCustomTypeAdapter.kt @@ -0,0 +1,50 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findMethodReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.nj2k.postProcessing.resolve +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtStringTemplateExpression + +object UpdateAddCustomTypeAdapter : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findMethodReferences( + project = project, + className = "com.apollographql.apollo.ApolloClient.Builder", + methodName = "addCustomTypeAdapter" + ) + .mapNotNull { + val callExpression = it.element.parent as? KtCallExpression ?: return@mapNotNull null + // 1st argument is (generally) a reference to the generated custom type, e.g. `CustomType.DATETIME` + val firstArgument = callExpression.valueArguments.firstOrNull() ?: return@mapNotNull null + val argumentDotQualifiedExpression = firstArgument.getArgumentExpression() as? KtDotQualifiedExpression ?: return@mapNotNull null + // e.g. `DATETIME` + val typeReference = argumentDotQualifiedExpression.selectorExpression as? KtNameReferenceExpression ?: return@mapNotNull null + val enumEntry = typeReference.resolve() as? KtEnumEntry ?: return@mapNotNull null + val classBody = enumEntry.body ?: return@mapNotNull null + // 1st function looks like `override fun typeName(): String = "DateTime"` + val typeNameFunction = classBody.functions.firstOrNull() ?: return@mapNotNull null + // `"DateTime"` + val bodyExpression = typeNameFunction.bodyExpression as? KtStringTemplateExpression ?: return@mapNotNull null + val typeName = bodyExpression.entries.firstOrNull()?.text ?: return@mapNotNull null + MigrationItemUsageInfo(this, firstArgument, attachedData = typeName) + } + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val psiFactory = KtPsiFactory(project) + val callExpression = usage.element.parentOfType() ?: return + callExpression.prevSibling.parent.addBefore( + psiFactory.createComment("// TODO: Use addCustomScalarAdapter instead. See https://www.apollographql.com/docs/kotlin/migration/3.0/#custom-scalar-adapters"), + callExpression.prevSibling + ) + usage.element.replace(psiFactory.createExpression("${usage.attachedData()}.type")) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateClassName.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateClassName.kt new file mode 100644 index 00000000000..1027e79ce96 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateClassName.kt @@ -0,0 +1,53 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findClassReferences +import com.apollographql.ijplugin.refactoring.findOrCreateClass +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.ImportPath + +class UpdateClassName( + private val oldName: String, + private val newName: String, +) : MigrationItem() { + override fun prepare(project: Project, migration: PsiMigration) { + findOrCreateClass(project, migration, newName) + } + + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findClassReferences(project, oldName) + .toMigrationItemUsageInfo() + .map { + val element = it.element + val importDirective = element.parentOfType() + if (importDirective != null) { + // Reference is an import + ReplaceImportUsageInfo(this@UpdateClassName, importDirective) + } else { + it + } + } + } + + private class ReplaceImportUsageInfo(migrationItem: MigrationItem, element: KtImportDirective) : + MigrationItemUsageInfo(migrationItem, element) + + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + val psiFactory = KtPsiFactory(project) + when (usage) { + is ReplaceImportUsageInfo -> { + element.replace(psiFactory.createImportDirective(ImportPath.fromString(newName))) + } + + else -> { + element.replace(psiFactory.createExpression(newName.split('.').last())) + } + } + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateCustomTypeMappingInBuildKts.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateCustomTypeMappingInBuildKts.kt new file mode 100644 index 00000000000..bf5766186b7 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateCustomTypeMappingInBuildKts.kt @@ -0,0 +1,82 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.util.addSiblingAfter +import com.apollographql.ijplugin.util.unquoted +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType + +object UpdateCustomTypeMappingInBuildKts : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val buildGradleKtsFiles: Array = FilenameIndex.getFilesByName(project, "build.gradle.kts", searchScope) + val usages = mutableListOf() + for (file in buildGradleKtsFiles) { + if (file !is KtFile) continue + file.accept(object : KtTreeVisitorVoid() { + override fun visitReferenceExpression(expression: KtReferenceExpression) { + super.visitReferenceExpression(expression) + if (expression.text == "customTypeMapping") { + // `customTypeMapping.set(mapOf(...))` + val qualifiedExpression = expression.parent as? KtQualifiedExpression ?: return + // `set(mapOf(...))` + val setCallExpression = qualifiedExpression.selectorExpression + ?.findDescendantOfType { it.calleeExpression?.text == "set" } ?: return + // `mapOf(...)` + val mapOfCallExpression = setCallExpression.valueArguments.firstOrNull() + ?.findDescendantOfType { it.calleeExpression?.text == "mapOf" } ?: return + + @Suppress("UNCHECKED_CAST") + val map: Map = mapOfCallExpression.valueArguments.associate { argument -> + val binaryExpression = argument.getArgumentExpression() as? KtBinaryExpression ?: return@associate null to null + val key = binaryExpression.left?.text?.unquoted() + val value = binaryExpression.right?.text?.unquoted() + key to value + } + .filterNot { it.key == null || it.value == null } as Map + usages.add(MigrationItemUsageInfo(this@UpdateCustomTypeMappingInBuildKts, qualifiedExpression, map)) + } + } + }) + } + return usages + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + val map: Map = usage.attachedData() + val psiFactory = KtPsiFactory(project) + map.entries.toList().reversed().forEach { (scalar, kotlinType) -> + val text = when (kotlinType) { + "kotlin.String" -> "mapScalarToKotlinString(\"$scalar\")" + "kotlin.Int" -> "mapScalarToKotlinInt(\"$scalar\")" + "kotlin.Double" -> "mapScalarToKotlinDouble(\"$scalar\")" + "kotlin.Float" -> "mapScalarToKotlinFloat(\"$scalar\")" + "kotlin.Long" -> "mapScalarToKotlinLong(\"$scalar\")" + "kotlin.Boolean" -> "mapScalarToKotlinBoolean(\"$scalar\")" + "kotlin.Any" -> "mapScalarToKotlinAny(\"$scalar\")" + "java.lang.String" -> "mapScalarToJavaString(\"$scalar\")" + "java.lang.Integer" -> "mapScalarToJavaInteger(\"$scalar\")" + "java.lang.Double" -> "mapScalarToJavaDouble(\"$scalar\")" + "java.lang.Float" -> "mapScalarToJavaFloat(\"$scalar\")" + "java.lang.Long" -> "mapScalarToJavaLong(\"$scalar\")" + "java.lang.Boolean" -> "mapScalarToJavaBoolean(\"$scalar\")" + "java.lang.Object" -> "mapScalarToJavaObject(\"$scalar\")" + "com.apollographql.apollo.api.FileUpload" -> "mapScalarToUpload(\"$scalar\")" + else -> "mapScalar(\"$scalar\", \"$kotlinType\")" + } + element.addSiblingAfter(psiFactory.createExpression(text)) + element.addSiblingAfter(psiFactory.createNewLine()) + } + element.delete() + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateEnumValueUpperCase.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateEnumValueUpperCase.kt new file mode 100644 index 00000000000..b00ed77ce17 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateEnumValueUpperCase.kt @@ -0,0 +1,68 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findClassReferences +import com.apollographql.ijplugin.refactoring.findFieldReferences +import com.apollographql.ijplugin.refactoring.findInheritorsOfClass +import com.apollographql.ijplugin.util.unquoted +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.asJava.classes.KtLightClassBase +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry + +object UpdateEnumValueUpperCase : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val usageInfo = mutableListOf() + val enumValueInheritors = findInheritorsOfClass(project, "com.apollographql.apollo.api.EnumValue").filterIsInstance() + for (enumValueInheritor in enumValueInheritors) { + val kotlinOrigin = enumValueInheritor.kotlinOrigin + when { + kotlinOrigin is KtObjectDeclaration -> { + // Sealed subclass + val superTypeCallEntry = kotlinOrigin.superTypeListEntries.firstOrNull() as? KtSuperTypeCallEntry ?: continue + val sealedClassOldName = kotlinOrigin.name!! + val sealedClassNewName = superTypeCallEntry.valueArguments.firstOrNull()?.getArgumentExpression()?.text?.unquoted() ?: continue + if (sealedClassNewName == sealedClassOldName) { + // Enum is upper case in the schema: no need to update + continue + } + val references = findClassReferences(project, kotlinOrigin.fqName.toString()) + // Exclude references in generated code + .filterNot { it.element.parentOfType()?.name == "safeValueOf" } + for (reference in references) { + usageInfo.add(MigrationItemUsageInfo(this@UpdateEnumValueUpperCase, reference, sealedClassNewName)) + } + } + + kotlinOrigin is KtClass && kotlinOrigin.isEnum() -> { + // Enum: look for references to enum values + for (enumEntry in kotlinOrigin.body?.enumEntries ?: emptyList()) { + val superTypeCallEntry = enumEntry.initializerList?.initializers?.firstOrNull() as? KtSuperTypeCallEntry ?: continue + val enumOldName = enumEntry.name!! + val enumNewName = superTypeCallEntry.valueArguments.firstOrNull()?.getArgumentExpression()?.text?.unquoted() ?: continue + if (enumNewName == enumOldName) { + // Enum is upper case in the schema: no need to update + continue + } + val references = findFieldReferences(project, enumValueInheritor.qualifiedName!!, enumOldName) + for (reference in references) { + usageInfo.add(MigrationItemUsageInfo(this@UpdateEnumValueUpperCase, reference, enumNewName)) + } + } + + } + } + } + + return usageInfo + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + usage.element.replace(KtPsiFactory(project).createExpression(usage.attachedData())) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFieldName.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFieldName.kt new file mode 100644 index 00000000000..adad9442d42 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFieldName.kt @@ -0,0 +1,22 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findFieldReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtPsiFactory + +class UpdateFieldName( + private val className: String, + private val oldFieldName: String, + private val newFieldName: String, +) : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findFieldReferences(project = project, className = className, fieldName = oldFieldName).toMigrationItemUsageInfo() + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val newFieldReference = KtPsiFactory(project).createExpression(newFieldName) + usage.element.replace(newFieldReference) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFileUpload.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFileUpload.kt new file mode 100644 index 00000000000..2d78ba8fb53 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateFileUpload.kt @@ -0,0 +1,34 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findClassReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.createExpressionByPattern + +object UpdateFileUpload : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findClassReferences(project, "com.apollographql.apollo.api.FileUpload") + .mapNotNull { + (it.element.parent as? KtCallExpression)?.toMigrationItemUsageInfo() + } + + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element as KtCallExpression + val psiFactory = KtPsiFactory(project) + val newElement = + psiFactory.createExpressionByPattern("File($0).toUpload($1)", element.valueArguments[1].text, element.valueArguments[0].text) + element.replace(newElement) + } + + override fun importsToAdd(): Set { + return setOf( + "java.io.File", + "com.apollographql.apollo3.api.toUpload", + ) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesBuildKts.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesBuildKts.kt new file mode 100644 index 00000000000..9dde65153a3 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesBuildKts.kt @@ -0,0 +1,50 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.util.unquoted +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.KtValueArgument + +class UpdateGradleDependenciesBuildKts( + private val oldGroupId: String, + private val newGroupId: String, +) : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val buildGradleKtsFiles: Array = FilenameIndex.getFilesByName(project, "build.gradle.kts", searchScope) + val usages = mutableListOf() + for (file in buildGradleKtsFiles) { + if (file !is KtFile) continue + file.accept(object : KtTreeVisitorVoid() { + override fun visitLiteralStringTemplateEntry(entry: KtLiteralStringTemplateEntry) { + super.visitLiteralStringTemplateEntry(entry) + if (entry.text.startsWith(oldGroupId)) { + val callExpression = entry.parent.parent.parent.parent as? KtCallExpression + if (callExpression?.calleeExpression?.text in listOf("implementation", "api", "testImplementation", "testApi")) { + usages.add(callExpression!!.toMigrationItemUsageInfo()) + } + } + } + }) + } + return usages + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val argument = (usage.element as KtCallExpression).valueArguments.first() + val entries = ((argument as KtValueArgument).getArgumentExpression() as? KtStringTemplateExpression)?.entries ?: return + val firstEntry = entries.firstOrNull() ?: return + val artifactId = firstEntry.text.unquoted().split(":")[1] + firstEntry.replace(KtPsiFactory(project).createLiteralStringTemplateEntry("$newGroupId:$artifactId")) + // Remove other entries (with recent versions of the Apollo Gradle plugin, there is no need to specify the version) + entries.drop(1).forEach { it.delete() } + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesInToml.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesInToml.kt new file mode 100644 index 00000000000..6134c4abd77 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradleDependenciesInToml.kt @@ -0,0 +1,105 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.util.quoted +import com.apollographql.ijplugin.util.unquoted +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.toml.lang.psi.TomlFile +import org.toml.lang.psi.TomlInlineTable +import org.toml.lang.psi.TomlLiteral +import org.toml.lang.psi.TomlPsiFactory +import org.toml.lang.psi.TomlRecursiveVisitor +import org.toml.lang.psi.TomlTable +import org.toml.lang.psi.ext.TomlLiteralKind +import org.toml.lang.psi.ext.kind + +class UpdateGradleDependenciesInToml( + private val oldGroupId: String, + private val newGroupId: String, + private val newVersion: String, +) : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val libsVersionTomlFiles: Array = FilenameIndex.getFilesByName(project, "libs.versions.toml", searchScope) + val usages = mutableListOf() + for (file in libsVersionTomlFiles) { + if (file !is TomlFile) continue + file.accept(object : TomlRecursiveVisitor() { + override fun visitLiteral(element: TomlLiteral) { + super.visitLiteral(element) + if (element.kind is TomlLiteralKind.String) { + val dependencyText = element.text.unquoted() + if (dependencyText == oldGroupId || dependencyText.startsWith("$oldGroupId:")) { + usages.add(MigrationItemUsageInfo(this@UpdateGradleDependenciesInToml, element.firstChild, Kind.SHORT_MODULE_OR_GROUP)) + // Find the associated version + val versionEntry = (element.parent.parent as? TomlInlineTable)?.entries + ?.first { it.key.text == "version" || it.key.text == "version.ref" } + if (versionEntry != null) { + if (versionEntry.key.text == "version") { + versionEntry.value?.let { + usages.add( + MigrationItemUsageInfo( + this@UpdateGradleDependenciesInToml, + it.firstChild, + Kind.VERSION + ) + ) + } + } else { + // Resolve the reference + val versionsTable = element.containingFile.children.filterIsInstance() + .firstOrNull { it.header.key?.text == "versions" } + val versionRefKey = versionEntry.value?.text?.unquoted() + val refTarget = versionsTable?.entries?.firstOrNull { it.key.text == versionRefKey } + refTarget?.value?.let { + usages.add( + MigrationItemUsageInfo( + this@UpdateGradleDependenciesInToml, + it.firstChild, + Kind.VERSION + ) + ) + } + } + } + } + } + } + }) + } + return usages + } + + private enum class Kind { + SHORT_MODULE_OR_GROUP, VERSION + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + when (usage.attachedData()) { + Kind.SHORT_MODULE_OR_GROUP -> { + val notation = element.text.unquoted().split(":") + val newNotation = when (notation.size) { + 1 -> newGroupId + 2 -> { + val part = notation[1] + if (part.matches(Regex("\\d+.*"))) { + // Part is a version number: plugin short notation case, e.g. "com.apollographql.apollo:2.5.14" + "$newGroupId:$newVersion" + } else { + // Part is an artifact name: library case, e.g. "com.apollographql.apollo:apollo-runtime" + "$newGroupId:$part" + } + } + + else -> "$newGroupId:${notation[1]}:$newVersion" + } + element.replace(TomlPsiFactory(project).createLiteral(newNotation.quoted())) + } + + Kind.VERSION -> element.replace(TomlPsiFactory(project).createLiteral(newVersion.quoted())) + } + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradlePluginInBuildKts.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradlePluginInBuildKts.kt new file mode 100644 index 00000000000..290e93af247 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGradlePluginInBuildKts.kt @@ -0,0 +1,97 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.util.quoted +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid + +class UpdateGradlePluginInBuildKts( + private val oldPluginId: String, + private val newPluginId: String, + private val newPluginVersion: String, +) : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val buildGradleKtsFiles: Array = FilenameIndex.getFilesByName(project, "build.gradle.kts", searchScope) + val usages = mutableListOf() + for (file in buildGradleKtsFiles) { + if (file !is KtFile) continue + file.accept(object : KtTreeVisitorVoid() { + override fun visitCallExpression(expression: KtCallExpression) { + super.visitCallExpression(expression) + if ((expression.calleeExpression as? KtNameReferenceExpression)?.getReferencedName() == "id") { + // id("xxx") + val dependencyText = + (expression.valueArguments.firstOrNull()?.getArgumentExpression() as? KtStringTemplateExpression)?.entries?.first()?.text + if (dependencyText == oldPluginId) { + val parent = expression.parent + if (parent is KtBinaryExpression || parent is KtDotQualifiedExpression) { + // id("xxx") version yyy / id("xxx").version(yyy) + usages.add(parent.toMigrationItemUsageInfo()) + } else { + usages.add(expression.toMigrationItemUsageInfo()) + } + } + } + } + }) + } + return usages + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + when (val element = usage.element) { + is KtBinaryExpression -> { + // id("xxx") version yyy + // If yyy is a simple String (a hardcoded version), replace it with the new version, otherwise leave it alone but add a comment + val versionStringTemplateEntries = (element.right as? KtStringTemplateExpression)?.entries + val versionIsHardcoded = versionStringTemplateEntries?.size == 1 && versionStringTemplateEntries[0] is KtLiteralStringTemplateEntry + if (versionIsHardcoded) { + element.replace(KtPsiFactory(project).createExpression("""id("$newPluginId") version "$newPluginVersion"""")) + } else { + // Replace id + (element.left as? KtCallExpression)?.valueArguments?.firstOrNull()?.replace( + KtPsiFactory(project).createExpression(newPluginId.quoted()) + ) + // Add comment + val comment = KtPsiFactory(project).createComment("// TODO: Update version to $newPluginVersion") + element.parent.addBefore(comment, element) + } + } + + is KtDotQualifiedExpression -> { + // id("xxx").version(yyy) + // If yyy is a simple String (a hardcoded version), replace it with the new version, otherwise leave it alone but add a comment + val versionCallExpression = element.selectorExpression as? KtCallExpression + val versionStringTemplateEntries = (versionCallExpression?.valueArgumentList?.arguments?.firstOrNull() + ?.children?.firstOrNull() as? KtStringTemplateExpression)?.entries + val versionIsHardcoded = versionStringTemplateEntries?.size == 1 && versionStringTemplateEntries[0] is KtLiteralStringTemplateEntry + if (versionIsHardcoded) { + element.replace(KtPsiFactory(project).createExpression("""id("$newPluginId").version("$newPluginVersion")""")) + } else { + // Replace id + (element.receiverExpression as? KtCallExpression)?.valueArguments?.firstOrNull() + ?.replace(KtPsiFactory(project).createExpression(newPluginId.quoted())) + // Add comment + val comment = KtPsiFactory(project).createComment("// TODO: Update version to $newPluginVersion") + element.parent.addBefore(comment, element) + } + } + + else -> { + // id("xxx") + element.replace(KtPsiFactory(project).createExpression("""id("$newPluginId")""")) + } + } + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGraphqlSourceDirectorySet.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGraphqlSourceDirectorySet.kt new file mode 100644 index 00000000000..c66fd77a3f6 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateGraphqlSourceDirectorySet.kt @@ -0,0 +1,57 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.createExpressionByPattern + +object UpdateGraphqlSourceDirectorySet : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + val buildGradleKtsFiles: Array = FilenameIndex.getFilesByName(project, "build.gradle.kts", searchScope) + val usages = mutableListOf() + for (file in buildGradleKtsFiles) { + if (file !is KtFile) continue + file.accept(object : KtTreeVisitorVoid() { + override fun visitReferenceExpression(expression: KtReferenceExpression) { + super.visitReferenceExpression(expression) + if (expression.text == "graphqlSourceDirectorySet") { + val dotQualifiedExpression = expression.parent as? KtDotQualifiedExpression ?: return + (dotQualifiedExpression.parent as? KtBinaryExpression)?.let { + usages.add(it.toMigrationItemUsageInfo()) + return + } + + val calleeText = (dotQualifiedExpression.selectorExpression as? KtCallExpression)?.calleeExpression?.text ?: return + if (calleeText in setOf("include", "exclude")) { + usages.add(dotQualifiedExpression.toMigrationItemUsageInfo(calleeText)) + } + } + } + }) + } + return usages + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val psiFactory = KtPsiFactory(project) + val element = usage.element + val newElement = if (element is KtBinaryExpression) { + psiFactory.createExpressionByPattern("srcDir($0)", element.right!!.text) + } else { + element as KtDotQualifiedExpression + val fieldName = if (usage.attachedData() == "include") "includes" else "excludes" + val argumentText = (element.selectorExpression as? KtCallExpression)?.valueArguments?.firstOrNull()?.text ?: return + psiFactory.createExpressionByPattern("$0.add($1)", fieldName, argumentText) + } + element.replace(newElement) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateHttpCache.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateHttpCache.kt new file mode 100644 index 00000000000..ae0a3fed699 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateHttpCache.kt @@ -0,0 +1,145 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findMethodReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.nj2k.postProcessing.resolve +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPsiFactory + +object UpdateHttpCache : MigrationItem(), DeletesElements { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findMethodReferences( + project = project, + className = "com.apollographql.apollo.ApolloClient.Builder", + methodName = "httpCache" + ) + .flatMap { + val element = it.element + // `httpCache(...)` + val httpCacheCallExpression = element.parent as? KtCallExpression ?: return@flatMap emptyList() + val httpCacheArgumentExpression = httpCacheCallExpression.valueArguments.firstOrNull()?.getArgumentExpression() + ?: return@flatMap emptyList() + val replaceUsageInfos = mutableSetOf() + val elementsToDelete = mutableSetOf() + when (httpCacheArgumentExpression) { + // `httpCache(ApolloHttpCache(...))` + is KtCallExpression -> { + val apolloHttpCacheArguments = extractApolloHttpCacheArguments(httpCacheArgumentExpression, elementsToDelete) + if (apolloHttpCacheArguments != null) { + replaceUsageInfos += ReplaceUsageInfo( + this@UpdateHttpCache, + element.parent, + "httpCache(${apolloHttpCacheArguments.joinToString(", ")})" + ) + } + } + + is KtNameReferenceExpression -> { + // `httpCache(xxx)` where xxx is a val defined as `val xxx = ApolloHttpCache(...)` + val referencedVal = httpCacheArgumentExpression.resolve() + if (referencedVal is KtProperty) { + val initializerExpression = referencedVal.initializer + if (initializerExpression is KtCallExpression) { + val apolloHttpCacheArguments = extractApolloHttpCacheArguments(initializerExpression, elementsToDelete) + if (apolloHttpCacheArguments != null) { + replaceUsageInfos += ReplaceUsageInfo( + this@UpdateHttpCache, + element.parent, + "httpCache(${apolloHttpCacheArguments.joinToString(", ")})" + ) + elementsToDelete += referencedVal + } + } + } + } + + else -> { + // Not supported, add a TODO comment + replaceUsageInfos += ReplaceUsageInfo( + this@UpdateHttpCache, + element.parent, + "httpCache(/* TODO: This could not be migrated automatically. Please check the migration guide at https://www.apollographql.com/docs/kotlin/migration/3.0/ */)" + ) + } + } + val deleteUsageInfos = elementsToDelete.map { elementToDelete -> + DeleteUsageInfo( + this@UpdateHttpCache, + elementToDelete + ) + } + replaceUsageInfos + deleteUsageInfos + } + } + + private fun extractApolloHttpCacheArguments(callExpression: KtCallExpression, elementsToDelete: MutableSet): List? { + // Look for `ApolloHttpCache(...)` + if (callExpression.calleeExpression?.text == "ApolloHttpCache") { + val apolloHttpCacheCtorArgument = callExpression.valueArguments.firstOrNull()?.getArgumentExpression() ?: return null + // Look for `DiskLruHttpCacheStore(...)` call + when (apolloHttpCacheCtorArgument) { + is KtCallExpression -> { + return extractDiskLruHttpCacheStoreArguments(apolloHttpCacheCtorArgument) + } + + is KtNameReferenceExpression -> { + // `ApolloHttpCache(xxx)` where xxx is a val defined as `val xxx = DiskLruHttpCacheStore(...)` + val referencedVal = apolloHttpCacheCtorArgument.resolve() + if (referencedVal is KtProperty) { + val initializerExpression = referencedVal.initializer + if (initializerExpression is KtCallExpression) { + elementsToDelete += referencedVal + return extractDiskLruHttpCacheStoreArguments(initializerExpression) + } + } + } + } + } + return null + } + + private fun extractDiskLruHttpCacheStoreArguments(initializerExpression: KtCallExpression): List? { + if (initializerExpression.calleeExpression?.text == "DiskLruHttpCacheStore") { + // There are 2 variants of `DiskLruHttpCacheStore` constructor: with 3 and 2 arguments + val diskLruHttpCacheStore = if (initializerExpression.valueArguments.size == 3) { + // Ignore the first argument (FileSystem) + initializerExpression.valueArguments.drop(1) + } else { + initializerExpression.valueArguments + } + if (diskLruHttpCacheStore.size == 2) { + val fileArgumentExpression = diskLruHttpCacheStore[0].getArgumentExpression()?.text + val maxSizeArgumentExpression = diskLruHttpCacheStore[1].getArgumentExpression()?.text + if (fileArgumentExpression != null && maxSizeArgumentExpression != null) { + return listOf(fileArgumentExpression, maxSizeArgumentExpression) + } + } + } + return null + } + + private class ReplaceUsageInfo(migrationItem: MigrationItem, element: PsiElement, val replacementExpression: String) : + MigrationItemUsageInfo(migrationItem, element) + + private class DeleteUsageInfo(migrationItem: MigrationItem, element: PsiElement) : MigrationItemUsageInfo(migrationItem, element) + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + when (usage) { + is ReplaceUsageInfo -> { + val psiFactory = KtPsiFactory(project) + val newMethodReference = psiFactory.createExpression(usage.replacementExpression) + element.replace(newMethodReference) + } + + is DeleteUsageInfo -> element.delete() + } + } + + override fun importsToAdd() = setOf("com.apollographql.apollo3.cache.http.httpCache") +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateIdlingResource.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateIdlingResource.kt new file mode 100644 index 00000000000..906500ba0e2 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateIdlingResource.kt @@ -0,0 +1,30 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findMethodReferences +import com.apollographql.ijplugin.util.addSiblingBefore +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.createExpressionByPattern + +object UpdateIdlingResource : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findMethodReferences(project, "com.apollographql.apollo.test.espresso.ApolloIdlingResource", "create") + .mapNotNull { + it.element.parentOfType()?.toMigrationItemUsageInfo() + } + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element as KtDotQualifiedExpression + val psiFactory = KtPsiFactory(project) + val argumentValue = (element.selectorExpression as? KtCallExpression)?.valueArguments?.firstOrNull()?.text ?: return + val newElement = psiFactory.createExpressionByPattern("ApolloIdlingResource($0)", argumentValue) + element.addSiblingBefore(psiFactory.createComment("// TODO: Set the ApolloIdlingResource on the ApolloClient. See https://www.apollographql.com/docs/kotlin/migration/3.0/#idlingresource")) + element.replace(newElement) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateInputAbsent.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateInputAbsent.kt new file mode 100644 index 00000000000..6766ed50747 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateInputAbsent.kt @@ -0,0 +1,20 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findMethodReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtPsiFactory + +object UpdateInputAbsent : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findMethodReferences(project = project, className = "com.apollographql.apollo.api.Input.Companion", methodName = "absent") + .map { it.element.parent.toMigrationItemUsageInfo() } + + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val newMethodReference = KtPsiFactory(project).createExpression("Absent") + usage.element.replace(newMethodReference) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateLruNormalizedCacheFactory.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateLruNormalizedCacheFactory.kt new file mode 100644 index 00000000000..1422f151141 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateLruNormalizedCacheFactory.kt @@ -0,0 +1,96 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findClassReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import com.intellij.util.castSafelyTo +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.KtNameReferenceExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType +import org.jetbrains.kotlin.resolve.ImportPath + +object UpdateLruNormalizedCacheFactory : MigrationItem() { + private const val CACHE_FACTORY_FQN = "com.apollographql.apollo.cache.normalized.lru.LruNormalizedCacheFactory" + + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findClassReferences(project, CACHE_FACTORY_FQN) + .mapNotNull { + val element = it.element + val importDirective = element.parentOfType() + if (importDirective != null) { + // Reference is an import + ReplaceImportUsageInfo(this@UpdateLruNormalizedCacheFactory, importDirective) + } else { + // Reference is a class reference + // Looking for something like: + // EvictionPolicy.builder() + // .maxSizeBytes(10 * 1024 * 1024) + // .expireAfterWrite(10, TimeUnit.MILLISECONDS) + // .build() + val callExpression = element.parent as? KtCallExpression ?: return@mapNotNull null + val argumentExpression = callExpression.valueArguments.first().getArgumentExpression() as? KtDotQualifiedExpression + ?: return@mapNotNull null + + var maxSizeBytesValue: String? = null + val maxSizeBytesCall = + argumentExpression.findDescendantOfType { it.getReferencedName() == "maxSizeBytes" } + if (maxSizeBytesCall != null) { + maxSizeBytesValue = maxSizeBytesCall.parent.castSafelyTo()?.valueArguments?.firstOrNull()?.text + } + + var expireAfterWriteTimeValue: String? = null + var expireAfterWriteUnitValue: String? = null + val expireAfterWriteCall = + argumentExpression.findDescendantOfType { it.getReferencedName() == "expireAfterWrite" } + if (expireAfterWriteCall != null) { + val arguments = expireAfterWriteCall.parent.castSafelyTo()?.valueArguments + expireAfterWriteTimeValue = arguments?.firstOrNull()?.text + expireAfterWriteUnitValue = arguments?.getOrNull(1)?.text + } + + val arguments = mutableListOf() + if (maxSizeBytesValue != null) { + arguments.add("maxSizeBytes = $maxSizeBytesValue") + } + if (expireAfterWriteTimeValue != null && expireAfterWriteUnitValue?.startsWith("TimeUnit.") == true) { + arguments.add("expireAfterMillis = $expireAfterWriteUnitValue.toMillis($expireAfterWriteTimeValue)") + } + + val replaceExpressionUsageInfo = ReplaceExpressionUsageInfo( + this@UpdateLruNormalizedCacheFactory, + element.parent, + "MemoryCacheFactory(${arguments.joinToString(", ")})" + ) + replaceExpressionUsageInfo + } + } + } + + private class ReplaceImportUsageInfo(migrationItem: MigrationItem, element: KtImportDirective) : + MigrationItemUsageInfo(migrationItem, element) + + private class ReplaceExpressionUsageInfo(migrationItem: MigrationItem, element: PsiElement, val replacementExpression: String) : + MigrationItemUsageInfo(migrationItem, element) + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + val psiFactory = KtPsiFactory(project) + when (usage) { + is ReplaceImportUsageInfo -> { + element.replace(psiFactory.createImportDirective(ImportPath.fromString("com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory"))) + } + + is ReplaceExpressionUsageInfo -> { + element.replace(psiFactory.createExpression(usage.replacementExpression)) + } + } + } + + override fun importsToAdd() = setOf("com.apollographql.apollo3.cache.normalized.normalizedCache") +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateMethodName.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateMethodName.kt new file mode 100644 index 00000000000..d065eade8e7 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateMethodName.kt @@ -0,0 +1,25 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findMethodReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtPsiFactory + +class UpdateMethodName( + private val className: String, + private val oldMethodName: String, + private val newMethodName: String, + private val importToAdd: String? = null, +) : MigrationItem() { + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findMethodReferences(project = project, className = className, methodName = oldMethodName).toMigrationItemUsageInfo() + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val newMethodReference = KtPsiFactory(project).createExpression(newMethodName) + usage.element.replace(newMethodReference) + } + + override fun importsToAdd() = if (importToAdd != null) setOf(importToAdd) else emptySet() +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateOkHttpExecutionContext.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateOkHttpExecutionContext.kt new file mode 100644 index 00000000000..49ac69111a5 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateOkHttpExecutionContext.kt @@ -0,0 +1,59 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findClassReferences +import com.apollographql.ijplugin.refactoring.findOrCreateClass +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate +import org.jetbrains.kotlin.resolve.ImportPath + +object UpdateOkHttpExecutionContext : MigrationItem() { + override fun prepare(project: Project, migration: PsiMigration) { + findOrCreateClass(project, migration, "com.apollographql.apollo3.network.http.HttpInfo") + } + + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findClassReferences(project, "com.apollographql.apollo.http.OkHttpExecutionContext") + .mapNotNull { + val element = it.element + val importDirective = element.parentOfType() + if (importDirective != null) { + // Reference is an import + ReplaceImportUsageInfo(this@UpdateOkHttpExecutionContext, importDirective) + } else { + val parent = element.getParentOfTypesAndPredicate(false, PsiElement::class.java) { parent -> + parent.text.matches(Regex(".+\\.response.?\\.(code|headers)\\(\\)")) + } + parent?.toMigrationItemUsageInfo() + } + } + + } + + private class ReplaceImportUsageInfo(migrationItem: MigrationItem, element: KtImportDirective) : + MigrationItemUsageInfo(migrationItem, element) + + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val element = usage.element + val psiFactory = KtPsiFactory(project) + when (usage) { + is ReplaceImportUsageInfo -> { + element.replace(psiFactory.createImportDirective(ImportPath.fromString("com.apollographql.apollo3.network.http.HttpInfo"))) + } + + else -> { + val newExpression = element.text + .replace("OkHttpExecutionContext", "HttpInfo") + .replace(Regex("response.?\\.code\\(\\)"), "statusCode") + .replace(Regex("response.?\\.headers\\(\\)"), "headers") + element.replace(psiFactory.createExpression(newExpression)) + } + } + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdatePackageName.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdatePackageName.kt new file mode 100644 index 00000000000..485b91e58e2 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdatePackageName.kt @@ -0,0 +1,26 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.bindReferencesToElement +import com.apollographql.ijplugin.refactoring.findOrCreatePackage +import com.apollographql.ijplugin.refactoring.findPackageReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope + +class UpdatePackageName( + private val oldName: String, + private val newName: String, +) : MigrationItem() { + override fun prepare(project: Project, migration: PsiMigration) { + findOrCreatePackage(project, migration, newName) + } + + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findPackageReferences(project, oldName).toMigrationItemUsageInfo() + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + val newPackage = findOrCreatePackage(project, migration, newName) + usage.element.bindReferencesToElement(newPackage) + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateSqlNormalizedCacheFactory.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateSqlNormalizedCacheFactory.kt new file mode 100644 index 00000000000..f771cef6b69 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/refactoring/migration/item/UpdateSqlNormalizedCacheFactory.kt @@ -0,0 +1,48 @@ +package com.apollographql.ijplugin.refactoring.migration.item + +import com.apollographql.ijplugin.refactoring.findClassReferences +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiMigration +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.idea.debugger.sequence.psi.resolveType +import org.jetbrains.kotlin.idea.refactoring.fqName.fqName +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtImportDirective + +object UpdateSqlNormalizedCacheFactory : MigrationItem() { + private const val CACHE_FACTORY_FQN = "com.apollographql.apollo.cache.normalized.sql.SqlNormalizedCacheFactory" + + override fun findUsages(project: Project, migration: PsiMigration, searchScope: GlobalSearchScope): List { + return findClassReferences( + project, + CACHE_FACTORY_FQN, + ) + .mapNotNull { + val element = it.element + val importDirective = element.parentOfType() + if (importDirective != null) { + // Reference is an import + null + } else { + // `SqlNormalizedCacheFactory(...)` + val callExpression = element.parent as? KtCallExpression ?: return@mapNotNull null + // `SqlNormalizedCacheFactory(xxx, yyy)` and yyy is a String + if (callExpression.valueArguments.size == 2 && + callExpression.valueArguments[1]?.getArgumentExpression()?.resolveType()?.fqName?.asString() == "kotlin.String" + ) { + callExpression + } else { + null + } + } + } + .map { it.toMigrationItemUsageInfo() } + } + + override fun performRefactoring(project: Project, migration: PsiMigration, usage: MigrationItemUsageInfo) { + (usage.element as KtCallExpression).valueArgumentList?.removeArgument(0) + } + + override fun importsToAdd() = setOf("com.apollographql.apollo3.cache.normalized.normalizedCache") +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloApplicationService.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloApplicationService.kt new file mode 100644 index 00000000000..6899170d9be --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloApplicationService.kt @@ -0,0 +1,9 @@ +package com.apollographql.ijplugin.services + +import com.apollographql.ijplugin.util.logd + +class ApolloApplicationService { + init { + logd() + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloProjectService.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloProjectService.kt new file mode 100644 index 00000000000..a0c0122258d --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/ApolloProjectService.kt @@ -0,0 +1,11 @@ +package com.apollographql.ijplugin.services + +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project + +interface ApolloProjectService { + val isApolloAndroid2Project: Boolean + val isApolloKotlin3Project: Boolean +} + +fun Project.apolloProjectService() = service() diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/impl/ApolloProjectServiceImpl.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/impl/ApolloProjectServiceImpl.kt new file mode 100644 index 00000000000..ad227f366eb --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/services/impl/ApolloProjectServiceImpl.kt @@ -0,0 +1,197 @@ +package com.apollographql.ijplugin.services.impl + +import com.apollographql.ijplugin.services.ApolloProjectService +import com.apollographql.ijplugin.util.getGradleName +import com.apollographql.ijplugin.util.isApolloAndroid2Project +import com.apollographql.ijplugin.util.isApolloKotlin3Project +import com.apollographql.ijplugin.util.logd +import com.apollographql.ijplugin.util.logw +import com.apollographql.ijplugin.util.runWriteActionInEdt +import com.intellij.lang.jsgraphql.GraphQLFileType +import com.intellij.openapi.Disposable +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.FileEditorManagerEvent +import com.intellij.openapi.fileEditor.FileEditorManagerListener +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessModuleDir +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.newvfs.BulkFileListener +import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent +import com.intellij.openapi.vfs.newvfs.events.VFileEvent +import com.intellij.psi.PsiDocumentManager +import org.gradle.tooling.CancellationTokenSource +import org.gradle.tooling.GradleConnector +import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID +import org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper +import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings +import org.jetbrains.plugins.gradle.util.GradleConstants + +private const val CODEGEN_GRADLE_TASK_NAME = "generateApolloSources" + +class ApolloProjectServiceImpl( + private val project: Project, +) : ApolloProjectService, Disposable { + + // TODO: This is initialized only once, but this could actually change during the project's lifecycle + // TODO: find a way to invalidate this whenever project's dependencies change + override val isApolloAndroid2Project by lazy { project.isApolloAndroid2Project } + override val isApolloKotlin3Project by lazy { project.isApolloKotlin3Project } + + private var dirtyGqlDocument: Document? = null + + private var codegenGradleCancellationTokenSource: CancellationTokenSource? = null + + init { + logd("project=${project.name} isApolloKotlin3Project=$isApolloKotlin3Project") + if (isApolloKotlin3Project) { + // Trigger the codegen whenever a GraphQL file is saved. + observeVfsChanges() + + // To make this more reactive, any touched GraphQL document will automatically be saved (thus triggering the codegen) + // as soon as the current editor is changed. + observeDocumentChanges() + observeFileEditorChanges() + } + } + + private fun observeDocumentChanges() { + EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener { + override fun documentChanged(event: DocumentEvent) { + val vFile = PsiDocumentManager.getInstance(project).getPsiFile(event.document)?.virtualFile ?: return + logd("vFile=${vFile.path}") + val module = getModuleForGqlFile(vFile) + if (module == null) { + // Not a GraphQL file or not from this project: ignore + return + } + dirtyGqlDocument = event.document + } + }, this) + } + + private fun observeFileEditorChanges() { + project.messageBus.connect(this).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener { + override fun selectionChanged(event: FileEditorManagerEvent) { + logd(event.newFile) + dirtyGqlDocument?.let { + dirtyGqlDocument = null + runWriteActionInEdt { + runCatching { + FileDocumentManager.getInstance().saveDocument(it) + } + } + } + } + }) + } + + private fun observeVfsChanges() { + project.messageBus.connect().subscribe( + VirtualFileManager.VFS_CHANGES, + object : BulkFileListener { + override fun before(events: MutableList) { + handleEvents(events.filterIsInstance()) + } + + override fun after(events: MutableList) { + handleEvents(events.filterNot { it is VFileDeleteEvent }) + } + + private fun handleEvents(events: List) { + val modules = mutableSetOf() + for (event in events) { + val vFile = event.file!! + logd("vFile=${vFile.path}") + val module = getModuleForGqlFile(vFile) + if (module == null) { + // Not a GraphQL file or not from this project: ignore + continue + } + modules += module + } + if (modules.isNotEmpty()) { + triggerCodegen(modules) + } + } + }, + ) + } + + private fun getModuleForGqlFile(vFile: VirtualFile): Module? { + if (vFile.fileType !is GraphQLFileType) { + // Only care for GraphQL files + return null + } + val moduleForFile = ProjectRootManager.getInstance(project).fileIndex.getModuleForFile(vFile) + if (moduleForFile == null) { + // A file from an external project: ignore + return null + } + return moduleForFile + } + + private fun triggerCodegen(modules: Set) { + logd("modules=$modules") + + // Cancel any already running codegen task + codegenGradleCancellationTokenSource?.cancel() + + val rootProjectPath = ExternalSystemApiUtil.getExternalRootProjectPath(modules.first()) + if (rootProjectPath == null) { + logw("Could not get root project path for module ${modules.first().name}") + return + } + val taskNames = modules.map { + val gradleModuleName = it.getGradleName() + if (gradleModuleName == "") CODEGEN_GRADLE_TASK_NAME else ":$gradleModuleName:$CODEGEN_GRADLE_TASK_NAME" + } + logd("taskNames=$taskNames") + val executionSettings = ExternalSystemApiUtil.getExecutionSettings( + project, + rootProjectPath, + GradleConstants.SYSTEM_ID + ) + + val gradleExecutionHelper = GradleExecutionHelper() + gradleExecutionHelper.execute(rootProjectPath, executionSettings) { connection -> + codegenGradleCancellationTokenSource = GradleConnector.newCancellationTokenSource() + + try { + val id = ExternalSystemTaskId.create(GRADLE_SYSTEM_ID, ExternalSystemTaskType.REFRESH_TASKS_LIST, project) + gradleExecutionHelper.getBuildLauncher( + id, + connection, + executionSettings, + ExternalSystemTaskNotificationListenerAdapter.NULL_OBJECT + ) + .forTasks(*taskNames.toTypedArray()) + .withCancellationToken(codegenGradleCancellationTokenSource!!.token()) + .run() + logd("Codegen task finished successfully") + + // Sync impacted modules so the generated files are visible to the IDE + for (module in modules) { + VfsUtil.markDirtyAndRefresh(true, true, true, module.guessModuleDir()) + } + } catch (t: Throwable) { + logd(t, "Failed to run codegen") + } + } + } + + override fun dispose() { + logd("project=${project.name}") + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Apollo.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Apollo.kt new file mode 100644 index 00000000000..80f65e7d561 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Apollo.kt @@ -0,0 +1,25 @@ +package com.apollographql.ijplugin.util + +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager + +val Project.isApolloAndroid2Project: Boolean + get() = dependsOn("com.apollographql.apollo") + +val Project.isApolloKotlin3Project: Boolean + get() = dependsOn("com.apollographql.apollo3") + +private fun Project.dependsOn(groupId: String): Boolean { + var found = false + service().orderEntries().librariesOnly().forEachLibrary { library -> + logd("library=${library.name}") + if (library.name?.contains("$groupId:") == true) { + found = true + false + } else { + true + } + } + return found +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Gradle.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Gradle.kt new file mode 100644 index 00000000000..d8d5a3eae7e --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Gradle.kt @@ -0,0 +1,12 @@ +package com.apollographql.ijplugin.util + +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.module.Module + +fun Module.getGradleName(): String? { + val projectId = ExternalSystemApiUtil.getExternalProjectId(this) ?: return null + // "MyProject:main" -> "" + // "MyProject:MyModule:main" -> "MyModule" + // "MyProject:MyModule:MySubModule:main" -> "MyModule:MySubModule" + return projectId.split(":").drop(1).dropLast(1).joinToString(":") +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Logging.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Logging.kt new file mode 100644 index 00000000000..a8e996ea296 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Logging.kt @@ -0,0 +1,70 @@ +package com.apollographql.ijplugin.util + +import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.kotlin.idea.util.application.isUnitTestMode +import org.jetbrains.kotlin.utils.PrintingLogger + +// To see debug logs, in the embedded IntelliJ app, go to Help / Diagnostic Tools / Debug Log Settings and add "Apollo" +// or pass -Didea.log.debug.categories=Apollo to the VM. +// See https://plugins.jetbrains.com/docs/intellij/ide-infrastructure.html#logging +// and https://plugins.jetbrains.com/docs/intellij/testing-faq.html#how-to-enable-debugtrace-logging +private val logger = if (isUnitTestMode()) PrintingLogger(System.err) else Logger.getInstance("Apollo") + +fun logd() { + logger.debug(prefix(null)) +} + +fun logd(message: String?) { + logger.debug(prefix(message)) +} + +fun logd(any: Any?) { + logger.debug(prefix(any?.toString())) +} + +fun logd(throwable: Throwable) { + logger.debug(throwable) +} + +fun logd(throwable: Throwable, message: String) { + logger.debug(prefix(message), throwable) +} + +fun logd(throwable: Throwable, any: Any) { + logger.debug(prefix(any.toString()), throwable) +} + +fun logw(message: String) { + logger.warn(message) +} + +fun logw(any: Any) { + logger.warn(any.toString()) +} + +fun logw(throwable: Throwable) { + logger.warn(throwable) +} + +fun logw(throwable: Throwable, message: String) { + logger.warn(message, throwable) +} + +fun logw(throwable: Throwable, any: Any) { + logger.warn(any.toString(), throwable) +} + +private fun getClassAndMethodName(): String { + val stackTrace = Thread.currentThread().stackTrace + val className = stackTrace[3].className.substringAfterLast('.') + return className + " " + stackTrace[3].methodName +} + +@Suppress("NOTHING_TO_INLINE") +private inline fun prefix(message: String?): String { + return if (message == null) { + getClassAndMethodName() + } else { + "${getClassAndMethodName()} - $message" + } +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Psi.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Psi.kt new file mode 100644 index 00000000000..569b90132b9 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Psi.kt @@ -0,0 +1,18 @@ +package com.apollographql.ijplugin.util + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtImportList +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +fun PsiElement.containingKtFile(): KtFile? = getStrictParentOfType() + +fun PsiElement.containingKtFileImportList(): KtImportList? = containingKtFile()?.importList + +fun PsiElement.addSiblingBefore(element: PsiElement): PsiElement { + return this.parent.addBefore(element, this) +} + +fun PsiElement.addSiblingAfter(element: PsiElement): PsiElement { + return this.parent.addAfter(element, this) +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt new file mode 100644 index 00000000000..4efe1503162 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt @@ -0,0 +1,9 @@ +package com.apollographql.ijplugin.util + +fun String.quoted(): String { + return if (this.startsWith('"') && this.endsWith('"')) this else "\"$this\"" +} + +fun String.unquoted(): String { + return removeSurrounding("\"") +} diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Threading.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Threading.kt new file mode 100644 index 00000000000..711fe73c672 --- /dev/null +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/util/Threading.kt @@ -0,0 +1,9 @@ +package com.apollographql.ijplugin.util + +import com.intellij.openapi.application.ApplicationManager + +fun runWriteActionInEdt(action: () -> Unit) { + ApplicationManager.getApplication().invokeLater { + ApplicationManager.getApplication().runWriteAction(action) + } +} diff --git a/intellij-plugin/src/main/resources/META-INF/plugin.xml b/intellij-plugin/src/main/resources/META-INF/plugin.xml new file mode 100644 index 00000000000..bdc99a38972 --- /dev/null +++ b/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,42 @@ + + + com.apollographql.ijplugin + Apollo GraphQL + + + apollographql + + + + com.intellij.modules.platform + com.intellij.modules.java + org.jetbrains.kotlin + com.intellij.gradle + com.intellij.lang.jsgraphql + org.toml.lang + + messages.ApolloBundle + + + + + + + + + + + + + + + + + + + diff --git a/intellij-plugin/src/main/resources/META-INF/pluginIcon.svg b/intellij-plugin/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 00000000000..e44971c0914 --- /dev/null +++ b/intellij-plugin/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/intellij-plugin/src/main/resources/icons/gutter-operation.svg b/intellij-plugin/src/main/resources/icons/gutter-operation.svg new file mode 100644 index 00000000000..fa5919aa65b --- /dev/null +++ b/intellij-plugin/src/main/resources/icons/gutter-operation.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/intellij-plugin/src/main/resources/messages/ApolloBundle.properties b/intellij-plugin/src/main/resources/messages/ApolloBundle.properties new file mode 100644 index 00000000000..99c3b424166 --- /dev/null +++ b/intellij-plugin/src/main/resources/messages/ApolloBundle.properties @@ -0,0 +1,18 @@ +name=Apollo +navigation.operation.tooltip=Navigate to GraphQL operation definition + +action.ApolloV2ToV3MigrationAction.text=Migrate to Apollo Kotlin 3... +action.ApolloV2ToV3MigrationAction.description=Run helpers to migrate from Apollo Android 2 to Apollo Kotlin 3 + +action.ApolloV2ToV3MigrationAction.confirmDialog.title=Migrate to Apollo Kotlin 3 +action.ApolloV2ToV3MigrationAction.confirmDialog.ok=Do Refactor +action.ApolloV2ToV3MigrationAction.confirmDialog.cancel=Cancel +action.ApolloV2ToV3MigrationAction.confirmDialog.message=This will migrate your project from Apollo Android v2 to Apollo Kotlin v3.\n\ + \n\ + Some manual changes may still be needed after refactoring: please refer to the migration guide.\n\ + \n\ + Before refactoring please make sure to commit any local changes and that the project builds with no errors. + +ApolloV2ToV3MigrationProcessor.title=Migrate to Apollo Kotlin 3 +ApolloV2ToV3MigrationProcessor.codeReferences=Items to be migrated +ApolloV2ToV3MigrationProcessor.noUsage=No Apollo Android 2 references found in the project diff --git a/intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/ApolloV2ToV3MigrationTest.kt b/intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/ApolloV2ToV3MigrationTest.kt new file mode 100644 index 00000000000..96a2eee0913 --- /dev/null +++ b/intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/ApolloV2ToV3MigrationTest.kt @@ -0,0 +1,131 @@ +package com.apollographql.ijplugin + +import com.apollographql.ijplugin.refactoring.migration.ApolloV2ToV3MigrationProcessor +import com.intellij.application.options.CodeStyle +import com.intellij.openapi.module.Module +import com.intellij.openapi.roots.ContentEntry +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.TestDataPath +import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + + +@TestDataPath("\$CONTENT_ROOT/testData/migration/v2-to-v3") +@RunWith(JUnit4::class) +class ApolloV2ToV3MigrationTest : LightJavaCodeInsightFixtureTestCase() { + private val mavenLibraries = listOf( + "com.apollographql.apollo:apollo-runtime:2.5.14", + "com.apollographql.apollo:apollo-coroutines-support:2.5.14", + "com.apollographql.apollo:apollo-normalized-cache-jvm:2.5.14", + "com.apollographql.apollo:apollo-normalized-cache-sqlite-jvm:2.5.14", + "com.apollographql.apollo:apollo-http-cache-api:2.5.14", + ) + + override fun getTestDataPath() = "src/test/testData/migration/v2-to-v3" + + private val projectDescriptor = object : DefaultLightProjectDescriptor() { + override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) { + for (library in mavenLibraries) { + addFromMaven(model, library, true, DependencyScope.COMPILE) + } + } + } + + override fun getProjectDescriptor(): LightProjectDescriptor { + return projectDescriptor + } + + override fun setUp() { + super.setUp() + // Set the indent size to 2 to match the fixtures (default is 4) + val codeStyleSettings = CodeStyle.getSettings(project) + val kotlinSettings = codeStyleSettings.getCommonSettings(KotlinLanguage.INSTANCE) + kotlinSettings.indentOptions!!.INDENT_SIZE = 2 + } + + @Test + fun testUpdatePackageName() = runMigration() + + @Test + fun testUpdateMethodName() = runMigration() + + @Test + fun testUpdateClassName() = runMigration() + + @Test + fun testHttpCache() = runMigration() + + @Test + fun testInMemoryNormalizedCache() = runMigration() + + @Test + fun testSqlNormalizedCache() = runMigration() + + @Test + fun testUpgradeGradlePluginInBuildGradleKts() = runMigration(extension = "gradle.kts", fileNameInProject = "build.gradle.kts") + + @Test + fun testRemoveGradleDependenciesInBuildGradleKts() = runMigration(extension = "gradle.kts", fileNameInProject = "build.gradle.kts") + + @Test + fun testRemoveGradleDependenciesInLibsVersionsToml() = runMigration(extension = "versions.toml", fileNameInProject = "libs.versions.toml") + + @Test + fun testUpdateGradleDependenciesInBuildGradleKts() = runMigration(extension = "gradle.kts", fileNameInProject = "build.gradle.kts") + + @Test + fun testUpdateGradleDependenciesInLibsVersionsToml() = runMigration(extension = "versions.toml", fileNameInProject = "libs.versions.toml") + + @Test + fun testWatch() = runMigration() + + @Test + fun testAddCustomTypeAdapter() = runMigration() + + @Test + fun testUpdateCustomTypeMapping() = runMigration(extension = "gradle.kts", fileNameInProject = "build.gradle.kts") + + @Test + fun testUpdateEnumValueUpperCase() = runMigration() + + @Test + fun testUpdateInput() = runMigration() + + @Test + fun testAddUseVersion2Compat() = runMigration(extension = "gradle.kts", fileNameInProject = "build.gradle.kts") + + @Test + fun testUpdateGraphqlSourceDirectorySet() = runMigration(extension = "gradle.kts", fileNameInProject = "build.gradle.kts") + + @Test + fun testUpdateOkHttpExecutionContext() = runMigration() + + @Test + fun testUpdateOperationName() = runMigration() + + @Test + fun testUpdateFileUpload() = runMigration() + + private fun runMigration(extension: String = "kt", fileNameInProject: String? = null) { + val fileBaseName = getTestName(true) + if (fileNameInProject != null) { + myFixture.copyFileToProject("$fileBaseName.$extension", fileNameInProject) + } else { + myFixture.copyFileToProject("$fileBaseName.$extension") + } + + ApolloV2ToV3MigrationProcessor(project).run() + + if (fileNameInProject != null) { + myFixture.checkResultByFile(fileNameInProject, fileBaseName + "_after.$extension", true) + } else { + myFixture.checkResultByFile("$fileBaseName.$extension", fileBaseName + "_after.$extension", true) + } + } +} diff --git a/intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/MavenUtil.kt b/intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/MavenUtil.kt new file mode 100644 index 00000000000..23aa51cedf8 --- /dev/null +++ b/intellij-plugin/src/test/kotlin/com/apollographql/ijplugin/MavenUtil.kt @@ -0,0 +1,49 @@ +package com.apollographql.ijplugin + +import com.intellij.jarRepository.JarRepositoryManager +import com.intellij.jarRepository.RemoteRepositoryDescription +import com.intellij.jarRepository.RepositoryLibraryType +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties + +/** + * This was copied from `com.intellij.testFramework.fixtures.MavenDependencyUtil#addFromMaven`, and modified to remove the call to + * `getRemoteRepositoryDescriptions()` which only works when the `intellij-community` repo is available, and the `idea.home.path` + * environment variable points to it. + * + * For this to work a "mock jdk" must still be present in `/java/mockJDK-1.7/jre/lib/rt.jar`. + * + * See https://jetbrains-platform.slack.com/archives/CPL5291JP/p1664105522154139 and https://youtrack.jetbrains.com/issue/IJSDK-321 + */ +fun addFromMaven( + model: ModifiableRootModel, + mavenCoordinates: String, + includeTransitiveDependencies: Boolean, + dependencyScope: DependencyScope, +) { + val remoteRepositoryDescriptions = listOf(RemoteRepositoryDescription.MAVEN_CENTRAL) + val libraryProperties = RepositoryLibraryProperties(mavenCoordinates, includeTransitiveDependencies) + val roots = JarRepositoryManager.loadDependenciesModal( + model.project, + libraryProperties, + false, + false, + null, + remoteRepositoryDescriptions + ) + val tableModel = model.moduleLibraryTable.modifiableModel + val library = tableModel.createLibrary(mavenCoordinates, RepositoryLibraryType.REPOSITORY_LIBRARY_KIND) + val libraryModel = library.modifiableModel + check(!roots.isEmpty()) { String.format("No roots for '%s'", mavenCoordinates) } + for (root in roots) { + libraryModel.addRoot(root.file, root.type) + } + (libraryModel as LibraryEx.ModifiableModelEx).properties = libraryProperties + val libraryOrderEntry = model.findLibraryOrderEntry(library) + ?: throw IllegalStateException("Unable to find registered library $mavenCoordinates") + libraryOrderEntry.scope = dependencyScope + libraryModel.commit() + tableModel.commit() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter.kt new file mode 100644 index 00000000000..193d81054c0 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter.kt @@ -0,0 +1,47 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.CustomTypeAdapter +import com.apollographql.apollo.api.CustomTypeValue +import com.apollographql.apollo.api.ScalarType +import java.net.URL +import java.util.* + +enum class CustomType : ScalarType { + DATETIME { + override fun typeName(): String = "DateTime" + override fun className(): String = "kotlin.Any" + }, + + URL { + override fun typeName(): String = "Url" + override fun className(): String = "kotlin.Any" + } +} + +suspend fun main() { + val apolloClient = ApolloClient.builder() + .addCustomTypeAdapter(CustomType.DATETIME, DateTimeAdapter()) + .addCustomTypeAdapter(CustomType.URL, UrlAdapter()) + .build() +} + +class DateTimeAdapter : CustomTypeAdapter { + override fun decode(value: CustomTypeValue<*>): Date { + TODO("Not yet implemented") + } + + override fun encode(value: Date): CustomTypeValue<*> { + TODO("Not yet implemented") + } +} + +class UrlAdapter : CustomTypeAdapter { + override fun decode(value: CustomTypeValue<*>): URL { + TODO("Not yet implemented") + } + + override fun encode(value: URL): CustomTypeValue<*> { + TODO("Not yet implemented") + } +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter_after.kt new file mode 100644 index 00000000000..8f444a57122 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/addCustomTypeAdapter_after.kt @@ -0,0 +1,49 @@ +package com.example + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.CustomTypeAdapter +import com.apollographql.apollo3.api.CustomTypeValue +import com.apollographql.apollo3.api.ScalarType +import java.net.URL +import java.util.* + +enum class CustomType : ScalarType { + DATETIME { + override fun typeName(): String = "DateTime" + override fun className(): String = "kotlin.Any" + }, + + URL { + override fun typeName(): String = "Url" + override fun className(): String = "kotlin.Any" + } +} + +suspend fun main() { + val apolloClient = ApolloClient.Builder() + // TODO: Use addCustomScalarAdapter instead. See https://www.apollographql.com/docs/kotlin/migration/3.0/#custom-scalar-adapters + .addCustomTypeAdapter(DateTime.type, DateTimeAdapter()) + // TODO: Use addCustomScalarAdapter instead. See https://www.apollographql.com/docs/kotlin/migration/3.0/#custom-scalar-adapters + .addCustomTypeAdapter(Url.type, UrlAdapter()) + .build() +} + +class DateTimeAdapter : CustomTypeAdapter { + override fun decode(value: CustomTypeValue<*>): Date { + TODO("Not yet implemented") + } + + override fun encode(value: Date): CustomTypeValue<*> { + TODO("Not yet implemented") + } +} + +class UrlAdapter : CustomTypeAdapter { + override fun decode(value: CustomTypeValue<*>): URL { + TODO("Not yet implemented") + } + + override fun encode(value: URL): CustomTypeValue<*> { + TODO("Not yet implemented") + } +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat.gradle.kts new file mode 100644 index 00000000000..eb66e09bd23 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat.gradle.kts @@ -0,0 +1,3 @@ +apollo { + // Other stuff +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat_after.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat_after.gradle.kts new file mode 100644 index 00000000000..610a8b738df --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/addUseVersion2Compat_after.gradle.kts @@ -0,0 +1,7 @@ +apollo { + // TODO: This shortcut jumpstarts the migration from v2 to v3, but it is recommended to use settings idiomatic to v3 instead. + // See https://www.apollographql.com/docs/kotlin/migration/3.0/ + useVersion2Compat() + + // Other stuff +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache.kt new file mode 100644 index 00000000000..7c13cf3f9fc --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache.kt @@ -0,0 +1,43 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.Query +import com.apollographql.apollo.api.cache.http.HttpCachePolicy +import com.apollographql.apollo.cache.http.ApolloHttpCache +import com.apollographql.apollo.cache.http.DiskLruHttpCacheStore +import com.apollographql.apollo.cache.http.internal.FileSystem +import java.io.File + +suspend fun main() { + val maxSize = 10000L + val apolloHttpCache1 = ApolloHttpCache(DiskLruHttpCacheStore(File("cache1"), maxSize)) + + val apolloHttpCache2 = ApolloHttpCache(DiskLruHttpCacheStore(FileSystem.SYSTEM, File("cache2"), maxSize)) + + val apolloHttpCache3: ApolloHttpCache? = null + + val diskLruHttpCacheStore1 = DiskLruHttpCacheStore(File("cache4"), maxSize) + val apolloHttpCache4 = ApolloHttpCache(diskLruHttpCacheStore1) + + val diskLruHttpCacheStore2 = DiskLruHttpCacheStore(FileSystem.SYSTEM, File("cache5"), maxSize) + val apolloHttpCache5 = ApolloHttpCache(diskLruHttpCacheStore2) + + val apolloClient = ApolloClient.builder() + .httpCache(ApolloHttpCache(DiskLruHttpCacheStore(File("cacheA"), maxSize))) + .httpCache(ApolloHttpCache(DiskLruHttpCacheStore(FileSystem.SYSTEM, File("cacheB"), maxSize))) + .httpCache(apolloHttpCache1) + .httpCache(apolloHttpCache2) + .httpCache(apolloHttpCache3!!) + .httpCache(apolloHttpCache4) + .httpCache(apolloHttpCache5) + .build() + + val myQuery: Query<*, *, *>? = null + apolloClient!! + .query(myQuery!!) + .toBuilder() + .httpCachePolicy(HttpCachePolicy.NETWORK_ONLY) + .build() + + apolloClient.clearHttpCache() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache_after.kt new file mode 100644 index 00000000000..cff18aae55d --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/httpCache_after.kt @@ -0,0 +1,32 @@ +package com.example + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.Query +import com.apollographql.apollo3.cache.http.HttpFetchPolicy +import com.apollographql.apollo3.cache.http.ApolloHttpCache +import java.io.File +import com.apollographql.apollo3.cache.http.httpFetchPolicy +import com.apollographql.apollo3.cache.http.httpCache + +suspend fun main() { + val maxSize = 10000L + + val apolloHttpCache3: ApolloHttpCache? = null + + val apolloClient = ApolloClient.Builder() + .httpCache(File("cacheA"), maxSize) + .httpCache(File("cacheB"), maxSize) + .httpCache(File("cache1"), maxSize) + .httpCache(File("cache2"), maxSize) + .httpCache(/* TODO: This could not be migrated automatically. Please check the migration guide at https://www.apollographql.com/docs/kotlin/migration/3.0/ */) + .httpCache(File("cache4"), maxSize) + .httpCache(File("cache5"), maxSize) + .build() + + val myQuery: Query<*, *, *>? = null + apolloClient!! + .query(myQuery!!) + .httpFetchPolicy(HttpFetchPolicy.NetworkOnly) + + apolloClient.httpCache.clearAll() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache.kt new file mode 100644 index 00000000000..80277cbc4dc --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache.kt @@ -0,0 +1,69 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.Operation +import com.apollographql.apollo.api.Query +import com.apollographql.apollo.api.internal.ResponseFieldMarshaller +import com.apollographql.apollo.cache.normalized.lru.EvictionPolicy +import com.apollographql.apollo.cache.normalized.lru.LruNormalizedCacheFactory +import com.apollographql.apollo.fetcher.ApolloResponseFetchers +import java.util.concurrent.TimeUnit + +suspend fun main() { + class MyData : Operation.Data { + override fun marshaller(): ResponseFieldMarshaller = TODO() + } + + class MyVariables : Operation.Variables() + + val cacheFactory1 = LruNormalizedCacheFactory( + evictionPolicy = EvictionPolicy.builder().maxSizeBytes(10 * 1024 * 1024).build() + ) + val cacheFactory2 = LruNormalizedCacheFactory( + EvictionPolicy.builder().maxSizeBytes(10 * 1024 * 1024).build() + ) + val cacheFactory3 = LruNormalizedCacheFactory( + EvictionPolicy.builder() + .maxSizeBytes(10 * 1024 * 1024) + .expireAfterWrite(10, TimeUnit.MILLISECONDS) + .build() + ) + val cacheFactory4 = LruNormalizedCacheFactory( + EvictionPolicy.builder() + .maxSizeBytes(10 * 1024 * 1024) + .expireAfterWrite(10, TimeUnit.MILLISECONDS) + .expireAfterAccess(10, TimeUnit.MILLISECONDS) + .maxEntries(42) + .build() + ) + val cacheFactory5 = LruNormalizedCacheFactory( + EvictionPolicy.builder() + .expireAfterWrite(10, TimeUnit.HOURS) + .build() + ) + + val apolloClient = ApolloClient.builder() + .normalizedCache(cacheFactory1) + .build() + + val myQuery: Query? = null + + apolloClient!! + .query(myQuery!!) + .toBuilder() + .responseFetcher(ApolloResponseFetchers.NETWORK_ONLY) + .build() + + val cachedData = apolloClient + .apolloStore + .read(myQuery) + .execute() + + val data: MyData? = null + apolloClient + .apolloStore + .writeAndPublish(myQuery, data!!) + .execute() + + apolloClient.clearNormalizedCache() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache_after.kt new file mode 100644 index 00000000000..5eb4a7046f4 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/inMemoryNormalizedCache_after.kt @@ -0,0 +1,47 @@ +package com.example + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.Operation +import com.apollographql.apollo3.api.Query +import com.apollographql.apollo3.api.internal.ResponseFieldMarshaller +import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory +import com.apollographql.apollo3.cache.normalized.FetchPolicy +import java.util.concurrent.TimeUnit +import com.apollographql.apollo3.cache.normalized.fetchPolicy +import com.apollographql.apollo3.cache.normalized.apolloStore +import com.apollographql.apollo3.cache.normalized.normalizedCache + +suspend fun main() { + class MyData : Operation.Data { + override fun marshaller(): ResponseFieldMarshaller = TODO() + } + + class MyVariables : Operation.Variables() + + val cacheFactory1 = MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024) + val cacheFactory2 = MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024) + val cacheFactory3 = MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024, expireAfterMillis = TimeUnit.MILLISECONDS.toMillis(10)) + val cacheFactory4 = MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024, expireAfterMillis = TimeUnit.MILLISECONDS.toMillis(10)) + val cacheFactory5 = MemoryCacheFactory(expireAfterMillis = TimeUnit.HOURS.toMillis(10)) + + val apolloClient = ApolloClient.Builder() + .normalizedCache(cacheFactory1) + .build() + + val myQuery: Query? = null + + apolloClient!! + .query(myQuery!!) + .fetchPolicy(FetchPolicy.NetworkOnly) + + val cachedData = apolloClient + .apolloStore + .readOperation(myQuery) + + val data: MyData? = null + apolloClient + .apolloStore + .writeOperation(myQuery, data!!) + + apolloClient.apolloStore.clearAll() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts.gradle.kts new file mode 100644 index 00000000000..51d1dc143e4 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts.gradle.kts @@ -0,0 +1,7 @@ +dependencies { + implementation("org.example:somelibrary:1.0.0") + implementation("com.apollographql.apollo:apollo-coroutines-support:2.5.14") + implementation("com.apollographql.apollo:apollo-coroutines-support:$apollo") + implementation("com.apollographql.apollo:apollo-coroutines-support") + implementation("com.apollographql.apollo:apollo-android-support:2.5.14") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts_after.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts_after.gradle.kts new file mode 100644 index 00000000000..ac84d85d3d9 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInBuildGradleKts_after.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + implementation("org.example:somelibrary:1.0.0") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml.versions.toml b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml.versions.toml new file mode 100644 index 00000000000..aa2a1430c8f --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml.versions.toml @@ -0,0 +1,14 @@ +[versions] +# gradlePlugin versions +androidBuildTools = "7.2.1" +apollo = "2.5.9" + +[libraries] +accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" } +apollo-coroutines-short = "com.apollographql.apollo:apollo-coroutines-support:2.5.9" +apollo-coroutines-medium-version = { module = "com.apollographql.apollo:apollo-coroutines-support", version = "2.5.9" } +apollo-coroutines-medium-ref = { module = "com.apollographql.apollo:apollo-coroutines-support", version.ref = "apollo" } +apollo-coroutines-long-version = { group = "com.apollographql.apollo", name = "apollo-coroutines-support", version = "2.5.9" } +apollo-coroutines-long-ref = { group = "com.apollographql.apollo", name = "apollo-coroutines-support", version.ref = "apollo" } + +apollo-android = { module = "com.apollographql.apollo:apollo-android-support", version.ref = "apollo" } diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml_after.versions.toml b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml_after.versions.toml new file mode 100644 index 00000000000..05fc3e149cb --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/removeGradleDependenciesInLibsVersionsToml_after.versions.toml @@ -0,0 +1,20 @@ +[versions] +# gradlePlugin versions +androidBuildTools = "7.2.1" +apollo = "3.7.1" + +[libraries] +accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" } +# TODO: remove this declaration and its uses in your gradle files +# apollo-coroutines-short = "com.apollographql.apollo:apollo-coroutines-support:2.5.9" +# TODO: remove this declaration and its uses in your gradle files +# apollo-coroutines-medium-version = { module = "com.apollographql.apollo:apollo-coroutines-support", version = "2.5.9" } +# TODO: remove this declaration and its uses in your gradle files +# apollo-coroutines-medium-ref = { module = "com.apollographql.apollo:apollo-coroutines-support", version.ref = "apollo" } +# TODO: remove this declaration and its uses in your gradle files +# apollo-coroutines-long-version = { group = "com.apollographql.apollo", name = "apollo-coroutines-support", version = "2.5.9" } +# TODO: remove this declaration and its uses in your gradle files +# apollo-coroutines-long-ref = { group = "com.apollographql.apollo", name = "apollo-coroutines-support", version.ref = "apollo" } + +# TODO: remove this declaration and its uses in your gradle files +# apollo-android = { module = "com.apollographql.apollo:apollo-android-support", version.ref = "apollo" } diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache.kt new file mode 100644 index 00000000000..2643fa81472 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache.kt @@ -0,0 +1,25 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.cache.normalized.sql.SqlNormalizedCacheFactory + +suspend fun main() { + val dbName = "apollo.db" + val cacheFactory1 = SqlNormalizedCacheFactory(context, "apollo.db") + val cacheFactory2 = SqlNormalizedCacheFactory(context, dbName) + val cacheFactory3 = SqlNormalizedCacheFactory(dbName()) + val cacheFactory4 = SqlNormalizedCacheFactory(context, someInt()) + val cacheFactory5 = SqlNormalizedCacheFactory(a, b, c, d) + + val apolloClient = ApolloClient.builder() + .normalizedCache(cacheFactory1) + .build() +} + +fun dbName(): String { + return "apollo.db" +} + +fun someInt(): Int { + return 1 +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache_after.kt new file mode 100644 index 00000000000..c9c2999c1ab --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/sqlNormalizedCache_after.kt @@ -0,0 +1,26 @@ +package com.example + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory +import com.apollographql.apollo3.cache.normalized.normalizedCache + +suspend fun main() { + val dbName = "apollo.db" + val cacheFactory1 = SqlNormalizedCacheFactory("apollo.db") + val cacheFactory2 = SqlNormalizedCacheFactory(dbName) + val cacheFactory3 = SqlNormalizedCacheFactory(dbName()) + val cacheFactory4 = SqlNormalizedCacheFactory(context, someInt()) + val cacheFactory5 = SqlNormalizedCacheFactory(a, b, c, d) + + val apolloClient = ApolloClient.Builder() + .normalizedCache(cacheFactory1) + .build() +} + +fun dbName(): String { + return "apollo.db" +} + +fun someInt(): Int { + return 1 +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName.kt new file mode 100644 index 00000000000..5e48f5a2132 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName.kt @@ -0,0 +1,7 @@ +package com.example + +import com.apollographql.apollo.api.Response + +suspend fun main() { + val response: Response? = null +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName_after.kt new file mode 100644 index 00000000000..19debf00ead --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateClassName_after.kt @@ -0,0 +1,7 @@ +package com.example + +import com.apollographql.apollo3.api.ApolloResponse + +suspend fun main() { + val response: ApolloResponse? = null +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping.gradle.kts new file mode 100644 index 00000000000..c15cb40dadc --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping.gradle.kts @@ -0,0 +1,14 @@ +apollo { + customTypeMapping.set( + mapOf( + "URL" to "kotlin.String", + "LocalDate" to "java.time.LocalDate", + "Upload" to "com.apollographql.apollo.api.FileUpload", + "PaymentMethodsResponse" to "com.adyen.checkout.components.model.PaymentMethodsApiResponse", + "CheckoutPaymentsAction" to "kotlin.String", + "CheckoutPaymentAction" to "kotlin.String", + "JSONString" to "org.json.JSONObject", + "Instant" to "java.time.Instant", + ) + ) +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping_after.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping_after.gradle.kts new file mode 100644 index 00000000000..c4956fce00d --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateCustomTypeMapping_after.gradle.kts @@ -0,0 +1,14 @@ +apollo { + // TODO: This shortcut jumpstarts the migration from v2 to v3, but it is recommended to use settings idiomatic to v3 instead. + // See https://www.apollographql.com/docs/kotlin/migration/3.0/ + useVersion2Compat() + + mapScalarToKotlinString("URL") + mapScalar("LocalDate", "java.time.LocalDate") + mapScalarToUpload("Upload") + mapScalar("PaymentMethodsResponse", "com.adyen.checkout.components.model.PaymentMethodsApiResponse") + mapScalarToKotlinString("CheckoutPaymentsAction") + mapScalarToKotlinString("CheckoutPaymentAction") + mapScalar("JSONString", "org.json.JSONObject") + mapScalar("Instant", "java.time.Instant") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase.kt new file mode 100644 index 00000000000..f524498d12a --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase.kt @@ -0,0 +1,63 @@ +package com.example + +import com.apollographql.apollo.api.EnumValue + +enum class UserInterfaceStyle( + override val rawValue: String, +) : EnumValue { + LIGHT("Light"), + + DARK("Dark"), + + CORNFLOWERBLUE("CornflowerBlue"), + + + /** + * Auto generated constant for unknown enum values + */ + UNKNOWN__("UNKNOWN__"); + + companion object { + fun safeValueOf(rawValue: String): UserInterfaceStyle = + values().find { it.rawValue == rawValue } ?: UNKNOWN__ + } +} + +sealed class EmbarkExternalRedirectLocation( + override val rawValue: String, +) : EnumValue { + object MAILINGLIST : EmbarkExternalRedirectLocation(rawValue = "MailingList") + + object OFFER : EmbarkExternalRedirectLocation(rawValue = "Offer") + + object CLOSE : EmbarkExternalRedirectLocation(rawValue = "Close") + + object CHAT : EmbarkExternalRedirectLocation(rawValue = "Chat") + + /** + * Auto generated constant for unknown enum values + */ + class UNKNOWN__( + rawValue: String, + ) : EmbarkExternalRedirectLocation(rawValue = rawValue) + + companion object { + fun safeValueOf(rawValue: String): EmbarkExternalRedirectLocation = when (rawValue) { + "MailingList" -> MAILINGLIST + "Offer" -> OFFER + "Close" -> CLOSE + "Chat" -> CHAT + else -> UNKNOWN__(rawValue) + } + } +} + + +fun main() { + val enumValue1 = UserInterfaceStyle.LIGHT + val enumValue2 = UserInterfaceStyle.DARK + val enumValue3 = UserInterfaceStyle.CORNFLOWERBLUE + + val sealedValue1 = EmbarkExternalRedirectLocation.MAILINGLIST + val sealedValue2 = EmbarkExternalRedirectLocation.OFFER +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase_after.kt new file mode 100644 index 00000000000..3600265f0cc --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateEnumValueUpperCase_after.kt @@ -0,0 +1,63 @@ +package com.example + +import com.apollographql.apollo3.api.EnumValue + +enum class UserInterfaceStyle( + override val rawValue: String, +) : EnumValue { + LIGHT("Light"), + + DARK("Dark"), + + CORNFLOWERBLUE("CornflowerBlue"), + + + /** + * Auto generated constant for unknown enum values + */ + UNKNOWN__("UNKNOWN__"); + + companion object { + fun safeValueOf(rawValue: String): UserInterfaceStyle = + values().find { it.rawValue == rawValue } ?: UNKNOWN__ + } +} + +sealed class EmbarkExternalRedirectLocation( + override val rawValue: String, +) : EnumValue { + object MAILINGLIST : EmbarkExternalRedirectLocation(rawValue = "MailingList") + + object OFFER : EmbarkExternalRedirectLocation(rawValue = "Offer") + + object CLOSE : EmbarkExternalRedirectLocation(rawValue = "Close") + + object CHAT : EmbarkExternalRedirectLocation(rawValue = "Chat") + + /** + * Auto generated constant for unknown enum values + */ + class UNKNOWN__( + rawValue: String, + ) : EmbarkExternalRedirectLocation(rawValue = rawValue) + + companion object { + fun safeValueOf(rawValue: String): EmbarkExternalRedirectLocation = when (rawValue) { + "MailingList" -> MAILINGLIST + "Offer" -> OFFER + "Close" -> CLOSE + "Chat" -> CHAT + else -> UNKNOWN__(rawValue) + } + } +} + + +fun main() { + val enumValue1 = UserInterfaceStyle.Light + val enumValue2 = UserInterfaceStyle.Dark + val enumValue3 = UserInterfaceStyle.CornflowerBlue + + val sealedValue1 = EmbarkExternalRedirectLocation.MailingList + val sealedValue2 = EmbarkExternalRedirectLocation.Offer +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload.kt new file mode 100644 index 00000000000..7e68f48b32f --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload.kt @@ -0,0 +1,7 @@ +package com.example + +import com.apollographql.apollo.api.FileUpload + +suspend fun main() { + val fileUpload: FileUpload = FileUpload("text/plain", "/etc/passwd") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload_after.kt new file mode 100644 index 00000000000..362591a15cd --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateFileUpload_after.kt @@ -0,0 +1,9 @@ +package com.example + +import com.apollographql.apollo3.api.Upload +import java.io.File +import com.apollographql.apollo3.api.toUpload + +suspend fun main() { + val fileUpload: Upload = File("/etc/passwd").toUpload("text/plain") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts.gradle.kts new file mode 100644 index 00000000000..dace5260d1a --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts.gradle.kts @@ -0,0 +1,7 @@ +dependencies { + implementation("org.example:somelibrary:1.0.0") + implementation("com.apollographql.apollo:apollo-runtime:2.5.14") + implementation("com.apollographql.apollo:apollo-runtime:$apollo") + implementation("com.apollographql.apollo:apollo-runtime:${versions.apollo}") + implementation("com.apollographql.apollo:apollo-runtime") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts_after.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts_after.gradle.kts new file mode 100644 index 00000000000..2c00e496b6d --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInBuildGradleKts_after.gradle.kts @@ -0,0 +1,7 @@ +dependencies { + implementation("org.example:somelibrary:1.0.0") + implementation("com.apollographql.apollo3:apollo-runtime") + implementation("com.apollographql.apollo3:apollo-runtime") + implementation("com.apollographql.apollo3:apollo-runtime") + implementation("com.apollographql.apollo3:apollo-runtime") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml.versions.toml b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml.versions.toml new file mode 100644 index 00000000000..555f13f31c3 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml.versions.toml @@ -0,0 +1,17 @@ +[versions] +# gradlePlugin versions +androidBuildTools = "7.2.1" +apollo = "2.5.9" + +[libraries] +accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" } +apollo-runtime-short = "com.apollographql.apollo:apollo-runtime:2.5.9" +apollo-runtime-medium-version = { module = "com.apollographql.apollo:apollo-runtime", version = "2.5.9" } +apollo-runtime-medium-ref = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" } +apollo-runtime-long-version = { group = "com.apollographql.apollo", name = "apollo-runtime", version = "2.5.9" } +apollo-runtime-long-ref = { group = "com.apollographql.apollo", name = "apollo-runtime", version.ref = "apollo" } + +[plugins] +apollo-shortNotation = "com.apollographql.apollo:2.5.14" +apollo-longNotation = { id = "com.apollographql.apollo", version.ref = "apollo" } +apollo-referenceNotation = { id = "com.apollographql.apollo", version = "2.5.9" } diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml_after.versions.toml b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml_after.versions.toml new file mode 100644 index 00000000000..606109cdb03 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGradleDependenciesInLibsVersionsToml_after.versions.toml @@ -0,0 +1,17 @@ +[versions] +# gradlePlugin versions +androidBuildTools = "7.2.1" +apollo = "3.7.1" + +[libraries] +accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" } +apollo-runtime-short = "com.apollographql.apollo3:apollo-runtime:3.7.1" +apollo-runtime-medium-version = { module = "com.apollographql.apollo3:apollo-runtime", version = "3.7.1" } +apollo-runtime-medium-ref = { module = "com.apollographql.apollo3:apollo-runtime", version.ref = "apollo" } +apollo-runtime-long-version = { group = "com.apollographql.apollo3", name = "apollo-runtime", version = "3.7.1" } +apollo-runtime-long-ref = { group = "com.apollographql.apollo3", name = "apollo-runtime", version.ref = "apollo" } + +[plugins] +apollo-shortNotation = "com.apollographql.apollo3:3.7.1" +apollo-longNotation = { id = "com.apollographql.apollo3", version.ref = "apollo" } +apollo-referenceNotation = { id = "com.apollographql.apollo3", version = "3.7.1" } diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet.gradle.kts new file mode 100644 index 00000000000..73f1a0c9d18 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet.gradle.kts @@ -0,0 +1,5 @@ +apollo { + graphqlSourceDirectorySet.srcDirs += "shared/graphql" + graphqlSourceDirectorySet.include("**/*.graphql") + graphqlSourceDirectorySet.exclude("**/schema.graphql") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet_after.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet_after.gradle.kts new file mode 100644 index 00000000000..06d65fe7c50 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateGraphqlSourceDirectorySet_after.gradle.kts @@ -0,0 +1,9 @@ +apollo { + // TODO: This shortcut jumpstarts the migration from v2 to v3, but it is recommended to use settings idiomatic to v3 instead. + // See https://www.apollographql.com/docs/kotlin/migration/3.0/ + useVersion2Compat() + + srcDir("shared/graphql") + includes.add("**/*.graphql") + excludes.add("**/schema.graphql") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput.kt new file mode 100644 index 00000000000..49654890fab --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput.kt @@ -0,0 +1,9 @@ +package com.example + +import com.apollographql.apollo.api.Input + +suspend fun main() { + val present = Input.fromNullable("a") + val absent = Input.absent() + val optional = Input.optional("a") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput_after.kt new file mode 100644 index 00000000000..1589d60e33e --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateInput_after.kt @@ -0,0 +1,9 @@ +package com.example + +import com.apollographql.apollo3.api.Optional + +suspend fun main() { + val present = Optional.Present("a") + val absent = Optional.Absent + val optional = Optional.presentIfNotNull("a") +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName.kt new file mode 100644 index 00000000000..dc3337514e0 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName.kt @@ -0,0 +1,19 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.Mutation +import com.apollographql.apollo.api.Subscription +import com.apollographql.apollo.coroutines.await +import com.apollographql.apollo.coroutines.toFlow + +suspend fun main() { + val apolloClient = ApolloClient.builder() + .serverUrl("http://example.com") + .build() + + val myMutation: Mutation<*, *, *>? = null + apolloClient.mutate(myMutation!!).await() + + val mySubscription: Subscription<*, *, *>? = null + apolloClient.subscribe(mySubscription!!).toFlow() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName_after.kt new file mode 100644 index 00000000000..e04d2596f5e --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateMethodName_after.kt @@ -0,0 +1,17 @@ +package com.example + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.Mutation +import com.apollographql.apollo3.api.Subscription + +suspend fun main() { + val apolloClient = ApolloClient.Builder() + .serverUrl("http://example.com") + .build() + + val myMutation: Mutation<*, *, *>? = null + apolloClient.mutation(myMutation!!).execute() + + val mySubscription: Subscription<*, *, *>? = null + apolloClient.subscription(mySubscription!!).toFlow() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext.kt new file mode 100644 index 00000000000..4e69581bc00 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext.kt @@ -0,0 +1,10 @@ +package com.example + +import com.apollographql.apollo.api.Response +import com.apollographql.apollo.http.OkHttpExecutionContext + +suspend fun main() { + val response: Response? = null + val responseCode: Int? = response!!.executionContext[OkHttpExecutionContext]?.response?.code() + val headers = response.executionContext[OkHttpExecutionContext]?.response?.headers() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext_after.kt new file mode 100644 index 00000000000..2ee995418c4 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOkHttpExecutionContext_after.kt @@ -0,0 +1,10 @@ +package com.example + +import com.apollographql.apollo3.api.ApolloResponse +import com.apollographql.apollo3.network.http.HttpInfo + +suspend fun main() { + val response: ApolloResponse? = null + val responseCode: Int? = response!!.executionContext[HttpInfo]?.statusCode + val headers = response.executionContext[HttpInfo]?.headers +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName.kt new file mode 100644 index 00000000000..6fe5244f393 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName.kt @@ -0,0 +1,10 @@ +package com.example + +import com.apollographql.apollo.api.OperationName + +suspend fun main() { + val OPERATION_NAME: OperationName = object : OperationName { + override fun name(): String = "MyMutation1" + } + println(OPERATION_NAME.name()) +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName_after.kt new file mode 100644 index 00000000000..f69a1ff7f0a --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updateOperationName_after.kt @@ -0,0 +1,10 @@ +package com.example + +import com.apollographql.apollo3.api.OperationName + +suspend fun main() { + val OPERATION_NAME: OperationName = object : OperationName { + override fun name(): String = "MyMutation1" + } + println(OPERATION_NAME) +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName.kt new file mode 100644 index 00000000000..336fc302d66 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName.kt @@ -0,0 +1,9 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.ApolloCall + +suspend fun main() { + val apolloClient: ApolloClient? = null + val call: ApolloCall? = null +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName_after.kt new file mode 100644 index 00000000000..5f9e71e2533 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/updatePackageName_after.kt @@ -0,0 +1,9 @@ +package com.example + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.ApolloCall + +suspend fun main() { + val apolloClient: ApolloClient? = null + val call: ApolloCall? = null +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts.gradle.kts new file mode 100644 index 00000000000..d6fc2b4b3cf --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts.gradle.kts @@ -0,0 +1,14 @@ +plugins { + java + kotlin("jvm") version "1.6.10" + id("com.apollographql.apollo") + id("com.apollographql.apollo").version("2.5.14") + id("com.apollographql.apollo").version("2.5.14").apply(false) + id("com.apollographql.apollo").version("2.5.14") apply (false) + id("com.apollographql.apollo").version(someClass.someConstant) + id("com.apollographql.apollo").version("${someClass.someConstant}") + id("com.apollographql.apollo") version "2.5.14" + id("com.apollographql.apollo") version "2.5.14" apply false + id("com.apollographql.apollo") version someClass.someConstant + id("com.apollographql.apollo") version "${someClass.someConstant}" +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts_after.gradle.kts b/intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts_after.gradle.kts new file mode 100644 index 00000000000..35432d87a53 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/upgradeGradlePluginInBuildGradleKts_after.gradle.kts @@ -0,0 +1,18 @@ +plugins { + java + kotlin("jvm") version "1.6.10" + id("com.apollographql.apollo3") + id("com.apollographql.apollo3").version("3.7.1") + id("com.apollographql.apollo3").version("3.7.1").apply(false) + id("com.apollographql.apollo3").version("3.7.1") apply (false) + // TODO: Update version to 3.7.1 + id("com.apollographql.apollo3").version(someClass.someConstant) + // TODO: Update version to 3.7.1 + id("com.apollographql.apollo3").version("${someClass.someConstant}") + id("com.apollographql.apollo3") version "3.7.1" + id("com.apollographql.apollo3") version "3.7.1" apply false + // TODO: Update version to 3.7.1 + id("com.apollographql.apollo3") version someClass.someConstant + // TODO: Update version to 3.7.1 + id("com.apollographql.apollo3") version "${someClass.someConstant}" +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/watch.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/watch.kt new file mode 100644 index 00000000000..8fd6b1fadca --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/watch.kt @@ -0,0 +1,13 @@ +package com.example + +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.Query +import com.apollographql.apollo.coroutines.toFlow + +suspend fun main() { + val apolloClient: ApolloClient? = null + val myQuery: Query<*, *, *>? = null + val flow = apolloClient!!.query(myQuery!!) + .watcher() + .toFlow() +} diff --git a/intellij-plugin/src/test/testData/migration/v2-to-v3/watch_after.kt b/intellij-plugin/src/test/testData/migration/v2-to-v3/watch_after.kt new file mode 100644 index 00000000000..330e83a9ed8 --- /dev/null +++ b/intellij-plugin/src/test/testData/migration/v2-to-v3/watch_after.kt @@ -0,0 +1,12 @@ +package com.example + +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.Query +import com.apollographql.apollo3.cache.normalized.watch + +suspend fun main() { + val apolloClient: ApolloClient? = null + val myQuery: Query<*, *, *>? = null + val flow = apolloClient!!.query(myQuery!!) + .watch() +} diff --git a/settings.gradle.kts b/settings.gradle.kts index d31c2af2a8a..c1bb482023d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,8 @@ rootProject.projectDir project(":${it.name}").projectDir = it } +include(":intellij-plugin") + pluginManagement { includeBuild("build-logic") }