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'
This commit is contained in:
@@ -5,5 +5,6 @@
|
||||
<jsSyntheticTranslateExtension implementation="org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIDEJsExtension"/>
|
||||
<irGenerationExtension implementation="org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIDEIrExtension"/>
|
||||
<storageComponentContainerContributor implementation="org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIDEContainerContributor"/>
|
||||
<quickFixContributor implementation="org.jetbrains.kotlinx.serialization.idea.quickfixes.SerializationQuickFixContributor"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
+3
-1
@@ -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<PsiElement> TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
DiagnosticFactory0<PsiElement> INCORRECT_TRANSIENT = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
Object _initializer = new Object() {
|
||||
{
|
||||
|
||||
+22
-1
@@ -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<SerializableProperty>,
|
||||
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
|
||||
|
||||
+4
@@ -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."
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+13
-1
@@ -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");
|
||||
|
||||
+12
@@ -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, <!INCORRECT_TRANSIENT!>@Transient<!> val y: String)
|
||||
|
||||
@Serializable
|
||||
class Data2(val x: Int, @Transient val y: String) : JavaSerializable
|
||||
+20
@@ -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, <!INCORRECT_TRANSIENT!>@JavaTransient<!> val y: String)
|
||||
@@ -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) {
|
||||
|
||||
|
||||
}
|
||||
+42
@@ -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<PsiElement>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user