Skip to content

Commit fe43563

Browse files
mapstruct#3837 Add warning/error for redundant ignoreUnmappedSourceProperties entries (mapstruct#3906)
Adds compiler warning / error when properties listed in `ignoreUnmappedSourceProperties` are actually mapped. Respects `unmappedSourcePolicy` and includes tests for redundant and valid ignore cases.
1 parent 2903654 commit fe43563

16 files changed

+485
-1
lines changed

processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public static class Builder extends AbstractMappingMethodBuilder<Builder, BeanMa
114114
private Map<String, Accessor> unprocessedTargetProperties;
115115
private Map<String, Accessor> unprocessedSourceProperties;
116116
private Set<String> missingIgnoredSourceProperties;
117+
private Set<String> redundantIgnoredSourceProperties;
117118
private Set<String> targetProperties;
118119
private final List<PropertyMapping> propertyMappings = new ArrayList<>();
119120
private final Set<Parameter> unprocessedSourceParameters = new HashSet<>();
@@ -278,11 +279,23 @@ else if ( !method.isUpdateMethod() ) {
278279

279280
// get bean mapping (when specified as annotation )
280281
this.missingIgnoredSourceProperties = new HashSet<>();
281-
if ( beanMapping != null ) {
282+
this.redundantIgnoredSourceProperties = new HashSet<>();
283+
if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) {
284+
// Get source properties explicitly mapped using @Mapping annotations
285+
Set<String> mappedSourceProperties = method.getOptions().getMappings().stream()
286+
.map( MappingOptions::getSourceName )
287+
.filter( Objects::nonNull )
288+
.collect( Collectors.toSet() );
289+
282290
for ( String ignoreUnmapped : beanMapping.getIgnoreUnmappedSourceProperties() ) {
291+
// Track missing ignored properties (i.e. not in source class)
283292
if ( unprocessedSourceProperties.remove( ignoreUnmapped ) == null ) {
284293
missingIgnoredSourceProperties.add( ignoreUnmapped );
285294
}
295+
// Track redundant ignored properties (actually mapped despite being ignored)
296+
if ( mappedSourceProperties.contains( ignoreUnmapped ) ) {
297+
redundantIgnoredSourceProperties.add( ignoreUnmapped );
298+
}
286299
}
287300
}
288301

@@ -331,6 +344,7 @@ else if ( !method.isUpdateMethod() ) {
331344
}
332345
reportErrorForMissingIgnoredSourceProperties();
333346
reportErrorForUnusedSourceParameters();
347+
reportErrorForRedundantIgnoredSourceProperties();
334348

335349
// mapNullToDefault
336350
boolean mapNullToDefault = method.getOptions()
@@ -1914,6 +1928,34 @@ private void reportErrorForMissingIgnoredSourceProperties() {
19141928
}
19151929
}
19161930

