Skip to content

Commit e18dbc5

Browse files
authored
#132 Support for unmappedTargetPolicy in unmapped target properties inspection
1 parent 20297a3 commit e18dbc5

14 files changed

+726
-2
lines changed

src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.stream.Stream;
1616

1717
import com.intellij.codeInspection.LocalQuickFixOnPsiElement;
18+
import com.intellij.codeInspection.ProblemHighlightType;
1819
import com.intellij.codeInspection.ProblemsHolder;
1920
import com.intellij.openapi.project.Project;
2021
import com.intellij.psi.JavaElementVisitor;
@@ -32,6 +33,7 @@
3233
import org.jetbrains.annotations.Nls;
3334
import org.jetbrains.annotations.NotNull;
3435
import org.jetbrains.annotations.Nullable;
36+
import org.mapstruct.ReportingPolicy;
3537
import org.mapstruct.intellij.MapStructBundle;
3638
import org.mapstruct.intellij.settings.ProjectSettings;
3739
import org.mapstruct.intellij.util.MapStructVersion;
@@ -40,6 +42,7 @@
4042
import static com.intellij.codeInsight.AnnotationUtil.findAnnotation;
4143
import static com.intellij.codeInsight.AnnotationUtil.getBooleanAttributeValue;
4244
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.addMappingAnnotation;
45+
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.getUnmappedTargetPolicy;
4346
import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration;
4447
import static org.mapstruct.intellij.util.MapstructUtil.isMapper;
4548
import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig;
@@ -83,6 +86,11 @@ public void visitMethod(PsiMethod method) {
8386
if ( isBeanMappingIgnoreByDefault( method ) ) {
8487
return;
8588
}
89+
ReportingPolicy reportingPolicy = getUnmappedTargetPolicy( method );
90+
if (reportingPolicy == ReportingPolicy.IGNORE) {
91+
return;
92+
}
93+
8694

8795
Set<String> allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, method );
8896

@@ -145,6 +153,8 @@ public void visitMethod(PsiMethod method) {
145153
holder.registerProblem(
146154
method.getNameIdentifier(),
147155
descriptionTemplate,
156+
(ReportingPolicy.ERROR == reportingPolicy ? ProblemHighlightType.ERROR :
157+
ProblemHighlightType.WARNING),
148158
quickFixes.toArray( UnmappedTargetPropertyFix.EMPTY_ARRAY )
149159
);
150160
}

src/main/java/org/mapstruct/intellij/util/MapstructAnnotationUtils.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
4747
import com.intellij.util.IncorrectOperationException;
4848
import org.jetbrains.annotations.NotNull;
49+
import org.mapstruct.ReportingPolicy;
4950

