Skip to content

Commit 815011b

Browse files
authored
#203 Add a warning when using source = "." (#204)
1 parent 48f4d6a commit 815011b

File tree

11 files changed

+384
-47
lines changed

11 files changed

+384
-47
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/
3838
* More than one `source` in `@Mapping` annotation defined with quick fixes: Remove `source`. Remove `constant`. Remove `expression`. Use `constant` as `defaultValue`. Use `expression` as `defaultExpression`.
3939
* More than one default source in `@Mapping` annotation defined with quick fixes: Remove `defaultValue`. Remove `defaultExpression`.
4040
* `target` mapped more than once by `@Mapping` annotations with quick fixes: Remove annotation and change target property.
41+
* `*` used as a source in `@Mapping` annotation with quick fixes: Replace `*` with method parameter name.
4142

4243
## Requirements
4344

description.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<li>More than one <code>source</code> in <code>@Mapping</code> annotation defined with quick fixes: Remove <code>source</code>. Remove <code>constant</code>. Remove <code>expression</code>. Use <code>constant</code> as <code>defaultValue</code>. Use <code>expression</code> as <code>defaultExpression</code>.</li>
4343
<li>More than one default source in <code>@Mapping</code> annotation defined with quick fixes: Remove <code>defaultValue</code>. Remove <code>defaultExpression</code>.</li>
4444
<li><code>target</code> mapped more than once by <code>@Mapping</code> annotations with quick fixes: Remove annotation and change target property.</li>
45+
<li><code>*</code> used as a source in <code>@Mapping</code> annotations with quick fixes: Replace <code>*</code> with method parameter name.</li>
4546
</ul>
4647
</li>
4748
</ul>

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

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@
88
import com.intellij.codeInspection.ProblemsHolder;
99
import com.intellij.psi.PsiAnnotation;
1010
import com.intellij.psi.PsiAnnotationMemberValue;
11-
import com.intellij.psi.PsiElement;
1211
import com.intellij.psi.PsiLiteralExpression;
1312
import com.intellij.psi.PsiMethod;
1413
import com.intellij.psi.PsiNameValuePair;
15-
import com.intellij.psi.impl.source.tree.java.PsiAnnotationParamListImpl;
1614
import org.jetbrains.annotations.NotNull;
17-
import org.jetbrains.annotations.Nullable;
1815
import org.mapstruct.intellij.MapStructBundle;
1916
import org.mapstruct.intellij.util.MapstructUtil;
2017
import org.mapstruct.intellij.util.SourceUtils;
2118