1931+
private void reportErrorForRedundantIgnoredSourceProperties() {
1932+
if ( !redundantIgnoredSourceProperties.isEmpty() ) {
1933+
ReportingPolicyGem unmappedSourcePolicy = getUnmappedSourcePolicy();
1934+
if ( unmappedSourcePolicy == ReportingPolicyGem.IGNORE ) { //don't show warning
1935+
return;
1936+
}
1937+
1938+
Message message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_WARNING;
1939+
if ( unmappedSourcePolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ) {
1940+
message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_ERROR;
1941+
}
1942+
1943+
Object[] args = new Object[] {
1944+
MessageFormat.format(
1945+
"{0,choice,1#property|1<properties} \"{1}\" {0,choice,1#is|1<are}",
1946+
redundantIgnoredSourceProperties.size(),
1947+
Strings.join( redundantIgnoredSourceProperties, ", " )
1948+
)
1949+
};
1950+
1951+
ctx.getMessager().printMessage(
1952+
method.getExecutable(),
1953+
message,
1954+
args
1955+
);
1956+
}
1957+
}
1958+
19171959
private void reportErrorForUnusedSourceParameters() {
19181960
for ( Parameter sourceParameter : unprocessedSourceParameters ) {
19191961
Type parameterType = sourceParameter.getType();

processor/src/main/java/org/mapstruct/ap/internal/util/Message.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public enum Message {
4242
BEANMAPPING_UNMAPPED_FORGED_SOURCES_WARNING( "Unmapped source %s. Mapping from %s to %s.", Diagnostic.Kind.WARNING ),
4343
BEANMAPPING_UNMAPPED_FORGED_SOURCES_ERROR( "Unmapped source %s. Mapping from %s to %s." ),
4444
BEANMAPPING_MISSING_IGNORED_SOURCES_ERROR( "Ignored unknown source %s." ),
45+
BEANMAPPING_REDUNDANT_IGNORED_SOURCES_ERROR( "Source %s mapped despite being listed in ignoreUnmappedSourceProperties." ),
46+
BEANMAPPING_REDUNDANT_IGNORED_SOURCES_WARNING( "Source %s mapped despite being listed in ignoreUnmappedSourceProperties.", Diagnostic.Kind.WARNING ),
4547
BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ),
4648
BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ),
4749
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." ),
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.ignoreunmapped;
7+
8+
import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapper;
9+
import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithWarnSourcePolicy;
10+
import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithWarnPolicyInMapperConfig;
11+
import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithWarnSourcePolicyInMapper;
12+
import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithoutBeanMapping;
13+
import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithIgnorePolicyInMapperConfig;
14+
import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithIgnoreSourcePolicy;
15+
import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithErrorPolicyInMapperConfig;
16+
import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithErrorSourcePolicy;
17+
import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithErrorSourcePolicyInMapper;
18+
import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithMultiMapping;
19+
import org.mapstruct.ap.testutil.IssueKey;
20+
import org.mapstruct.ap.testutil.ProcessorTest;
21+
import org.mapstruct.ap.testutil.WithClasses;
22+
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
23+
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
24+
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
25+
26+
import javax.tools.Diagnostic.Kind;
27+
28+
/**
29+
* Verifies that mapped properties listed in ignoreUnmappedSourceProperties trigger a warning.
30+
*
31+
* @author Ritesh Chopade(codeswithritesh)
32+
*/
33+
@IssueKey("3837")
34+
@WithClasses({UserEntity.class, UserDto.class})
35+
public class IgnoredMappedPropertyTest {
36+
37+
@ProcessorTest
38+
@WithClasses({
39+
UserMapper.class,
40+
UserMapperWithIgnoreSourcePolicy.class,
41+
UserMapperWithoutBeanMapping.class,
42+
UserMapperWithIgnorePolicyInMapperConfig.class
43+
})
44+
@ExpectedCompilationOutcome(
45+
value = CompilationResult.SUCCEEDED
46+
)
47+
public void shouldNotWarnAboutRedundantIgnore() {
48+
}
49+
50+
@ProcessorTest
51+
@WithClasses({
52+
UserMapperWithErrorSourcePolicy.class,
53+
UserMapperWithErrorSourcePolicyInMapper.class,
54+
UserMapperWithErrorPolicyInMapperConfig.class
55+
})
56+
@ExpectedCompilationOutcome(
57+
value = CompilationResult.FAILED,
58+
diagnostics = {
59+
@Diagnostic(
60+
type = UserMapperWithErrorSourcePolicy.class,
61+
kind = Kind.ERROR,
62+
line = 20,
63+
message = "Source property \"email\" is mapped despite being " +
64+
"listed in ignoreUnmappedSourceProperties."
65+
),
66+
@Diagnostic(
67+
type = UserMapperWithErrorSourcePolicyInMapper.class,
68+
kind = Kind.ERROR,
69+
line = 19,
70+
message = "Source property \"email\" is mapped despite being " +
71+
"listed in ignoreUnmappedSourceProperties."
72+
),
73+
@Diagnostic(
74+
type = UserMapperWithErrorPolicyInMapperConfig.class,
75+
kind = Kind.ERROR,
76+
line = 23,
77+
message = "Source property \"email\" is mapped despite being " +
78+
"listed in ignoreUnmappedSourceProperties."
79+
)
80+
}
81+
)
82+
public void shouldWarnAboutRedundantIgnoreWithErrorPolicy() {
83+
}
84+
85+
@ProcessorTest
86+
@WithClasses({
87+
UserMapperWithWarnSourcePolicy.class,
88+
UserMapperWithWarnSourcePolicyInMapper.class,
89+
UserMapperWithWarnPolicyInMapperConfig.class
90+
})
91+
@ExpectedCompilationOutcome(
92+
value = CompilationResult.SUCCEEDED,
93+
diagnostics = {
94+
@Diagnostic(
95+
type = UserMapperWithWarnSourcePolicy.class,
96+
kind = Kind.WARNING,
97+
line = 20,
98+
message = "Source property \"email\" is mapped despite being " +
99+
"listed in ignoreUnmappedSourceProperties."
100+
),
101+
@Diagnostic(
102+
type = UserMapperWithWarnSourcePolicyInMapper.class,
103+
kind = Kind.WARNING,
104+
line = 19,
105+
message = "Source property \"email\" is mapped despite being " +
106+
"listed in ignoreUnmappedSourceProperties."
107+
),
108+
@Diagnostic(
109+
type = UserMapperWithWarnPolicyInMapperConfig.class,
110+
kind = Kind.WARNING,
111+
line = 23,
112+
message = "Source property \"email\" is mapped despite being " +
113+
"listed in ignoreUnmappedSourceProperties."
114+
)
115+
}
116+
)
117+
public void shouldWarnAboutRedundantIgnoreWithWarnPolicy() {
118+
}
119+
120+
@ProcessorTest
121+
@WithClasses({UserMapperWithMultiMapping.class})
122+
@ExpectedCompilationOutcome(
123+
value = CompilationResult.FAILED,
124+
diagnostics = {
125+
@Diagnostic(
126+
type = UserMapperWithMultiMapping.class,
127+
kind = Kind.ERROR,
128+
line = 21,
129+
message = "Source properties \"email, username\" are mapped despite " +
130+
"being listed in ignoreUnmappedSourceProperties."
131+
)
132+
}
133+
)
134+
public void shouldWarnAboutRedundantIgnoreWithMultiMapping() {
135+
}
136+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.ignoreunmapped;
7+
8+
/**
9+
* @author Ritesh Chopade(codeswithritesh)
10+
*/
11+
public class UserDto {
12+
13+
private String username;
14+
private String email;
15+
16+
public String getUsername() {
17+
return username;
18+
}
19+
20+
public void setUsername(String username) {
21+
this.username = username;
22+
}
23+
24+
public String getEmail() {
25+
return email;
26+
}
27+
28+
public void setEmail(String email) {
29+
this.email = email;
30+
}
31+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.ignoreunmapped;
7+
8+
/**
9+
* @author Ritesh Chopade(codeswithritesh)
10+
*/
11+
public class UserEntity {
12+
13+
private String username;
14+
private String email;
15+
private String password;
16+
17+
public String getUsername() {
18+
return username;
19+
}
20+
21+
public void setUsername(String username) {
22+
this.username = username;
23+
}
24+
25+
public String getEmail() {
26+
return email;
27+
}
28+
29+
public void setEmail(String email) {
30+
this.email = email;
31+
}
32+
33+
public String getPassword() {
34+
return password;
35+
}
36+
37+
public void setPassword(String password) {
38+
this.password = password;
39+
}
40+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.ignoreunmapped.mapper;
7+
8+
import org.mapstruct.BeanMapping;
9+
import org.mapstruct.Mapper;
10+
import org.mapstruct.Mapping;
11+
import org.mapstruct.ap.test.ignoreunmapped.UserDto;
12+
import org.mapstruct.ap.test.ignoreunmapped.UserEntity;
13+
14+
@Mapper
15+
public interface UserMapper {
16+
@BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"})
17+
@Mapping(source = "email", target = "email")
18+
UserDto map(UserEntity user);
19+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.ignoreunmapped.mapper;
7+
8+
import org.mapstruct.BeanMapping;
9+
import org.mapstruct.Mapper;
10+
import org.mapstruct.MapperConfig;
11+
import org.mapstruct.Mapping;
12+
import org.mapstruct.ReportingPolicy;
13+
import org.mapstruct.ap.test.ignoreunmapped.UserDto;
14+
import org.mapstruct.ap.test.ignoreunmapped.UserEntity;
15+
16+
@MapperConfig(unmappedSourcePolicy = ReportingPolicy.IGNORE)
17+
interface IgnorePolicyConfig { }
18+
19+
@Mapper(config = IgnorePolicyConfig.class)
20+
public interface UserMapperWithIgnorePolicyInMapperConfig {
21+
@BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"})
22+
@Mapping(source = "email", target = "email")
23+
UserDto map(UserEntity user);
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.ignoreunmapped.mapper;
7+
8+
import org.mapstruct.BeanMapping;
9+
import org.mapstruct.Mapper;
10+
import org.mapstruct.Mapping;
11+
import org.mapstruct.ReportingPolicy;
12+
import org.mapstruct.ap.test.ignoreunmapped.UserDto;
13+
import org.mapstruct.ap.test.ignoreunmapped.UserEntity;
14+
15+
@Mapper
16+
public interface UserMapperWithIgnoreSourcePolicy {
17+
18+
@BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}, unmappedSourcePolicy = ReportingPolicy.IGNORE)
19+
@Mapping(source = "email", target = "email")
20+
UserDto map(UserEntity user);
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.ignoreunmapped.mapper;
7+
8+
import org.mapstruct.BeanMapping;
9+
import org.mapstruct.Mapper;
10+
import org.mapstruct.MapperConfig;
11+
import org.mapstruct.Mapping;
12+
import org.mapstruct.ReportingPolicy;
13+
import org.mapstruct.ap.test.ignoreunmapped.UserDto;
14+
import org.mapstruct.ap.test.ignoreunmapped.UserEntity;
15+
16+
@MapperConfig(unmappedSourcePolicy = ReportingPolicy.WARN)
17+
interface WarnPolicyConfig { }
18+
19+
@Mapper(config = WarnPolicyConfig.class)
20+
public interface UserMapperWithWarnPolicyInMapperConfig {
21+
@BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"})
22+
@Mapping(source = "email", target = "email")
23+
UserDto map(UserEntity user);
24+
}

0 commit comments

Comments
 (0)