File tree Expand file tree Collapse file tree
equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal
equalsverifier-test-kotlin-noreflect/src/test/kotlin/nl/jqno/equalsverifier/kotlin
equalsverifier-test-kotlin/src/test/kotlin/nl/jqno/equalsverifier/kotlin Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ java temurin-25.0.1+8.0.LTS
Original file line number Diff line number Diff line change @@ -15,6 +15,20 @@ EqualsVerifier.forClass(Foo::class.java)
1515
1616For most simple classes and data classes, that's enough.
1717
18+ ## Data classes
19+
20+ Since data class equality is based on primary constructor parameters, EqualsVerifier ignores other properties in such classes.
21+
22+ For instance, in following code, ` isEmpty ` property is ignored:
23+
24+ ``` kotlin
25+ data class MyDataclass (val value : String ) {
26+ val isEmpty = value.isEmpty()
27+ }
28+ ```
29+
30+ This behavior requires ` org.jetbrains.kotlin:kotlin-reflect ` library to be available, otherwise data classes are treated like any other class.
31+
1832## Delegates
1933
2034Kotlin supports various forms of delegate fields. In a Kotlin class definition, delegates may look like this:
Original file line number Diff line number Diff line change 1111import nl .jqno .equalsverifier .internal .reflection .FieldProbe ;
1212import nl .jqno .equalsverifier .internal .reflection .annotations .AnnotationCache ;
1313import nl .jqno .equalsverifier .internal .reflection .annotations .SupportedAnnotations ;
14- import nl .jqno .equalsverifier .internal .util .*;
14+ import nl .jqno .equalsverifier .internal .reflection .kotlin .KotlinScreen ;
15+ import nl .jqno .equalsverifier .internal .util .CachedHashCodeInitializer ;
16+ import nl .jqno .equalsverifier .internal .util .Configuration ;
17+ import nl .jqno .equalsverifier .internal .util .Context ;
18+ import nl .jqno .equalsverifier .internal .util .Formatter ;
19+ import nl .jqno .equalsverifier .internal .util .PrimitiveMappers ;
1520import nl .jqno .equalsverifier .internal .valueproviders .SubjectCreator ;
1621
1722public class SignificantFieldCheck <T > implements FieldCheck <T > {
@@ -189,6 +194,13 @@ else if (anotherFieldIsMarkedAsId) {
189194 Significant fields: equals does not use %%, or it is stateless.
190195 Suppress Warning.SURROGATE_KEY if you want to use only the @Id or @EmbeddedId field(s).""" ;
191196 }
197+ else if (KotlinScreen .isKotlin (type )) {
198+ message =
199+ """
200+ Significant fields: equals does not use %%, or it is stateless.
201+ Note: This is a Kotlin class. Import kotlin reflect in the classpath to use the adapted equalsverifier implementation.
202+ """ ;
203+ }
192204 else {
193205 message = "Significant fields: equals does not use %%, or it is stateless." ;
194206 }
Original file line number Diff line number Diff line change @@ -112,4 +112,19 @@ private static void assertHasKotlinReflect(Field f) {
112112 Assert .fail (Formatter .of (msg ));
113113 }
114114 }
115+
116+ public static boolean isDeclaredInPrimaryConstructor (Field field ) {
117+ Class <?> declaringClass = field .getDeclaringClass ();
118+ KClass <?> kType = JvmClassMappingKt .getKotlinClass (declaringClass );
119+ if (kType .getConstructors ().isEmpty ()) {
120+ return false ;
121+ }
122+
123+ KFunction <?> constructor = KClasses .getPrimaryConstructor (kType );
124+ return constructor .getParameters ().stream ().anyMatch (p -> p .getName ().equals (field .getName ()));
125+ }
126+
127+ public static <T > boolean isDataClass (Class <T > type ) {
128+ return JvmClassMappingKt .getKotlinClass (type ).isData ();
129+ }
115130}
Original file line number Diff line number Diff line change 1313import nl .jqno .equalsverifier .internal .reflection .FieldProbe ;
1414import nl .jqno .equalsverifier .internal .reflection .TypeTag ;
1515import nl .jqno .equalsverifier .internal .reflection .annotations .*;
16+ import nl .jqno .equalsverifier .internal .reflection .kotlin .KotlinProbe ;
1617import nl .jqno .equalsverifier .internal .reflection .kotlin .KotlinScreen ;
1718
1819// CHECKSTYLE OFF: ParameterNumber
@@ -55,6 +56,11 @@ public static <T> Configuration<T> build(
5556
5657 if (isKotlin ) {
5758 for (FieldProbe f : FieldIterable .ofKotlin (type )) {
59+ if (KotlinScreen .canProbe ()
60+ && KotlinProbe .isDataClass (type )
61+ && !KotlinProbe .isDeclaredInPrimaryConstructor (f .getField ())) {
62+ ignoredFields .add (f .getName ());
63+ }
5864 if (KotlinScreen .isSyntheticKotlinDelegate (f .getField ())) {
5965 nonnullFields .add (f .getName ());
6066 }
Original file line number Diff line number Diff line change 1+ package nl.jqno.equalsverifier.kotlin
2+
3+ import nl.jqno.equalsverifier.EqualsVerifier
4+ import org.assertj.core.api.Assertions.assertThatThrownBy
5+ import org.junit.jupiter.api.Test
6+
7+ class KotlinDataClassTest {
8+
9+ data class DataClassWithComputedProperty (val value : String ) {
10+ val isEmpty = value.isEmpty()
11+ }
12+
13+ @Test
14+ fun `properties not declared in primary constructor of data classes are ignored` () {
15+ assertThatThrownBy {
16+ EqualsVerifier .forClass(DataClassWithComputedProperty ::class .java)
17+ .verify()
18+ }.isInstanceOf(AssertionError ::class .java)
19+ .hasMessageContaining(" Significant fields: equals does not use isEmpty, or it is stateless." )
20+ .hasMessageContaining(" Note: This is a Kotlin class. Import kotlin reflect in the classpath to use the adapted equalsverifier implementation." )
21+ }
22+
23+ data class DataClassWithSecondaryConstructor (val value : String ) {
24+ constructor (prefix: String , isEmpty: Boolean ) : this (prefix)
25+ val isEmpty = value.isEmpty()
26+ }
27+
28+ @Test
29+ fun `properties declared in secondary constructor of data classes are ignored` () {
30+ assertThatThrownBy {
31+ EqualsVerifier .forClass(DataClassWithSecondaryConstructor ::class .java)
32+ .verify()
33+ }.isInstanceOf(AssertionError ::class .java)
34+ .hasMessageContaining(" Significant fields: equals does not use isEmpty, or it is stateless." )
35+ .hasMessageContaining(" Note: This is a Kotlin class. Import kotlin reflect in the classpath to use the adapted equalsverifier implementation." )
36+ }
37+ }
Original file line number Diff line number Diff line change 1+ package nl.jqno.equalsverifier.kotlin
2+
3+ import nl.jqno.equalsverifier.EqualsVerifier
4+ import org.junit.jupiter.api.Test
5+
6+ class KotlinDataClassTest {
7+
8+ data class DataClassWithComputedProperty (val value : String ) {
9+ val isEmpty = value.isEmpty()
10+ }
11+
12+ @Test
13+ fun `properties not declared in primary constructor of data classes are ignored` () {
14+ EqualsVerifier .forClass(DataClassWithComputedProperty ::class .java)
15+ .verify()
16+ }
17+
18+ data class DataClassWithSecondaryConstructor (val value : String ) {
19+ constructor (prefix: String , isEmpty: Boolean ) : this (prefix)
20+ val isEmpty = value.isEmpty()
21+ }
22+
23+ @Test
24+ fun `properties declared in secondary constructor of data classes are ignored` () {
25+ EqualsVerifier .forClass(DataClassWithSecondaryConstructor ::class .java)
26+ .verify()
27+ }
28+ }
You can’t perform that action at this time.
0 commit comments