5051
import static com.intellij.codeInsight.AnnotationUtil.findAnnotation;
5152
import static com.intellij.codeInsight.AnnotationUtil.findDeclaredAttribute;
@@ -61,6 +62,8 @@
6162
*/
6263
public class MapstructAnnotationUtils {
6364

65+
private static final String UNMAPPED_TARGET_POLICY = "unmappedTargetPolicy";
66+
6467
private MapstructAnnotationUtils() {
6568
}
6669

@@ -479,4 +482,81 @@ private static Stream<PsiClass> findReferencedMappersOfMapperConfig(PsiAnnotatio
479482
return findReferencedMappers( mapperConfigAnnotation );
480483
}
481484

485+
@NotNull
486+
public static ReportingPolicy getUnmappedTargetPolicy( @NotNull PsiMethod method ) {
487+
PsiAnnotation beanMapping = method.getAnnotation( MapstructUtil.BEAN_MAPPING_FQN );
488+
if (beanMapping != null) {
489+
PsiAnnotationMemberValue beanAnnotationOverwrite =
490+
beanMapping.findDeclaredAttributeValue( UNMAPPED_TARGET_POLICY );
491+
if (beanAnnotationOverwrite != null) {
492+
return getUnmappedTargetPolicyPolicyFromAnnotation( beanAnnotationOverwrite );
493+
}
494+
}
495+
PsiClass containingClass = method.getContainingClass();
496+
if (containingClass == null) {
497+
return ReportingPolicy.WARN;
498+
}
499+
return getUnmappedTargetPolicyFromClass( containingClass );
500+
}
501+
502+
@NotNull
503+
private static ReportingPolicy getUnmappedTargetPolicyFromClass( @NotNull PsiClass containingClass ) {
504+
PsiAnnotation mapperAnnotation = containingClass.getAnnotation( MapstructUtil.MAPPER_ANNOTATION_FQN );
505+
if (mapperAnnotation == null) {
506+
return ReportingPolicy.WARN;
507+
}
508+
509+
PsiAnnotationMemberValue classAnnotationOverwrite = mapperAnnotation.findDeclaredAttributeValue(
510+
UNMAPPED_TARGET_POLICY );
511+
if (classAnnotationOverwrite != null) {
512+
return getUnmappedTargetPolicyPolicyFromAnnotation( classAnnotationOverwrite );
513+
}
514+
return getUnmappedTargetPolicyFromMapperConfig( mapperAnnotation );
515+
}
516+
517+
@NotNull
518+
private static ReportingPolicy getUnmappedTargetPolicyFromMapperConfig( @NotNull PsiAnnotation mapperAnnotation ) {
519+
PsiModifierListOwner mapperConfigReference = findMapperConfigReference( mapperAnnotation );
520+
if ( mapperConfigReference == null ) {
521+
return ReportingPolicy.WARN;
522+
}
523+
PsiAnnotation mapperConfigAnnotation = mapperConfigReference.getAnnotation(
524+
MapstructUtil.MAPPER_CONFIG_ANNOTATION_FQN );
525+
526+
if (mapperConfigAnnotation == null) {
527+
return ReportingPolicy.WARN;
528+
}
529+
PsiAnnotationMemberValue configValue =
530+
mapperConfigAnnotation.findDeclaredAttributeValue( UNMAPPED_TARGET_POLICY );
531+
if (configValue == null) {
532+
return ReportingPolicy.WARN;
533+
}
534+
return getUnmappedTargetPolicyPolicyFromAnnotation( configValue );
535+
}
536+
537+
538+
/**
539+
* Converts the configValue to ReportingPolicy enum. If no matching ReportingPolicy found,
540+
* returns ReportingPolicy.WARN.
541+
*
542+
* @param configValue The annotation value to convert to ReportingPolicy enum
543+
* @return the mapped ReportingPolicy enum
544+
*/
545+
@NotNull
546+
private static ReportingPolicy getUnmappedTargetPolicyPolicyFromAnnotation(
547+
@NotNull PsiAnnotationMemberValue configValue ) {
548+
switch (configValue.getText()) {
549+
case "IGNORE":
550+
case "ReportingPolicy.IGNORE":
551+
return ReportingPolicy.IGNORE;
552+
case "ERROR":
553+
case "ReportingPolicy.ERROR":
554+
return ReportingPolicy.ERROR;
555+
case "WARN":
556+
case "ReportingPolicy.WARN":
557+
default:
558+
return ReportingPolicy.WARN;
559+
}
560+
}
561+
482562
}

