From 340deb01ddc74c61cd5b1131c3891b6ee1b3bd73 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Fri, 3 Jul 2020 18:30:07 +0300 Subject: [PATCH] Add 'Incorrect Transient' diagnostic when users write @Transient, they get @kotlin.jvm.Transient, because it's always in auto-import. This annotation does not work with @Serializable classes; @kotlinx.serialization.Transient should be used instead. Add quickfix for 'incorrect transient' diagnostic: 'Import kotlinx.serialization.Transient' --- .../META-INF/kotlinx-serialization.xml | 1 + .../diagnostic/SerializationErrors.java | 4 +- .../SerializationPluginDeclarationChecker.kt | 23 +++++++++- .../SerializationPluginErrorsRendering.kt | 4 ++ .../compiler/resolve/NamingConventions.kt | 2 +- ...lizationPluginDiagnosticTestGenerated.java | 14 ++++++- .../diagnostics/IncorrectTransient.kt | 12 ++++++ .../diagnostics/IncorrectTransient2.kt | 20 +++++++++ .../kotlin-serialization-ide/build.gradle.kts | 5 ++- .../quickfixes/IncorrectTransientQuickFix.kt | 42 +++++++++++++++++++ .../SerializationQuickFixContributor.kt | 16 +++++++ 11 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient.kt create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient2.kt create mode 100644 plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/IncorrectTransientQuickFix.kt create mode 100644 plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/SerializationQuickFixContributor.kt diff --git a/idea/resources/META-INF/kotlinx-serialization.xml b/idea/resources/META-INF/kotlinx-serialization.xml index 4ad16af7a6a..3e2a22229ab 100644 --- a/idea/resources/META-INF/kotlinx-serialization.xml +++ b/idea/resources/META-INF/kotlinx-serialization.xml @@ -5,5 +5,6 @@ + diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java index 7a0d339e30e..5616ece19ec 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -32,6 +32,8 @@ public interface SerializationErrors { DiagnosticFactory0 TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 INCORRECT_TRANSIENT = DiagnosticFactory0.create(WARNING); + @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { { diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt index 5f44776c2dc..2729b12a3fb 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -14,10 +14,14 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces import org.jetbrains.kotlin.resolve.hasBackingField import org.jetbrains.kotlin.resolve.isInlineClassType +import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyAnnotationDescriptor +import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.util.slicedMap.Slices @@ -37,10 +41,27 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { if (!canBeSerializedInternally(descriptor, declaration, context.trace)) return if (declaration !is KtPureClassOrObject) return val props = buildSerializableProperties(descriptor, context.trace) ?: return + checkCorrectTransientAnnotationIsUsed(descriptor, props.serializableProperties, context.trace) checkTransients(declaration, context.trace) analyzePropertiesSerializers(context.trace, descriptor, props.serializableProperties) } + private fun checkCorrectTransientAnnotationIsUsed( + descriptor: ClassDescriptor, + properties: List, + trace: BindingTrace + ) { + if (descriptor.getSuperInterfaces().any { it.fqNameSafe.asString() == "java.io.Serializable" }) return // do not check + for (prop in properties) { + if (prop.transient) continue // correct annotation is used + val incorrectTransient = prop.descriptor.backingField?.annotations?.findAnnotation(TRANSIENT_ANNOTATION_FQ_NAME) + if (incorrectTransient != null) { + val elementToReport = incorrectTransient.source.getPsi() ?: prop.descriptor.findPsi() ?: continue + trace.report(SerializationErrors.INCORRECT_TRANSIENT.on(elementToReport)) + } + } + } + private fun canBeSerializedInternally(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace): Boolean { if (descriptor.isSerializableEnumWithMissingSerializer()) { val declarationToReport = declaration.modifierList ?: declaration diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt index f541b3ec908..f071e10e63f 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt @@ -65,5 +65,9 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { SerializationErrors.TRANSIENT_IS_REDUNDANT, "Property does not have backing field which makes it non-serializable and therefore @Transient is redundant" ) + MAP.put( + SerializationErrors.INCORRECT_TRANSIENT, + "@kotlin.jvm.Transient does not affect @Serializable classes. Please use @kotlinx.serialization.Transient instead." + ) } } \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt index cac1db59996..9a3e2e8b3d8 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt @@ -23,7 +23,7 @@ object SerializationAnnotations { val serializerAnnotationFqName = FqName("kotlinx.serialization.Serializer") internal val serialNameAnnotationFqName = FqName("kotlinx.serialization.SerialName") internal val requiredAnnotationFqName = FqName("kotlinx.serialization.Required") - internal val serialTransientFqName = FqName("kotlinx.serialization.Transient") + val serialTransientFqName = FqName("kotlinx.serialization.Transient") internal val serialInfoFqName = FqName("kotlinx.serialization.SerialInfo") internal val contextualFqName = FqName("kotlinx.serialization.ContextualSerialization") // this one is deprecated diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java index 2a07c127c73..04f8eacec97 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java @@ -25,7 +25,9 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ } public void testAllFilesPresentInDiagnostics() throws Exception { - KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), null, true); + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File( + "plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), null, + true); } @TestMetadata("DuplicateSerialName.kt") @@ -33,6 +35,16 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/DuplicateSerialName.kt"); } + @TestMetadata("IncorrectTransient.kt") + public void testIncorrectTransient() throws Exception { + runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient.kt"); + } + + @TestMetadata("IncorrectTransient2.kt") + public void testIncorrectTransient2() throws Exception { + runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient2.kt"); + } + @TestMetadata("LazyRecursionBug.kt") public void testLazyRecursionBug() throws Exception { runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/LazyRecursionBug.kt"); diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient.kt new file mode 100644 index 00000000000..b1eef1936d2 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient.kt @@ -0,0 +1,12 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE +// WITH_RUNTIME +// SKIP_TXT + +import kotlinx.serialization.Serializable +import java.io.Serializable as JavaSerializable + +@Serializable +class Data(val x: Int, @Transient val y: String) + +@Serializable +class Data2(val x: Int, @Transient val y: String) : JavaSerializable \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient2.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient2.kt new file mode 100644 index 00000000000..8c1e812a131 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient2.kt @@ -0,0 +1,20 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE +// WITH_RUNTIME +// SKIP_TXT + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import java.io.Serializable as JavaSerializable +import kotlin.jvm.Transient as JavaTransient + +@Serializable +class Data(val x: Int, @Transient val y: String = "a") + +@Serializable +class Data2(val x: Int, @Transient val y: String = "a") : JavaSerializable + +@Serializable +class Data3(val x: Int, @Transient @JavaTransient val y: String = "a") : JavaSerializable + +@Serializable +class Data4(val x: Int, @JavaTransient val y: String) \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-ide/build.gradle.kts b/plugins/kotlin-serialization/kotlin-serialization-ide/build.gradle.kts index 80119173823..2ef3944b795 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-ide/build.gradle.kts +++ b/plugins/kotlin-serialization/kotlin-serialization-ide/build.gradle.kts @@ -15,6 +15,9 @@ dependencies { compile(project(":idea:idea-maven")) compile(project(":plugins:annotation-based-compiler-plugins-ide-support")) compileOnly(intellijDep()) + Platform[192].orHigher { + compileOnly(intellijPluginDep("java")) + } excludeInAndroidStudio(rootProject) { compileOnly(intellijPluginDep("maven")) } compileOnly(intellijPluginDep("gradle")) @@ -30,5 +33,5 @@ sourceSets { runtimeJar() projectTest(parallel = true) { - + } \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/IncorrectTransientQuickFix.kt b/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/IncorrectTransientQuickFix.kt new file mode 100644 index 00000000000..cca28360931 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/IncorrectTransientQuickFix.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.serialization.idea.quickfixes + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction +import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory +import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SerializationErrors +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations + +internal class AddKotlinxSerializationTransientImportQuickFix(expression: PsiElement) : + KotlinQuickFixAction(expression) { + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + // Do not use Helper itself because it does not insert import on conflict; + // so it will always fail because k.jvm.Transient is always in auto-import + ImportInsertHelperImpl.addImport(project, file, SerializationAnnotations.serialTransientFqName, allUnder = false) + } + + override fun getFamilyName(): String = text + + override fun getText(): String = "Import ${SerializationAnnotations.serialTransientFqName}" + + object Factory : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + if (diagnostic.factory != SerializationErrors.INCORRECT_TRANSIENT) return null + val castedDiagnostic = SerializationErrors.INCORRECT_TRANSIENT.cast(diagnostic) + + val element = castedDiagnostic.psiElement + + return AddKotlinxSerializationTransientImportQuickFix(element) + } + } +} \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/SerializationQuickFixContributor.kt b/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/SerializationQuickFixContributor.kt new file mode 100644 index 00000000000..df870ab45ba --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/idea/quickfixes/SerializationQuickFixContributor.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.serialization.idea.quickfixes + +import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor +import org.jetbrains.kotlin.idea.quickfix.QuickFixes +import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SerializationErrors + +class SerializationQuickFixContributor : QuickFixContributor { + override fun registerQuickFixes(quickFixes: QuickFixes) { + quickFixes.register(SerializationErrors.INCORRECT_TRANSIENT, AddKotlinxSerializationTransientImportQuickFix.Factory) + } +} \ No newline at end of file