Skip to content

Commit 40d6f59

Browse files
authored
#243: Do not warn for unkown source properties on maps (#244)
When Map<String, String> is used as the sole source parameter, map mapping is applied. Therefore, there must be no references to source properties.
1 parent 3de787e commit 40d6f59

9 files changed

Lines changed: 187 additions & 7 deletions

File tree

src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public abstract class BaseReference extends PsiReferenceBase<PsiElement> impleme
4343
* @return The mapping method that this reference belongs to
4444
*/
4545
@Nullable
46-
PsiMethod getMappingMethod() {
46+
public PsiMethod getMappingMethod() {
4747
PsiElement element = getElement();
4848
UExpression expression = UastContextKt.toUElement( element, UExpression.class );
4949
if ( expression != null ) {

src/main/java/org/mapstruct/intellij/codeinsight/references/BaseValueMappingReference.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public final PsiElement resolve() {
4747

4848
@Override
4949
@Nullable
50-
PsiMethod getMappingMethod() {
50+
public PsiMethod getMappingMethod() {
5151
PsiMethod mappingMethod = super.getMappingMethod();
5252
if ( isNotValueMapping( mappingMethod ) ) {
5353
return null;

src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructBaseReference.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ public final PsiElement resolve() {
6969
}
7070

7171
if ( previous != null ) {
72-
PsiType psiType = canDescendIntoType( previous.resolvedType() ) ? previous.resolvedType() : null;
72+
PsiType previousResolvedType = previous.resolvedType();
73+
PsiType psiType = supportsNestedReferenceForType( previousResolvedType ) ? previousResolvedType : null;
7374
return psiType == null ? null : resolveInternal( value, psiType );
7475
}
7576

@@ -78,6 +79,10 @@ public final PsiElement resolve() {
7879
return mappingMethod == null ? null : resolveInternal( value, mappingMethod );
7980
}
8081

82+
protected boolean supportsNestedReferenceForType(@Nullable PsiType psiType) {
83+
return canDescendIntoType( psiType );
84+
}
85+
8186
/**
8287
* Resolved the reference from the {@code value} for the reference {@code psiClass}
8388
*

src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructSourceReference.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ PsiElement resolveInternal(@NotNull String value, @NotNull PsiType psiType) {
5757
return null;
5858
}
5959

60+
if ( MapstructUtil.isMapWithStringKeyType( psiType ) ) {
61+
return psiClass;
62+
}
63+
6064
PsiRecordComponent recordComponent = findRecordComponent( value, psiClass );
6165
if ( recordComponent != null ) {
6266
return recordComponent;
@@ -78,6 +82,11 @@ PsiElement resolveInternal(@NotNull String value, @NotNull PsiType psiType) {
7882
return null;
7983
}
8084

85+
@Override
86+
protected boolean supportsNestedReferenceForType(@Nullable PsiType psiType) {
87+
return super.supportsNestedReferenceForType( psiType ) || MapstructUtil.isMapWithStringKeyType( psiType );
88+
}
89+
8190
@Override
8291
PsiElement resolveInternal(@NotNull String value, @NotNull PsiMethod mappingMethod) {
8392
PsiParameter[] sourceParameters = MapstructUtil.getSourceParameters( mappingMethod );

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,28 @@
1313
import com.intellij.psi.PsiElement;
1414
import com.intellij.psi.PsiElementVisitor;
1515
import com.intellij.psi.PsiLanguageInjectionHost;
16+
import com.intellij.psi.PsiMethod;
17+
import com.intellij.psi.PsiParameter;
1618
import com.intellij.psi.PsiReference;
19+
import com.intellij.psi.PsiType;
1720
import com.intellij.psi.util.PsiTreeUtil;
1821
import org.jetbrains.annotations.NotNull;
22+
import org.jetbrains.annotations.Nullable;
1923
import org.mapstruct.intellij.codeinsight.references.BaseReference;
2024
import org.mapstruct.intellij.codeinsight.references.BaseValueMappingReference;
25+
import org.mapstruct.intellij.util.MapstructUtil;
2126

2227
/**
2328
* Inspection that checks if mapstruct references can be resolved.
24-
* @see BaseReference
29+
*
2530
* @author hduelme
31+
* @see BaseReference
2632
*/
2733
public class MapstructReferenceInspection extends InspectionBase {
2834

2935
@Override
3036
@NotNull PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
31-
return new MapstructReferenceVisitor(holder);
37+
return new MapstructReferenceVisitor( holder );
3238
}
3339

3440
private static class MapstructReferenceVisitor extends PsiElementVisitor {
@@ -50,8 +56,10 @@ public void visitElement(@NotNull PsiElement element) {
5056
TextRange range = psiReference.getRangeInElement();
5157
if ( range.isEmpty() && range.getStartOffset() == 1 && "\"\"".equals( element.getText() ) ) {
5258
String message = ProblemsHolder.unresolvedReferenceMessage( baseReference );
53-
holder.registerProblem( element, message, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL,
54-
TextRange.create( 0, 2 ) );
59+
holder.registerProblem(
60+
element, message, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL,
61+
TextRange.create( 0, 2 )
62+
);
5563
}
5664
else if ( shouldRegisterProblem( baseReference ) ) {
5765
holder.registerProblem( psiReference );
@@ -67,9 +75,28 @@ private boolean shouldRegisterProblem(BaseReference reference) {
6775
return valueMappingReference.getEnumClass() != null;
6876
}
6977

78+
if ( hasSingleStringKeyMapSourceParameter( reference.getMappingMethod() ) ) {
79+
// MapStruct allows source values as map keys, even if they are not resolvable as Java properties.
80+
// Therefore, we don't report an unresolved reference problem here.
81+
return false;
82+
}
83+
7084
return !containingClassIsAnnotationType( reference.getElement() );
7185
}
7286

87+
private boolean hasSingleStringKeyMapSourceParameter(@Nullable PsiMethod mappingMethod) {
88+
89+
if ( mappingMethod != null ) {
90+
PsiParameter[] parameters = MapstructUtil.getSourceParameters( mappingMethod );
91+
if ( parameters.length == 1 ) {
92+
PsiType parameterType = parameters[0].getType();
93+
return MapstructUtil.isMapWithStringKeyType( parameterType );
94+
}
95+
}
96+
97+
return false;
98+
}
99+
73100
private boolean containingClassIsAnnotationType(PsiElement element) {
74101

75102
PsiClass containingClass = PsiTreeUtil.getParentOfType( element, PsiClass.class );

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,4 +659,33 @@ public static boolean isInheritInverseConfiguration(PsiMethod method) {
659659
return isAnnotated( method, INHERIT_INVERSE_CONFIGURATION_FQN, AnnotationUtil.CHECK_TYPE );
660660
}
661661

662+
/**
663+
* Checks if the given type is a {@link java.util.Map} with a {@link String} key type.
664+
*
665+
* @param type to be checked
666+
* @return {@code true} if the {@code type} is a {@link java.util.Map} with a {@link String} key type,
667+
* {@code false} otherwise
668+
*/
669+
public static boolean isMapWithStringKeyType(@Nullable PsiType type) {
670+
if ( type == null ) {
671+
return false;
672+
}
673+
674+
PsiClass psiClass = PsiUtil.resolveClassInType( type );
675+
if ( psiClass == null ||
676+
!CommonClassNames.JAVA_UTIL_MAP.equals( psiClass.getQualifiedName() ) ) {
677+
return false;
678+
}
679+
680+
if ( !( type instanceof PsiClassType ct ) ) {
681+
return false;
682+
}
683+
684+
PsiType[] parameters = ct.getParameters();
685+
if ( parameters.length != 2 ) {
686+
return false;
687+
}
688+
689+
return parameters[0].equalsToText( CommonClassNames.JAVA_LANG_STRING );
690+
}
662691
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.bugs._243;
7+
8+
import org.jetbrains.annotations.NotNull;
9+
import org.mapstruct.intellij.inspection.BaseInspectionTest;
10+
import org.mapstruct.intellij.inspection.MapstructReferenceInspection;
11+
12+
/**
13+
* @author Oliver Erhart
14+
*/
15+
public class DisableSourcePropertyInspectionOnMapTest extends BaseInspectionTest {
16+
17+
@Override
18+
protected String getTestDataPath() {
19+
return "testData/bugs/_243";
20+
}
21+
22+
@NotNull
23+
@Override
24+
protected Class<MapstructReferenceInspection> getInspection() {
25+
return MapstructReferenceInspection.class;
26+
}
27+
28+
public void testDisableSourcePropertyInspectionOnMap() {
29+
doTest();
30+
}
31+
32+
public void testDisableSourcePropertyInspectionOnMapErroneous() {
33+
doTest();
34+
}
35+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 java.time.LocalDate;
8+
import java.util.Map;
9+
10+
import org.mapstruct.Mapper;
11+
import org.mapstruct.Mapping;
12+
import org.mapstruct.MappingTarget;
13+
14+
@Mapper
15+
abstract class Issue243Mapper {
16+
17+
@Mapping(source = "exDate", target = "exDate")
18+
@Mapping(source = "payDate", target = "payDate")
19+
public abstract CorporateAction mapWithStringKeyMap(Map<String, String> rowValues);
20+
21+
@Mapping(source = "exDate", target = "exDate")
22+
public abstract void updateWithStringKeyMap(Map<String, String> rowValues, @MappingTarget CorporateAction target);
23+
24+
@Mapping(source = "exDate", target = "exDate")
25+
public abstract CorporateAction mapWithObjectValueMap(Map<String, Object> rowValues);
26+
27+
@Mapping(source = "rowValues.exDate", target = "exDate")
28+
@Mapping(source = "payDate", target = "payDate")
29+
public abstract CorporateAction mapWithMultipleSourcesAndMapName(
30+
Map<String, String> rowValues,
31+
LocalDate payDate
32+
);
33+
}
34+
35+
class CorporateAction {
36+
public LocalDate exDate;
37+
public LocalDate payDate;
38+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 java.time.LocalDate;
8+
import java.util.Map;
9+
10+
import org.mapstruct.Mapper;
11+
import org.mapstruct.Mapping;
12+
import org.mapstruct.MappingTarget;
13+
14+
@Mapper
15+
abstract class Issue243ErroneousMapper {
16+
17+
@Mapping(source = "<error descr="Unknown property 'exDate'">exDate</error>", target = "exDate")
18+
public abstract CorporateAction mapWithIntegerKeyMap(Map<Integer, String> rowValues);
19+
20+
@Mapping(source = "<error descr="Unknown property 'exDate'">exDate</error>", target = "exDate")
21+
public abstract void updateWithIntegerKeyMap(
22+
Map<Integer, String> rowValues,
23+
@MappingTarget CorporateAction target
24+
);
25+
26+
@Mapping(source = "<error descr="Unknown property 'exDate'">exDate</error>", target = "exDate")
27+
@Mapping(source = "payDate", target = "payDate")
28+
public abstract CorporateAction mapWithMultipleSources(
29+
Map<String, String> rowValues,
30+
LocalDate payDate
31+
);
32+
}
33+
34+
class CorporateAction {
35+
public LocalDate exDate;
36+
public LocalDate payDate;
37+
}

0 commit comments

Comments
 (0)