src/main/resources/inspectionDescriptions/UnmappedTargetProperties.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
<p>This inspection reports when a mapping method has unmapped target properties.</p>
44
<p>
55
No check is done on methods annotated with <code>@InheritInverseConfiguration</code>.
6-
The <code>@Mapper</code>, or <code>@MapperConfig</code> unmapped target policy is still not taken into
7-
consideration
6+
The <code>@BeanMapping</code>, <code>@Mapper</code>, and <code>@MapperConfig</code> unmapped target policy is taken into consideration
87
</p>
98
<!-- tooltip end -->
109
</body>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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.intellij.inspection;
7+
8+
import com.intellij.codeInsight.intention.IntentionAction;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
import java.util.List;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
/**
16+
* @author hduelme
17+
*/
18+
public class UnmappedTargetPropertiesInspectionUnmappedTargetPolicyTest extends BaseInspectionTest {
19+
20+
@NotNull
21+
@Override
22+
protected Class<UnmappedTargetPropertiesInspection> getInspection() {
23+
return UnmappedTargetPropertiesInspection.class;
24+
}
25+
26+
@Override
27+
protected void setUp() throws Exception {
28+
super.setUp();
29+
myFixture.copyFileToProject(
30+
"UnmappedTargetPropertiesData.java",
31+
"org/example/data/UnmappedTargetPropertiesData.java"
32+
);
33+
}
34+
35+
/**
36+
* Tests if unmappedTargetPolicy is read from methode first. Methode level annotation should overwrite class values.
37+
*/
38+
public void testUnmappedTargetPropertiesReportPolicyBeanMappingBeforeClassConfig() {
39+
doTest();
40+
checkQuickFixes();
41+
}
42+
43+
/**
44+
* Tests if unmappedTargetPolicy could be read from @BeanMapping annotation.
45+
*/
46+
public void testUnmappedTargetPropertiesReportPolicyBeanMapping() {
47+
doTest();
48+
checkQuickFixes();
49+
}
50+
51+
/**
52+
* Tests if unmappedTargetPolicy is read from class annotation first.
53+
* Class level annotation should overwrite config class values.
54+
*/
55+
public void testUnmappedTargetPropertiesReportPolicyClassLevelBeforeConfigClassError() {
56+
doTest();
57+
checkQuickFixes();
58+
}
59+
60+
/**
61+
* Tests if unmappedTargetPolicy is read from class annotation first.
62+
* Class level annotation should overwrite config class values.
63+
*/
64+
public void testUnmappedTargetPropertiesReportPolicyClassLevelBeforeConfigClassWarn() {
65+
doTest();
66+
checkQuickFixes();
67+
}
68+
69+
/**
70+
* Tests if unmappedTargetPolicy could be read from mapper config class.
71+
*/
72+
public void testUnmappedTargetPropertiesReportPolicyConfigClass() {
73+
doTest();
74+
checkQuickFixes();
75+
}
76+
77+
/**
78+
* Tests if unmappedTargetPolicy set to ERROR, results in reported errors instead of warnings.
79+
*/
80+
public void testUnmappedTargetPropertiesReportPolicyError() {
81+
doTest();
82+
checkQuickFixes();
83+
}
84+
85+
/**
86+
* Tests if unmappedTargetPolicy set to IGNORE, suppress all unmapped warnings
87+
*/
88+
public void testUnmappedTargetPropertiesReportPolicyIgnoreBeanMapping() {
89+
doTest();
90+
assertThat( myFixture.getAllQuickFixes() ).isEmpty();
91+
}
92+
93+
/**
94+
* Tests if unmappedTargetPolicy set to IGNORE, suppress all unmapped warnings
95+
*/
96+
public void testUnmappedTargetPropertiesReportPolicyIgnoreClassAnnotation() {
97+
doTest();
98+
assertThat( myFixture.getAllQuickFixes() ).isEmpty();
99+
}
100+
101+
/**
102+
* Tests if unmappedTargetPolicy set to IGNORE, suppress all unmapped warnings
103+
*/
104+
public void testUnmappedTargetPropertiesReportPolicyIgnoreConfigClass() {
105+
doTest();
106+
assertThat( myFixture.getAllQuickFixes() ).isEmpty();
107+
}
108+
109+
/**
110+
* Tests if unmappedTargetPolicy could be read, if static import is used.
111+
*/
112+
public void testUnmappedTargetPropertiesReportPolicyStaticImport() {
113+
doTest();
114+
checkQuickFixes();
115+
}
116+
117+
private void checkQuickFixes() {
118+
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();
119+
120+
assertThat( allQuickFixes )
121+
.extracting( IntentionAction::getText )
122+
.as( "Intent Text" )
123+
.containsExactly(
124+
"Ignore unmapped target property: 'moreTarget'",
125+
"Add unmapped target property: 'moreTarget'",
126+
"Ignore unmapped target property: 'moreTarget'",
127+
"Add unmapped target property: 'moreTarget'",
128+
"Ignore unmapped target property: 'testName'",
129+
"Add unmapped target property: 'testName'",
130+
"Ignore all unmapped target properties",
131+
"Ignore unmapped target property: 'testName'",
132+
"Add unmapped target property: 'testName'",
133+
"Ignore unmapped target property: 'moreTarget'",
134+
"Add unmapped target property: 'moreTarget'"
135+
);
136+
}
137+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package inspection;/*
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+
7+
import org.example.data.UnmappedTargetPropertiesData.Source;
8+
import org.example.data.UnmappedTargetPropertiesData.Target;
9+
import org.mapstruct.*;
10+
11+
@Mapper
12+
interface SingleMappingMapper {
13+
14+
@Mapping(target = "testName", source = "name")
15+
@BeanMapping(unmappedTargetPolicy = ReportingPolicy.ERROR)
16+
Target <error descr="Unmapped target property: moreTarget">map</error>(Source source);
17+
}
18+
19+
@Mapper
20+
interface NoMappingMapper {
21+
22+
@BeanMapping(unmappedTargetPolicy = ReportingPolicy.ERROR)
23+
Target <error descr="Unmapped target properties: moreTarget, testName">map</error>(Source source);
24+
25+
@InheritInverseConfiguration
26+
Source reverse(Target target);
27+
}
28+
29+
@MapperConfig
30+
interface AllMappingsMapperConfig {
31+
32+
@Mappings({
33+
@Mapping(target = "testName", source = "name"),
34+
@Mapping(target = "moreTarget", source = "moreSource")
35+
})
36+
@BeanMapping(unmappedTargetPolicy = ReportingPolicy.ERROR)
37+
Target mapWithAllMappings(Source source);
38+
}
39+
40+
@Mapper
41+
interface UpdateMapper {
42+
43+
@Mapping(target = "moreTarget", source = "moreSource")
44+
@BeanMapping(unmappedTargetPolicy = ReportingPolicy.ERROR)
45+
void <error descr="Unmapped target property: testName">update</error>(@MappingTarget Target target, Source source);
46+
}
47+
48+
@Mapper
49+
interface MultiSourceUpdateMapper {
50+
51+
@BeanMapping(unmappedTargetPolicy = ReportingPolicy.ERROR)
52+
void <error descr="Unmapped target property: moreTarget">update</error>(@MappingTarget Target moreTarget, Source source, String testName, @Context String matching);
53+
}

0 commit comments

Comments
 (0)