19+
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.getAnnotatedMethod;
20+
2221
/**
2322
* Inspection that checks if inside a @Mapping at least one source property is defined
2423
*
@@ -59,48 +58,4 @@ private static boolean isIgnoreByDefaultEnabled( @NotNull PsiMethod annotatedMet
5958
&& Boolean.TRUE.equals( ((PsiLiteralExpression) ignoreByDefault).getValue() );
6059
}
6160

62-
@Nullable
63-
private static PsiMethod getAnnotatedMethod(@NotNull PsiAnnotation psiAnnotation) {
64-
PsiElement psiAnnotationParent = psiAnnotation.getParent();
65-
if (psiAnnotationParent == null) {
66-
return null;
67-
}
68-
PsiElement psiAnnotationParentParent = psiAnnotationParent.getParent();
69-
if (psiAnnotationParentParent instanceof PsiMethod) {
70-
// directly annotated with @Mapping
71-
return (PsiMethod) psiAnnotationParentParent;
72-
}
73-
74-
PsiElement psiAnnotationParentParentParent = psiAnnotationParentParent.getParent();
75-
if (psiAnnotationParentParentParent instanceof PsiAnnotation) {
76-
// inside @Mappings without array
77-
PsiElement mappingsAnnotationParent = psiAnnotationParentParentParent.getParent();
78-
if (mappingsAnnotationParent == null) {
79-
return null;
80-
}
81-
PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent();
82-
if (mappingsAnnotationParentParent instanceof PsiMethod) {
83-
return (PsiMethod) mappingsAnnotationParentParent;
84-
}
85-
return null;
86-
}
87-
else if (psiAnnotationParentParentParent instanceof PsiAnnotationParamListImpl) {
88-
// inside @Mappings wit array
89-
PsiElement mappingsArray = psiAnnotationParentParentParent.getParent();
90-
if (mappingsArray == null) {
91-
return null;
92-
}
93-
PsiElement mappingsAnnotationParent = mappingsArray.getParent();
94-
if (mappingsAnnotationParent == null) {
95-
return null;
96-
}
97-
PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent();
98-
if (mappingsAnnotationParentParent instanceof PsiMethod) {
99-
return (PsiMethod) mappingsAnnotationParentParent;
100-
}
101-
return null;
102-
103-
}
104-
return null;
105-
}
10661
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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.LocalQuickFix;
9+
import com.intellij.codeInspection.LocalQuickFixOnPsiElement;
10+
import com.intellij.codeInspection.ProblemsHolder;
11+
import com.intellij.codeInspection.util.IntentionFamilyName;
12+
import com.intellij.codeInspection.util.IntentionName;
13+
import com.intellij.openapi.project.Project;
14+
import com.intellij.psi.PsiAnnotation;
15+
import com.intellij.psi.PsiElement;
16+
import com.intellij.psi.PsiFile;
17+
import com.intellij.psi.PsiMethod;
18+
import com.intellij.psi.PsiNameValuePair;
19+
import com.intellij.psi.PsiParameter;
20+
import com.intellij.psi.impl.source.tree.java.PsiAnnotationParamListImpl;
21+
import org.jetbrains.annotations.NotNull;
22+
import org.mapstruct.intellij.MapStructBundle;
23+
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
27+
import static com.intellij.psi.PsiElementFactory.getInstance;
28+
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.getAnnotatedMethod;
29+
import static org.mapstruct.intellij.util.MapstructUtil.getSourceParameters;
30+
31+
/**
32+
* @author hduelme
33+
*/
34+
public class ThisUsedAsSourcePropertyInspection extends MappingAnnotationInspectionBase {
35+
@Override
36+
void visitMappingAnnotation(@NotNull ProblemsHolder problemsHolder, @NotNull PsiAnnotation psiAnnotation,
37+
@NotNull MappingAnnotation mappingAnnotation) {
38+
PsiNameValuePair sourceProperty = mappingAnnotation.getSourceProperty();
39+
if (sourceProperty == null || sourceProperty.getValue() == null) {
40+
return;
41+
}
42+
if ( !".".equals( sourceProperty.getLiteralValue() ) ) {
43+
return;
44+
}
45+
List<LocalQuickFix> fixes = new ArrayList<>();
46+
PsiMethod annotatedMethod = getAnnotatedMethod( psiAnnotation );
47+
if (annotatedMethod != null) {
48+
for (PsiParameter sourceParameter : getSourceParameters( annotatedMethod )) {
49+
fixes.add( new ReplaceSourceParameterValueQuickFix(sourceProperty, sourceParameter.getName() ) );
50+
}
51+
}
52+
problemsHolder.registerProblem( sourceProperty.getValue(),
53+
MapStructBundle.message( "inspection.source.property.this.used" ),
54+
fixes.toArray( new LocalQuickFix[0] ) );
55+
}
56+
57+
private static class ReplaceSourceParameterValueQuickFix extends LocalQuickFixOnPsiElement {
58+
59+
private final String targetMethodeParameterName;
60+
private final String text;
61+
private final String family;
62+
63+
private ReplaceSourceParameterValueQuickFix(@NotNull PsiNameValuePair element,
64+
@NotNull String targetMethodeParameterName) {
65+
super( element );
66+
this.targetMethodeParameterName = targetMethodeParameterName;
67+
this.text = MapStructBundle.message( "intention.replace.source.property", targetMethodeParameterName );
68+
this.family = MapStructBundle.message( "inspection.source.property.this.used" );
69+
}
70+
71+
@Override
72+
public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement,
73+
@NotNull PsiElement endElement ) {
74+
if ( !endElement.isValid() ) {
75+
return false;
76+
}
77+
PsiElement parent = endElement.getParent();
78+
return parent.isValid() && parent instanceof PsiAnnotationParamListImpl;
79+
}
80+
81+
@Override
82+
public @IntentionName @NotNull String getText() {
83+
return text;
84+
}
85+
86+
@Override
87+
public void invoke( @NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement,
88+
@NotNull PsiElement endElement ) {
89+
if (endElement instanceof PsiNameValuePair end) {
90+
PsiAnnotationParamListImpl parent = (PsiAnnotationParamListImpl) end.getParent();
91+
PsiElement parent1 = parent.getParent();
92+
93+
// don't replace inside of strings. Only the constant value name
94+
String annotationText = parent1.getText().replaceFirst( "(?<!\")\\s*,?\\s*source\\s*=\\s*\"\\.\"",
95+
"source = \"" + targetMethodeParameterName + "\"" );
96+
parent1.replace( getInstance( project ).createAnnotationFromText( annotationText, parent1 ) );
97+
}
98+
}
99+
100+
@Override
101+
public @IntentionFamilyName @NotNull String getFamilyName() {
102+
return family;
103+
}
104+
}
105+
}

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.intellij.psi.PsiNameValuePair;
4646
import com.intellij.psi.PsiReference;
4747
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
48+
import com.intellij.psi.impl.source.tree.java.PsiAnnotationParamListImpl;
4849
import com.intellij.util.IncorrectOperationException;
4950
import com.intellij.util.containers.ContainerUtil;
5051
import org.jetbrains.annotations.NotNull;
@@ -618,4 +619,49 @@ private static ReportingPolicy getUnmappedTargetPolicyPolicyFromAnnotation(
618619
};
619620
}
620621

