Skip to content

Commit c461730

Browse files
authored
Add inspection for mapping to target = "." without source (#175)
1 parent f602c3a commit c461730

13 files changed

+400
-3
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ public void visitAnnotation(@NotNull PsiAnnotation annotation ) {
4848
&& annotationAttribute.getAttributeValue() != null) {
4949
PsiNameValuePair nameValuePair = (PsiNameValuePair) annotationAttribute;
5050
switch (nameValuePair.getAttributeName()) {
51+
case "target" :
52+
mappingAnnotation.setTargetProperty( nameValuePair );
53+
break;
5154
case "source":
5255
mappingAnnotation.setSourceProperty( nameValuePair );
5356
break;
@@ -91,6 +94,7 @@ abstract void visitMappingAnnotation( @NotNull ProblemsHolder problemsHolder, @N
9194
@NotNull MappingAnnotation mappingAnnotation );
9295

9396
protected static class MappingAnnotation {
97+
private PsiNameValuePair targetProperty;
9498
private PsiNameValuePair sourceProperty;
9599
private PsiNameValuePair constantProperty;
96100
private PsiNameValuePair defaultValueProperty;
@@ -101,6 +105,18 @@ protected static class MappingAnnotation {
101105
private PsiNameValuePair qualifiedByNameProperty;
102106
private PsiNameValuePair conditionExpression;
103107

108+
public PsiNameValuePair getTargetProperty() {
109+
return targetProperty;
110+
}
111+
112+
public void setTargetProperty( PsiNameValuePair targetProperty ) {
113+
this.targetProperty = targetProperty;
114+
}
115+
116+
public boolean isNotThisTarget() {
117+
return targetProperty == null || !".".equals( targetProperty.getLiteralValue() );
118+
}
119+
104120
public PsiNameValuePair getSourceProperty() {
105121
return sourceProperty;
106122
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,16 @@ void visitMappingAnnotation( @NotNull ProblemsHolder problemsHolder, @NotNull Ps
2828
|| (mappingAnnotation.getSourceProperty() != null && mappingAnnotation.getExpressionProperty() != null)) {
2929
ArrayList<LocalQuickFix> quickFixes = new ArrayList<>( 5 );
3030
String family = MapStructBundle.message( "intention.more.than.one.source.property" );
31-
if (mappingAnnotation.getSourceProperty() != null) {
31+
if (mappingAnnotation.getSourceProperty() != null && mappingAnnotation.isNotThisTarget()) {
3232
quickFixes.add( createRemoveAnnotationAttributeQuickFix( mappingAnnotation.getSourceProperty(),
3333
"Remove source value", family ) );
3434
}
3535
if (mappingAnnotation.getConstantProperty() != null) {
3636
quickFixes.add( createRemoveAnnotationAttributeQuickFix( mappingAnnotation.getConstantProperty(),
3737
"Remove constant value", family ) );
3838

39-
if (mappingAnnotation.hasNoDefaultProperties() && mappingAnnotation.getSourceProperty() != null) {
39+
if (mappingAnnotation.hasNoDefaultProperties() && mappingAnnotation.getSourceProperty() != null
40+
&& mappingAnnotation.isNotThisTarget() ) {
4041
quickFixes.add( createReplaceAsDefaultValueQuickFix(
4142
mappingAnnotation.getConstantProperty(), "constant", "defaultValue",
4243
"Use constant value as default value", family ) );
@@ -45,7 +46,8 @@ void visitMappingAnnotation( @NotNull ProblemsHolder problemsHolder, @NotNull Ps
4546
if (mappingAnnotation.getExpressionProperty() != null) {
4647
quickFixes.add( createRemoveAnnotationAttributeQuickFix( mappingAnnotation.getExpressionProperty(),
4748
"Remove expression", family ) );
48-
if (mappingAnnotation.hasNoDefaultProperties() && mappingAnnotation.getSourceProperty() != null) {
49+
if (mappingAnnotation.hasNoDefaultProperties() && mappingAnnotation.getSourceProperty() != null
50+
&& mappingAnnotation.isNotThisTarget() ) {
4951
quickFixes.add( createReplaceAsDefaultValueQuickFix(
5052
mappingAnnotation.getExpressionProperty(), "expression", "defaultExpression",
5153
"Use expression as default expression", family ) );
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.inspection;
7+
8+
import com.intellij.codeInspection.ProblemsHolder;
9+
import com.intellij.psi.PsiAnnotation;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.mapstruct.intellij.MapStructBundle;
12+
13+
/**
14+
* @author hduelme
15+
*/
16+
public class TargetThisMappingNoSourcePropertyInspection extends MappingAnnotationInspectionBase {
17+
18+
@Override
19+
void visitMappingAnnotation(@NotNull ProblemsHolder problemsHolder, @NotNull PsiAnnotation psiAnnotation,
20+
@NotNull MappingAnnotation mappingAnnotation) {
21+
if ( mappingAnnotation.isNotThisTarget() || mappingAnnotation.getIgnoreProperty() != null) {
22+
return;
23+
}
24+
if (mappingAnnotation.getSourceProperty() == null ) {
25+
problemsHolder.registerProblem( psiAnnotation,
26+
MapStructBundle.message( "inspection.this.target.mapping.no.source.property" ) );
27+
}
28+
}
29+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@
120120
key="inspection.wrong.map.mapping.map.type"
121121
shortName="FromMapMappingInspection"
122122
implementationClass="org.mapstruct.intellij.inspection.FromMapMappingMapTypeInspection"/>
123+
<localInspection
124+
language="JAVA"
125+
enabledByDefault="true"
126+
level="ERROR"
127+
bundle="org.mapstruct.intellij.messages.MapStructBundle"
128+
key="inspection.this.target.mapping.no.source.property"
129+
shortName="TargetThisMappingNoSourcePropertyInspection"
130+
implementationClass="org.mapstruct.intellij.inspection.TargetThisMappingNoSourcePropertyInspection"/>
123131
</extensions>
124132

125133
<actions>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<html>
2+
<body>
3+
<p>
4+
This inspection reports when <code> @Mapping( target = ".") </code> is used without a source property.
5+
</p>
6+
<p>
7+
<pre><code>
8+
//wrong
9+
@Mapper
10+
public interface EmployeeMapper {
11+
@Mapping(target = ".", expression = "java(company.getEmployee())"
12+
Employee toEmployee(Company company, @Context CycleAvoidingMappingContext context);
13+
}
14+
</code></pre>
15+
</p>
16+
<p>
17+
<pre><code>
18+
//correct
19+
@Mapper
20+
public interface EmployeeMapper {
21+
@Mapping(target = ".", source = "employee")
22+
Employee toEmployee(Company company, @Context CycleAvoidingMappingContext context);
23+
}
24+
</code></pre>
25+
</p>
26+
<!-- tooltip end -->
27+
</body>
28+
</html>

src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ inspection.not.null.checkable.property.source.used.with.default.property.title=C
1919
inspection.java.expression.unnecessary.whitespace=Unnecessary whitespaces {0} {1}
2020
inspection.java.expression.remove.unnecessary.whitespace=Remove unnecessary whitespaces {0} {1}
2121
inspection.java.expression.unnecessary.whitespace.title=Unnecessary whitespaces before or after Java expression
22+
inspection.this.target.mapping.no.source.property=Using @Mapping( target = ".") requires a source property. Expression or constant cannot be used as a source
2223
inspection.wrong.map.mapping.map.type=Map type is raw or key type is not string for mapping Map to Bean
2324
inspection.wrong.map.mapping.map.type.raw=Raw map used for mapping Map to Bean
2425
inspection.wrong.map.mapping.map.type.raw.set.default=Replace {0} with {0}<String, String>

src/test/java/org/mapstruct/intellij/inspection/MoreThanOneSourcePropertyDefinedInspectionTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ public void testMoreThanOneSourceConstantAndSource() {
4040
myFixture.checkResultByFile( testName + "_after.java" );
4141
}
4242

43+
public void testMoreThanOneSourceConstantAndSourceThisMapping() {
44+
doTest();
45+
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();
46+
assertThat( allQuickFixes )
47+
.extracting( IntentionAction::getText )
48+
.as( "Intent Text" )
49+
.containsExactly(
50+
"Remove constant value",
51+
"Remove constant value"
52+
);
53+
allQuickFixes.forEach( myFixture::launchAction );
54+
String testName = getTestName( false );
55+
myFixture.checkResultByFile( testName + "_after.java" );
56+
}
57+
4358
public void testMoreThanOneSourceConstantAndExpression() {
4459
doTest();
4560
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();
@@ -74,4 +89,19 @@ public void testMoreThanOneSourceExpressionAndSource() {
7489
String testName = getTestName( false );
7590
myFixture.checkResultByFile( testName + "_after.java" );
7691
}
92+
93+
public void testMoreThanOneSourceExpressionAndSourceThisMapping() {
94+
doTest();
95+
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();
96+
assertThat( allQuickFixes )
97+
.extracting( IntentionAction::getText )
98+
.as( "Intent Text" )
99+
.containsExactly(
100+
"Remove expression",
101+
"Remove expression"
102+
);
103+
allQuickFixes.forEach( myFixture::launchAction );
104+
String testName = getTestName( false );
105+
myFixture.checkResultByFile( testName + "_after.java" );
106+
}
77107
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.inspection;
7+
8+
import com.intellij.codeInspection.LocalInspectionTool;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
/**
12+
* @author hduelme
13+
*/
14+
public class TargetThisMappingNoSourcePropertyInspectionTest extends BaseInspectionTest {
15+
@Override
16+
protected @NotNull Class<? extends LocalInspectionTool> getInspection() {
17+
return TargetThisMappingNoSourcePropertyInspection.class;
18+
}
19+
20+
public void testTargetThisMappingNoSourceProperty() {
21+
doTest();
22+
}
23+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
import org.mapstruct.Mapper;
8+
import org.mapstruct.Mapping;
9+
import org.mapstruct.Mappings;
10+
11+
class Source {
12+
13+
private Target innerTarget;
14+
15+
public Target getInnerTarget() {
16+
return innerTarget;
17+
}
18+
19+
public void setInnerTarget(Target innerTarget) {
20+
this.innerTarget = innerTarget;
21+
}
22+
}
23+
24+
class Target {
25+
26+
private String testName;
27+
28+
public String getTestName() {
29+
return testName;
30+
}
31+
32+
public void setTestName(String testName) {
33+
this.testName = testName;
34+
}
35+
}
36+
37+
@Mapper
38+
interface SingleMappingMapper {
39+
40+
<error descr="More than one source property defined">@Mapping(target = ".", source = "innerTarget", constant = "My name")</error>
41+
Target map(Source source);
42+
}
43+
44+
@Mapper
45+
interface SingleMappingsMapper {
46+
47+
@Mappings({
48+
<error descr="More than one source property defined">@Mapping(target = ".", source = "innerTarget", constant = "My name")</error>
49+
})
50+
Target map(Source source);
51+
}
52+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
import org.mapstruct.Mapper;
8+
import org.mapstruct.Mapping;
9+
import org.mapstruct.Mappings;
10+
11+
class Source {
12+
13+
private Target innerTarget;
14+
15+
public Target getInnerTarget() {
16+
return innerTarget;
17+
}
18+
19+
public void setInnerTarget(Target innerTarget) {
20+
this.innerTarget = innerTarget;
21+
}
22+
}
23+
24+
class Target {
25+
26+
private String testName;
27+
28+
public String getTestName() {
29+
return testName;
30+
}
31+
32+
public void setTestName(String testName) {
33+
this.testName = testName;
34+
}
35+
}
36+
37+
@Mapper
38+
interface SingleMappingMapper {
39+
40+
@Mapping(target = ".", source = "innerTarget")
41+
Target map(Source source);
42+
}
43+
44+
@Mapper
45+
interface SingleMappingsMapper {
46+
47+
@Mappings({
48+
@Mapping(target = ".", source = "innerTarget")
49+
})
50+
Target map(Source source);
51+
}
52+

0 commit comments

Comments
 (0)