From 06e377b9e14014d1307cee6ae0dc5561e2cd7109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Wed, 30 Jul 2025 00:59:25 +0900 Subject: [PATCH 1/5] test: add failing test for unknown property in @Ignored --- .../ap/test/bugs/_3902/Issue3902Mapper.java | 69 +++++++++++++++++++ .../ap/test/bugs/_3902/Issue3902Test.java | 57 +++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Mapper.java new file mode 100644 index 0000000000..6f78d6f643 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Mapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3902; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing bug #3902. + * + * @author znight1020 + */ +@Mapper +public interface Issue3902Mapper { + + Issue3902Mapper INSTANCE = Mappers.getMapper( Issue3902Mapper.class ); + + @Ignored(targets = {"name", "foo", "bar"}) + ZooDto mapWithOneKnownAndMultipleUnknowns(Zoo source); + + @Ignored(targets = {"name", "address", "foo"}) + ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + + class Zoo { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + class ZooDto { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java new file mode 100644 index 0000000000..ccdfabcc1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -0,0 +1,57 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3902; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * Verifies that using an unknown property in {@code @Ignored} yields a proper + * compile error instead of an internal processor error. + * + * @author znight1020 + */ +@WithClasses({Issue3902Mapper.class}) +@IssueKey("3902") +public class Issue3902Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + // Test case: mapWithOneKnownAndMultipleUnknowns + @Diagnostic( + type = Issue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "Unknown property \"foo\" in @Ignored for target type \"Issue3902Mapper.ZooDto\"." + + " No property named \"foo\" exists in the source or target." + ), + @Diagnostic( + type = Issue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "Unknown property \"bar\" in @Ignored for target type \"Issue3902Mapper.ZooDto\"." + + " No property named \"bar\" exists in the source or target." + ), + + // Test case: mapWithMultipleKnownAndOneUnknown + @Diagnostic( + type = Issue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 26, + message = "Unknown property \"foo\" in @Ignored for target type \"Issue3902Mapper.ZooDto\"." + + " No property named \"foo\" exists in the source or target." + ) + } + ) + public void shouldFailOnUnknownPropertiesInIgnored() { + } +} From 9965bcc488466fc9d5a619afaf63e35467d607b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Wed, 30 Jul 2025 01:00:20 +0900 Subject: [PATCH 2/5] fix: validate unknown properties in @Ignored --- .../ap/internal/model/BeanMappingMethod.java | 59 ++++++++++++------- .../mapstruct/ap/internal/util/Message.java | 1 + 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index b5208d302b..557cfdd273 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1280,40 +1280,55 @@ else if ( inheritContext.isReversed() ) { return false; } } - Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); Message msg; String[] args; - if ( targetRef.getPathProperties().isEmpty() ) { - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + Element elementForMessage = mapping.getElement(); + if ( elementForMessage == null ) { + elementForMessage = method.getExecutable(); + } + + if ( mapping.isIgnored() && mapping.getElement() == null ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, resultTypeToMap.describe(), - mostSimilarProperty + targetPropertyName }; } else { - List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); - pathProperties.add( mostSimilarProperty ); - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; - args = new String[] { - targetPropertyName, - resultTypeToMap.describe(), - mapping.getTargetName(), - Strings.join( pathProperties, "." ) - }; + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + if ( targetRef.getPathProperties().isEmpty() ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mostSimilarProperty + }; + } + else { + List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); + pathProperties.add( mostSimilarProperty ); + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mapping.getTargetName(), + Strings.join( pathProperties, "." ) + }; + } } - ctx.getMessager() - .printMessage( - mapping.getElement(), - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - msg, - args - ); + ctx.getMessager().printMessage( + elementForMessage, + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args + ); return true; } else if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 883b3e3792..8c18133db4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -45,6 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("Unknown property \"%s\" in @Ignored for target type \"%s\". No property named \"%s\" exists in the source or target."), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), From 225c268d54c0e7f28c0ac663f914765c8713c748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Wed, 30 Jul 2025 01:02:59 +0900 Subject: [PATCH 3/5] build: exclude issue #3902 from full feature test of integration test --- .../itest/tests/FullFeatureCompilationExclusionCliEnhancer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 0cc1a87818..92a771ec3c 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -27,6 +27,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3089/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3902/*.java" ); switch ( currentJreVersion ) { case JAVA_8: From 6899c046335491062295068392f2afdfa137dbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Wed, 30 Jul 2025 07:01:05 +0900 Subject: [PATCH 4/5] refactor: improve error message for unknown property in @Ignored --- .../ap/internal/model/BeanMappingMethod.java | 3 +-- .../java/org/mapstruct/ap/internal/util/Message.java | 2 +- .../mapstruct/ap/test/bugs/_3902/Issue3902Test.java | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 557cfdd273..c81fc51800 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1293,8 +1293,7 @@ else if ( inheritContext.isReversed() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, - resultTypeToMap.describe(), - targetPropertyName + resultTypeToMap.describe() }; } else { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 8c18133db4..8534758285 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -45,7 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), - BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("Unknown property \"%s\" in @Ignored for target type \"%s\". No property named \"%s\" exists in the source or target."), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\""), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java index ccdfabcc1c..3d759adc51 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -31,15 +31,15 @@ public class Issue3902Test { type = Issue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, - message = "Unknown property \"foo\" in @Ignored for target type \"Issue3902Mapper.ZooDto\"." - + " No property named \"foo\" exists in the source or target." + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"Issue3902Mapper.ZooDto\"" ), @Diagnostic( type = Issue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, - message = "Unknown property \"bar\" in @Ignored for target type \"Issue3902Mapper.ZooDto\"." - + " No property named \"bar\" exists in the source or target." + message = "No property named \"bar\" exists in @Ignored for target type " + + "\"Issue3902Mapper.ZooDto\"" ), // Test case: mapWithMultipleKnownAndOneUnknown @@ -47,8 +47,8 @@ public class Issue3902Test { type = Issue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 26, - message = "Unknown property \"foo\" in @Ignored for target type \"Issue3902Mapper.ZooDto\"." - + " No property named \"foo\" exists in the source or target." + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"Issue3902Mapper.ZooDto\"" ) } ) From 192a17a904276ab3c6692dd34af61f8b5a67106d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Wed, 30 Jul 2025 16:20:23 +0900 Subject: [PATCH 5/5] refactor: rename issue mapper --- ...FullFeatureCompilationExclusionCliEnhancer.java | 1 - ...02Mapper.java => ErroneousIssue3902Mapper.java} | 4 ++-- .../ap/test/bugs/_3902/Issue3902Test.java | 14 +++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/{Issue3902Mapper.java => ErroneousIssue3902Mapper.java} (91%) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 92a771ec3c..0cc1a87818 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -27,7 +27,6 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3089/*.java" ); - additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3902/*.java" ); switch ( currentJreVersion ) { case JAVA_8: diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java similarity index 91% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Mapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java index 6f78d6f643..07f94ce6fe 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -15,9 +15,9 @@ * @author znight1020 */ @Mapper -public interface Issue3902Mapper { +public interface ErroneousIssue3902Mapper { - Issue3902Mapper INSTANCE = Mappers.getMapper( Issue3902Mapper.class ); + ErroneousIssue3902Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3902Mapper.class ); @Ignored(targets = {"name", "foo", "bar"}) ZooDto mapWithOneKnownAndMultipleUnknowns(Zoo source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java index 3d759adc51..f1332bbdc6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -18,7 +18,7 @@ * * @author znight1020 */ -@WithClasses({Issue3902Mapper.class}) +@WithClasses({ErroneousIssue3902Mapper.class}) @IssueKey("3902") public class Issue3902Test { @@ -28,27 +28,27 @@ public class Issue3902Test { diagnostics = { // Test case: mapWithOneKnownAndMultipleUnknowns @Diagnostic( - type = Issue3902Mapper.class, + type = ErroneousIssue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"Issue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\"" ), @Diagnostic( - type = Issue3902Mapper.class, + type = ErroneousIssue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"bar\" exists in @Ignored for target type " + - "\"Issue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\"" ), // Test case: mapWithMultipleKnownAndOneUnknown @Diagnostic( - type = Issue3902Mapper.class, + type = ErroneousIssue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 26, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"Issue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\"" ) } )