622+
@Nullable
623+
public static PsiMethod getAnnotatedMethod(@NotNull PsiAnnotation psiAnnotation) {
624+
PsiElement psiAnnotationParent = psiAnnotation.getParent();
625+
if (psiAnnotationParent == null) {
626+
return null;
627+
}
628+
PsiElement psiAnnotationParentParent = psiAnnotationParent.getParent();
629+
if (psiAnnotationParentParent instanceof PsiMethod annotatedPsiMethod) {
630+
// directly annotated with @Mapping
631+
return annotatedPsiMethod;
632+
}
633+
634+
PsiElement psiAnnotationParentParentParent = psiAnnotationParentParent.getParent();
635+
if (psiAnnotationParentParentParent instanceof PsiAnnotation) {
636+
// inside @Mappings without array
637+
PsiElement mappingsAnnotationParent = psiAnnotationParentParentParent.getParent();
638+
if (mappingsAnnotationParent == null) {
639+
return null;
640+
}
641+
PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent();
642+
if (mappingsAnnotationParentParent instanceof PsiMethod annotatedPsiMethod) {
643+
return annotatedPsiMethod;
644+
}
645+
return null;
646+
}
647+
else if (psiAnnotationParentParentParent instanceof PsiAnnotationParamListImpl) {
648+
// inside @Mappings wit array
649+
PsiElement mappingsArray = psiAnnotationParentParentParent.getParent();
650+
if (mappingsArray == null) {
651+
return null;
652+
}
653+
PsiElement mappingsAnnotationParent = mappingsArray.getParent();
654+
if (mappingsAnnotationParent == null) {
655+
return null;
656+
}
657+
PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent();
658+
if (mappingsAnnotationParentParent instanceof PsiMethod annotatedPsiMethod) {
659+
return annotatedPsiMethod;
660+
}
661+
return null;
662+
663+
}
664+
return null;
665+
}
666+
621667
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@
136136
key="inspection.target.property.mapped.more.than.once.title"
137137
shortName="TargetPropertyMappedMoreThanOnceInspection"
138138
implementationClass="org.mapstruct.intellij.inspection.TargetPropertyMappedMoreThanOnceInspection"/>
139+
<localInspection
140+
language="JAVA"
141+
enabledByDefault="true"
142+
level="WARNING"
143+
bundle="org.mapstruct.intellij.messages.MapStructBundle"
144+
key="inspection.source.property.this.used"
145+
shortName="ThisUsedAsSourcePropertyInspection"
146+
implementationClass="org.mapstruct.intellij.inspection.ThisUsedAsSourcePropertyInspection"/>
139147
</extensions>
140148

141149
<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 "." is used as a source in <code>@Mapping</code>
5+
</p>
6+
<p>
7+
<pre><code>
8+
//wrong
9+
@Mapper
10+
public interface EmployeeMapper {
11+
@Mapping(target = "dto", source = ".")
12+
Employee toEmployee(EmployeeDto employeeDto, @Context CycleAvoidingMappingContext context);
13+
}
14+
</code></pre>
15+
</p>
16+
<p>
17+
<pre><code>
18+
//correct
19+
@Mapper
20+
public interface EmployeeMapper {
21+
@Mapping(source = "employeeDto", target = "dto")
22+
Employee toEmployee(EmployeeDto employeeDto, @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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ inspection.wrong.map.mapping.map.key=Key must be of type String for mapping Map
2727
inspection.wrong.map.mapping.map.key.change.to.string=Change key type to String
2828
inspection.target.property.mapped.more.than.once=Target property ''{0}'' must not be mapped more than once.
2929
inspection.target.property.mapped.more.than.once.title=Target properties must not be mapped more than once.
30+
inspection.source.property.this.used=''.'' should not be used as a source.
3031
intention.add.ignore.all.unmapped.target.properties=Add ignore all unmapped target properties
3132
intention.add.ignore.unmapped.target.property=Add ignore unmapped target property
3233
intention.add.unmapped.target.property=Add unmapped target property
@@ -38,6 +39,7 @@ intention.wrong.map.mapping.map.type.raw=Add type to Map for mapping Map to Bean
3839
intention.wrong.map.mapping.map.key=Use Map with key of type String for mapping Map to Bean
3940
intention.remove.annotation=Remove {0} annotation
4041
intention.change.target.property=Change target property
42+
intention.replace.source.property=Replace source ''.'' with ''{0}''
4143
plugin.settings.title=MapStruct
4244
plugin.settings.quickFix.title=Quick fix properties
4345
plugin.settings.quickFix.preferSourceBeforeTargetInMapping=Prefer source before target in @Mapping
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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.codeInsight.intention.IntentionAction;
9+
import com.intellij.codeInspection.LocalInspectionTool;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
import java.util.List;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
16+
/**
17+
* @author hduelme
18+
*/
19+
public class ThisUsedAsSourcePropertyInspectionTest extends BaseInspectionTest {
20+
@Override
21+
protected @NotNull Class<? extends LocalInspectionTool> getInspection() {
22+
return ThisUsedAsSourcePropertyInspection.class;
23+
}
24+
25+
public void testThisUsedAsSourcePropertyInspection() {
26+
doTest();
27+
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();
28+
29+
assertThat( allQuickFixes )
30+
.extracting( IntentionAction::getText )
31+
.as( "Intent Text" )
32+
.containsExactlyInAnyOrder(
33+
"Replace source '.' with 'source'",
34+
"Replace source '.' with 'source'",
35+
"Replace source '.' with 'source'",
36+
"Replace source '.' with 'age'"
37+
);
38+
39+
myFixture.launchAction( allQuickFixes.get( 0 ) );
40+
myFixture.launchAction( allQuickFixes.get( 1 ) );
41+
myFixture.launchAction( allQuickFixes.get( 2 ) );
42+
String testName = getTestName( false );
43+
myFixture.checkResultByFile( testName + "_after.java" );
44+
}
45+
}

0 commit comments

Comments
 (0)