From 7ce36a42ce5d48d34b7adce99a76c3f1273c423f Mon Sep 17 00:00:00 2001 From: hduelme Date: Wed, 13 Dec 2023 23:22:09 +0100 Subject: [PATCH 1/7] support Map as MappingSource --- .../FromMapMappingMapTypeInspection.java | 190 ++++++++++++++++++ .../UnmappedTargetPropertiesInspection.java | 37 +--- .../mapstruct/intellij/util/SourceUtils.java | 21 ++ .../mapstruct/intellij/util/TargetUtils.java | 28 +++ src/main/resources/META-INF/plugin.xml | 8 + .../FromMapMappingInspection.html | 33 +++ .../messages/MapStructBundle.properties | 7 + .../FromMapMappingMapTypeInspectionTest.java | 61 ++++++ ...argetPropertiesWithFromMapMappingTest.java | 43 ++++ ...FromMapMappingMapTypeInspectionRawMap.java | 76 +++++++ ...pMappingMapTypeInspectionRawMap_after.java | 76 +++++++ ...pMappingMapTypeInspectionWrongKeyType.java | 76 +++++++ ...ngMapTypeInspectionWrongKeyType_after.java | 76 +++++++ ...pedTargetPropertiesWithFromMapMapping.java | 25 +++ 14 files changed, 726 insertions(+), 31 deletions(-) create mode 100644 src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java create mode 100644 src/main/resources/inspectionDescriptions/FromMapMappingInspection.html create mode 100644 src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java create mode 100644 src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java create mode 100644 testData/inspection/FromMapMappingMapTypeInspectionRawMap.java create mode 100644 testData/inspection/FromMapMappingMapTypeInspectionRawMap_after.java create mode 100644 testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java create mode 100644 testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType_after.java create mode 100644 testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java diff --git a/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java b/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java new file mode 100644 index 00000000..36f71edd --- /dev/null +++ b/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java @@ -0,0 +1,190 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.intellij.inspection; + +import com.intellij.codeInspection.LocalQuickFixOnPsiElement; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.codeInspection.util.IntentionName; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaElementVisitor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiParameter; +import com.intellij.psi.PsiType; +import com.intellij.psi.impl.source.PsiClassReferenceType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mapstruct.intellij.MapStructBundle; +import org.mapstruct.intellij.util.MapstructUtil; + +import static com.intellij.psi.PsiElementFactory.getInstance; +import static org.mapstruct.intellij.util.SourceUtils.getFromMapMappingParameter; +import static org.mapstruct.intellij.util.TargetUtils.getTargetType; + +/** + * @author hduelme + */ +public class FromMapMappingMapTypeInspection extends InspectionBase { + + @NotNull + @Override + PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new MyJavaElementVisitor( holder ); + } + + private static class MyJavaElementVisitor extends JavaElementVisitor { + private final ProblemsHolder holder; + + private MyJavaElementVisitor(ProblemsHolder holder) { + this.holder = holder; + } + + @Override + public void visitMethod(@NotNull PsiMethod method) { + super.visitMethod( method ); + + if (!MapstructUtil.isMapper( method.getContainingClass() ) ) { + return; + } + + PsiType targetType = getTargetType( method ); + if (targetType == null) { + return; + } + + PsiParameter fromMapMappingParameter = getFromMapMappingParameter( method ); + PsiType[] parameters = getParameters( fromMapMappingParameter ); + if ( parameters == null ) { + return; + } + if (parameters.length == 0) { + // handle raw type + holder.registerProblem( fromMapMappingParameter, + MapStructBundle.message( "inspection.wrong.map.mapping.map.type.raw" ), + new ReplaceByStringStringMapTypeFix( fromMapMappingParameter ) ); + } + else if (parameters.length == 2) { + // only if both parameters of the map are set + PsiType keyParameter = parameters[0]; + if ( !keyParameter.equalsToText( "java.lang.String" ) ) { + // handle wrong map key type + holder.registerProblem( fromMapMappingParameter, + MapStructBundle.message( "inspection.wrong.map.mapping.map.key" ), + new ReplaceMapKeyByStringTypeFix( fromMapMappingParameter ) ); + } + } + } + + @Nullable + private static PsiType[] getParameters(@Nullable PsiParameter fromMapMappingParameter) { + if (fromMapMappingParameter == null || + !(fromMapMappingParameter.getType() instanceof PsiClassReferenceType)) { + return null; + } + return ((PsiClassReferenceType) fromMapMappingParameter.getType()).getParameters(); + } + + private static class ReplaceByStringStringMapTypeFix extends LocalQuickFixOnPsiElement { + + private final String text; + + private ReplaceByStringStringMapTypeFix(@NotNull PsiParameter element) { + super( element ); + this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.type.raw.set.default", + element.getType().getPresentableText() ); + } + + @Override + public @IntentionName @NotNull String getText() { + return text; + } + + @Override + public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file, + @NotNull PsiElement startElement, @NotNull PsiElement endElement) { + if (!super.isAvailable( project, file, startElement, endElement ) ) { + return false; + } + if (startElement instanceof PsiParameter) { + PsiParameter parameter = (PsiParameter) startElement; + PsiType[] parameters = getParameters( parameter ); + return parameters != null && parameters.length == 0; + } + return false; + } + + @Override + public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @NotNull PsiElement psiElement, + @NotNull PsiElement psiElement1) { + if (psiElement instanceof PsiParameter) { + String mapText = psiElement.getText(); + String prefix = mapText.substring( 0, mapText.indexOf( ' ' ) ); + String end = mapText.substring( mapText.lastIndexOf( ' ' ) ); + String result = prefix + "" + end; + psiElement.replace( getInstance( project ).createParameterFromText( result, psiElement ) ); + } + } + + @Override + public @IntentionFamilyName @NotNull String getFamilyName() { + return MapStructBundle.message( "intention.wrong.map.mapping.map.type.raw" ); + } + } + + private static class ReplaceMapKeyByStringTypeFix extends LocalQuickFixOnPsiElement { + + private final String text; + + private ReplaceMapKeyByStringTypeFix(@NotNull PsiParameter element) { + super( element ); + this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.key.change.to.String" ); + } + + @Override + public @IntentionName @NotNull String getText() { + return text; + } + + @Override + public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file, + @NotNull PsiElement startElement, @NotNull PsiElement endElement) { + if (!super.isAvailable( project, file, startElement, endElement ) ) { + return false; + } + if (startElement instanceof PsiParameter) { + PsiParameter parameter = (PsiParameter) startElement; + PsiType[] parameters = getParameters( parameter ); + if (parameters == null || parameters.length != 2) { + return false; + } + return !parameters[0].equalsToText( "java.lang.String" ); + } + return false; + } + + @Override + public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @NotNull PsiElement psiElement, + @NotNull PsiElement psiElement1) { + if (psiElement instanceof PsiParameter) { + String mapText = psiElement.getText(); + String prefix = mapText.substring( 0, mapText.indexOf( '<' ) + 1 ); + String end = mapText.substring( mapText.indexOf( ',' ) ); + String result = prefix + "String" + end; + psiElement.replace( getInstance( project ).createParameterFromText( result, psiElement ) ); + } + } + + @Override + public @IntentionFamilyName @NotNull String getFamilyName() { + return MapStructBundle.message( "intention.wrong.map.mapping.map.key" ); + } + } + } + +} diff --git a/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java b/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java index dcaa9677..7416df56 100644 --- a/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java +++ b/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java @@ -21,18 +21,15 @@ import com.intellij.psi.JavaElementVisitor; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiType; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.mapstruct.ReportingPolicy; import org.mapstruct.intellij.MapStructBundle; import org.mapstruct.intellij.settings.ProjectSettings; @@ -44,14 +41,12 @@ import static org.mapstruct.intellij.inspection.inheritance.InheritConfigurationUtils.findInheritedTargetProperties; import static org.mapstruct.intellij.util.MapstructAnnotationUtils.addMappingAnnotation; import static org.mapstruct.intellij.util.MapstructAnnotationUtils.getUnmappedTargetPolicy; -import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration; -import static org.mapstruct.intellij.util.MapstructUtil.isMapper; -import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig; import static org.mapstruct.intellij.util.SourceUtils.findAllSourceProperties; +import static org.mapstruct.intellij.util.SourceUtils.isFromMapMapping; import static org.mapstruct.intellij.util.TargetUtils.findAllDefinedMappingTargets; import static org.mapstruct.intellij.util.TargetUtils.findAllSourcePropertiesForCurrentTarget; import static org.mapstruct.intellij.util.TargetUtils.findAllTargetProperties; -import static org.mapstruct.intellij.util.TargetUtils.getRelevantType; +import static org.mapstruct.intellij.util.TargetUtils.getTargetType; /** * Inspection that checks if there are unmapped target properties. @@ -91,6 +86,10 @@ public void visitMethod(PsiMethod method) { return; } + if ( isFromMapMapping( method ) ) { + return; + } + ReportingPolicy reportingPolicy = getUnmappedTargetPolicy( method ); if (reportingPolicy == ReportingPolicy.IGNORE) { return; @@ -185,30 +184,6 @@ private static boolean isBeanMappingIgnoreByDefault(PsiMethod method) { return false; } - /** - * @param method the method to be used - * - * @return the target class for the inspection, or {@code null} if no inspection needs to be performed - */ - @Nullable - private static PsiType getTargetType(PsiMethod method) { - if ( !method.getModifierList().hasModifierProperty( PsiModifier.ABSTRACT ) ) { - return null; - } - - if ( isInheritInverseConfiguration( method ) ) { - return null; - } - PsiClass containingClass = method.getContainingClass(); - - if ( containingClass == null - || method.getNameIdentifier() == null - || !( isMapper( containingClass ) || isMapperConfig( containingClass ) ) ) { - return null; - } - return getRelevantType( method ); - } - } private static class UnmappedTargetPropertyFix extends LocalQuickFixOnPsiElement { diff --git a/src/main/java/org/mapstruct/intellij/util/SourceUtils.java b/src/main/java/org/mapstruct/intellij/util/SourceUtils.java index f78faf10..bc401d54 100644 --- a/src/main/java/org/mapstruct/intellij/util/SourceUtils.java +++ b/src/main/java/org/mapstruct/intellij/util/SourceUtils.java @@ -10,9 +10,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.intellij.codeInsight.AnnotationUtil; @@ -211,4 +213,23 @@ else if ( methodName.startsWith( "is" ) && ( } } + + public static boolean isFromMapMapping(@NotNull PsiMethod method) { + return getFromMapMappingParameter( method ) != null; + } + + @Nullable + public static PsiParameter getFromMapMappingParameter(@NotNull PsiMethod method) { + List sourceParameters = Arrays.stream( method.getParameterList().getParameters() ) + .filter( MapstructUtil::isValidSourceParameter ).collect( Collectors.toList() ); + if (sourceParameters.size() == 1) { + PsiParameter parameter = sourceParameters.get( 0 ); + if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(), + method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) { + return parameter; + } + + } + return null; + } } diff --git a/src/main/java/org/mapstruct/intellij/util/TargetUtils.java b/src/main/java/org/mapstruct/intellij/util/TargetUtils.java index 33fa861e..4c42d55f 100644 --- a/src/main/java/org/mapstruct/intellij/util/TargetUtils.java +++ b/src/main/java/org/mapstruct/intellij/util/TargetUtils.java @@ -29,6 +29,7 @@ import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiMember; import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiSubstitutor; @@ -45,6 +46,9 @@ import static org.mapstruct.intellij.util.MapstructUtil.MAPPER_ANNOTATION_FQN; import static org.mapstruct.intellij.util.MapstructUtil.canDescendIntoType; import static org.mapstruct.intellij.util.MapstructUtil.isFluentSetter; +import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration; +import static org.mapstruct.intellij.util.MapstructUtil.isMapper; +import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig; import static org.mapstruct.intellij.util.MapstructUtil.publicFields; /** @@ -433,4 +437,28 @@ public static Set findAllTargetProperties(@NotNull PsiType targetType, M return publicWriteAccessors( targetType, mapStructVersion, mappingMethod ).keySet(); } + /** + * @param method the method to be used + * + * @return the target class for the inspection, or {@code null} if no inspection needs to be performed + */ + @Nullable + public static PsiType getTargetType( @NotNull PsiMethod method) { + if ( !method.getModifierList().hasModifierProperty( PsiModifier.ABSTRACT ) ) { + return null; + } + + if ( isInheritInverseConfiguration( method ) ) { + return null; + } + PsiClass containingClass = method.getContainingClass(); + + if ( containingClass == null + || method.getNameIdentifier() == null + || !( isMapper( containingClass ) || isMapperConfig( containingClass ) ) ) { + return null; + } + return getRelevantType( method ); + } + } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index b35a58f4..62863a55 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -112,6 +112,14 @@ key="inspection.java.expression.unnecessary.whitespace.title" shortName="JavaExpressionUnnecessaryWhitespaces" implementationClass="org.mapstruct.intellij.inspection.JavaExpressionUnnecessaryWhitespacesInspector"/> + diff --git a/src/main/resources/inspectionDescriptions/FromMapMappingInspection.html b/src/main/resources/inspectionDescriptions/FromMapMappingInspection.html new file mode 100644 index 00000000..c8cd215c --- /dev/null +++ b/src/main/resources/inspectionDescriptions/FromMapMappingInspection.html @@ -0,0 +1,33 @@ + + +

+ This inspection reports when a raw Map or a Map where the key isn't a String + is used for mapping to Bean. +

+

+


+//wrong
+@Mapper
+public interface EmployeeMapper {
+
+    Employee toEmployee(Map map);
+
+    EmployeeDto toEmployeeDto(Map<Long, ?> map);
+}
+
+

+

+


+//correct
+@Mapper
+public interface EmployeeMapper {
+
+    Employee toEmployee(Map<String String> map);
+
+    EmployeeDto toEmployeeDto(Map<String, ?> map);
+}
+
+

+ + + \ No newline at end of file diff --git a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties index 5f175e06..03817c44 100644 --- a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties +++ b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties @@ -19,6 +19,11 @@ inspection.not.null.checkable.property.source.used.with.default.property.title=C inspection.java.expression.unnecessary.whitespace=Unnecessary whitespaces {0} {1} inspection.java.expression.remove.unnecessary.whitespace=Remove unnecessary whitespaces {0} {1} inspection.java.expression.unnecessary.whitespace.title=Unnecessary whitespaces before or after Java expression +inspection.wrong.map.mapping.map.type=Map type is raw or key type is not string for mapping Map to Bean +inspection.wrong.map.mapping.map.type.raw=Raw map used for mapping Map to Bean +inspection.wrong.map.mapping.map.type.raw.set.default=Replace {0} with {0} +inspection.wrong.map.mapping.map.key=Key must be of type String for mapping Map to Bean +inspection.wrong.map.mapping.map.key.change.to.String=Change key type to String intention.add.ignore.all.unmapped.target.properties=Add ignore all unmapped target properties intention.add.ignore.unmapped.target.property=Add ignore unmapped target property intention.add.unmapped.target.property=Add unmapped target property @@ -27,6 +32,8 @@ intention.more.than.one.source.property=Only use one source property intention.more.than.one.default.source.property=Only use one default source property intention.not.null.checkable.property.source.used.with.default.property=Remove default properties intention.java.expression.remove.unnecessary.whitespace=Remove unnecessary whitespaces +intention.wrong.map.mapping.map.type.raw=Add type to Map for mapping Map to Bean +intention.wrong.map.mapping.map.key=Use Map with key of type String for mapping Map to Bean plugin.settings.title=MapStruct plugin.settings.quickFix.title=Quick fix properties plugin.settings.quickFix.preferSourceBeforeTargetInMapping=Prefer source before target in @Mapping diff --git a/src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java b/src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java new file mode 100644 index 00000000..bc223234 --- /dev/null +++ b/src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java @@ -0,0 +1,61 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.intellij.inspection; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.LocalInspectionTool; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author hduelme + */ +public class FromMapMappingMapTypeInspectionTest extends BaseInspectionTest { + + @Override + protected @NotNull Class getInspection() { + return FromMapMappingMapTypeInspection.class; + } + + public void testFromMapMappingMapTypeInspectionRawMap() { + doTest(); + String testName = getTestName( false ); + List allQuickFixes = myFixture.getAllQuickFixes(); + + assertThat( allQuickFixes ) + .extracting( IntentionAction::getText ) + .as( "Intent Text" ) + .containsExactly( + "Replace Map with Map", + "Replace HashMap with HashMap", + "Replace Map with Map", + "Replace HashMap with HashMap" + ); + + allQuickFixes.forEach( myFixture::launchAction ); + myFixture.checkResultByFile( testName + "_after.java" ); + } + + public void testFromMapMappingMapTypeInspectionWrongKeyType() { + doTest(); + String testName = getTestName( false ); + List allQuickFixes = myFixture.getAllQuickFixes(); + + assertThat( allQuickFixes ) + .extracting( IntentionAction::getText ) + .as( "Intent Text" ) + .containsExactly( + "Change key type to String", "Change key type to String", + "Change key type to String", "Change key type to String" + ); + + allQuickFixes.forEach( myFixture::launchAction ); + myFixture.checkResultByFile( testName + "_after.java" ); + } +} diff --git a/src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java b/src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java new file mode 100644 index 00000000..54bbd6df --- /dev/null +++ b/src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.intellij.inspection; + +import com.intellij.codeInsight.intention.IntentionAction; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author hduelme + */ +public class UnmappedTargetPropertiesWithFromMapMappingTest extends BaseInspectionTest { + + @NotNull + @Override + protected Class getInspection() { + return UnmappedTargetPropertiesInspection.class; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + myFixture.copyFileToProject( + "UnmappedTargetPropertiesData.java", + "org/example/data/UnmappedTargetPropertiesData.java" + ); + } + + /** + * Tests if no unmapped target properties warnings are generated when source is map + */ + public void testUnmappedTargetPropertiesWithFromMapMapping() { + doTest(); + List allQuickFixes = myFixture.getAllQuickFixes(); + assertThat( allQuickFixes ).isEmpty(); + } +} diff --git a/testData/inspection/FromMapMappingMapTypeInspectionRawMap.java b/testData/inspection/FromMapMappingMapTypeInspectionRawMap.java new file mode 100644 index 00000000..2ee2e297 --- /dev/null +++ b/testData/inspection/FromMapMappingMapTypeInspectionRawMap.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +import java.util.HashMap; +import java.util.Map; + +class Target { + + private String name; + private String lastName; + private String city; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +interface NotMapStructMapper { + + Target map(Map source); +} + +@Mapper +interface NoMappingMapper { + + Target map(Map source); + + Target map(HashMap source); +} + +@Mapper +interface MultiSourceMappingsMapper { + + Target mapWithAllMapping(Map source, String moreTarget, String testName); +} + +@Mapper +interface UpdateMapper { + + void update(@MappingTarget Target target, Map source); + + void update(@MappingTarget Target target, HashMap source); +} + +@Mapper +interface MultiSourceUpdateMapper { + + void update(@MappingTarget Target moreTarget, Map source, String testName, @Context String matching); +} + +@Mapper +interface DefaultMapper { + + default Target map(Map source) { + return null; + } +} + +@Mapper +abstract class AbstractMapperWithoutAbstractMethod { + + protected Target map(Map source) { + return null; + } +} diff --git a/testData/inspection/FromMapMappingMapTypeInspectionRawMap_after.java b/testData/inspection/FromMapMappingMapTypeInspectionRawMap_after.java new file mode 100644 index 00000000..52e12b61 --- /dev/null +++ b/testData/inspection/FromMapMappingMapTypeInspectionRawMap_after.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +import java.util.HashMap; +import java.util.Map; + +class Target { + + private String name; + private String lastName; + private String city; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +interface NotMapStructMapper { + + Target map(Map source); +} + +@Mapper +interface NoMappingMapper { + + Target map(Map source); + + Target map(HashMap source); +} + +@Mapper +interface MultiSourceMappingsMapper { + + Target mapWithAllMapping(Map source, String moreTarget, String testName); +} + +@Mapper +interface UpdateMapper { + + void update(@MappingTarget Target target, Map source); + + void update(@MappingTarget Target target, HashMap source); +} + +@Mapper +interface MultiSourceUpdateMapper { + + void update(@MappingTarget Target moreTarget, Map source, String testName, @Context String matching); +} + +@Mapper +interface DefaultMapper { + + default Target map(Map source) { + return null; + } +} + +@Mapper +abstract class AbstractMapperWithoutAbstractMethod { + + protected Target map(Map source) { + return null; + } +} diff --git a/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java b/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java new file mode 100644 index 00000000..9e7a082f --- /dev/null +++ b/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +import java.util.HashMap; +import java.util.Map; + +class Target { + + private String name; + private String lastName; + private String city; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +interface NotMapStructMapper { + + Target map(Map source); +} + +@Mapper +interface NoMappingMapper { + + Target map(Map source); + + Target map(HashMap source); +} + +@Mapper +interface MultiSourceMappingsMapper { + + Target mapWithAllMapping(Map source, String moreTarget, String testName); +} + +@Mapper +interface UpdateMapper { + + void update(@MappingTarget Target target, Map source); + + void update(@MappingTarget Target target, HashMap source); +} + +@Mapper +interface MultiSourceUpdateMapper { + + void update(@MappingTarget Target moreTarget, Map source, String testName, @Context String matching); +} + +@Mapper +interface DefaultMapper { + + default Target map(Map source) { + return null; + } +} + +@Mapper +abstract class AbstractMapperWithoutAbstractMethod { + + protected Target map(Map source) { + return null; + } +} diff --git a/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType_after.java b/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType_after.java new file mode 100644 index 00000000..138d2c2f --- /dev/null +++ b/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType_after.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +import java.util.HashMap; +import java.util.Map; + +class Target { + + private String name; + private String lastName; + private String city; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +interface NotMapStructMapper { + + Target map(Map source); +} + +@Mapper +interface NoMappingMapper { + + Target map(Map source); + + Target map(HashMap source); +} + +@Mapper +interface MultiSourceMappingsMapper { + + Target mapWithAllMapping(Map source, String moreTarget, String testName); +} + +@Mapper +interface UpdateMapper { + + void update(@MappingTarget Target target, Map source); + + void update(@MappingTarget Target target, HashMap source); +} + +@Mapper +interface MultiSourceUpdateMapper { + + void update(@MappingTarget Target moreTarget, Map source, String testName, @Context String matching); +} + +@Mapper +interface DefaultMapper { + + default Target map(Map source) { + return null; + } +} + +@Mapper +abstract class AbstractMapperWithoutAbstractMethod { + + protected Target map(Map source) { + return null; + } +} diff --git a/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java b/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java new file mode 100644 index 00000000..3f662a2e --- /dev/null +++ b/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.example.data.UnmappedTargetPropertiesData.Target; + +import java.util.Map; + +@Mapper +interface SinglMapper { + + Target map(Map source); +} +@Mapper +abstract class AbstractMapperWitAbstractMethod { + + abstract Target map(Map source); +} From 02b12a6d8b03b18257afd54e9f6752eee56526a1 Mon Sep 17 00:00:00 2001 From: hduelme Date: Wed, 13 Dec 2023 23:42:43 +0100 Subject: [PATCH 2/7] use getSourceParameters --- .../java/org/mapstruct/intellij/util/SourceUtils.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mapstruct/intellij/util/SourceUtils.java b/src/main/java/org/mapstruct/intellij/util/SourceUtils.java index bc401d54..7b11ccb5 100644 --- a/src/main/java/org/mapstruct/intellij/util/SourceUtils.java +++ b/src/main/java/org/mapstruct/intellij/util/SourceUtils.java @@ -10,11 +10,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import com.intellij.codeInsight.AnnotationUtil; @@ -220,15 +218,13 @@ public static boolean isFromMapMapping(@NotNull PsiMethod method) { @Nullable public static PsiParameter getFromMapMappingParameter(@NotNull PsiMethod method) { - List sourceParameters = Arrays.stream( method.getParameterList().getParameters() ) - .filter( MapstructUtil::isValidSourceParameter ).collect( Collectors.toList() ); - if (sourceParameters.size() == 1) { - PsiParameter parameter = sourceParameters.get( 0 ); + PsiParameter[] sourceParameters = getSourceParameters( method ); + if (sourceParameters.length == 1) { + PsiParameter parameter = sourceParameters[0]; if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(), method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) { return parameter; } - } return null; } From 0a785e59726541295986d6594912b2878fea4cb3 Mon Sep 17 00:00:00 2001 From: hduelme Date: Wed, 27 Dec 2023 16:57:28 +0100 Subject: [PATCH 3/7] fix typo --- .../inspection/UnmappedTargetPropertiesWithFromMapMapping.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java b/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java index 3f662a2e..eb2dc551 100644 --- a/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java +++ b/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java @@ -14,7 +14,7 @@ import java.util.Map; @Mapper -interface SinglMapper { +interface SingleMapper { Target map(Map source); } From 9342d1e12a7afccd3c9427b6fc6c799c63d3fc56 Mon Sep 17 00:00:00 2001 From: hduelme Date: Wed, 27 Dec 2023 16:58:59 +0100 Subject: [PATCH 4/7] use lowercase s --- .../intellij/inspection/FromMapMappingMapTypeInspection.java | 2 +- .../org/mapstruct/intellij/messages/MapStructBundle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java b/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java index 36f71edd..00b5f466 100644 --- a/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java +++ b/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java @@ -143,7 +143,7 @@ private static class ReplaceMapKeyByStringTypeFix extends LocalQuickFixOnPsiElem private ReplaceMapKeyByStringTypeFix(@NotNull PsiParameter element) { super( element ); - this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.key.change.to.String" ); + this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.key.change.to.string" ); } @Override diff --git a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties index 03817c44..98252a11 100644 --- a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties +++ b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties @@ -23,7 +23,7 @@ inspection.wrong.map.mapping.map.type=Map type is raw or key type is not string inspection.wrong.map.mapping.map.type.raw=Raw map used for mapping Map to Bean inspection.wrong.map.mapping.map.type.raw.set.default=Replace {0} with {0} inspection.wrong.map.mapping.map.key=Key must be of type String for mapping Map to Bean -inspection.wrong.map.mapping.map.key.change.to.String=Change key type to String +inspection.wrong.map.mapping.map.key.change.to.string=Change key type to String intention.add.ignore.all.unmapped.target.properties=Add ignore all unmapped target properties intention.add.ignore.unmapped.target.property=Add ignore unmapped target property intention.add.unmapped.target.property=Add unmapped target property From 0e21cc2e2c6923bdc56847a5883db503ab8fec1b Mon Sep 17 00:00:00 2001 From: hduelme Date: Wed, 27 Dec 2023 17:00:38 +0100 Subject: [PATCH 5/7] remove unused intention --- .../org/mapstruct/intellij/messages/MapStructBundle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties index 98252a11..c7ed3a12 100644 --- a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties +++ b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties @@ -27,7 +27,6 @@ inspection.wrong.map.mapping.map.key.change.to.string=Change key type to String intention.add.ignore.all.unmapped.target.properties=Add ignore all unmapped target properties intention.add.ignore.unmapped.target.property=Add ignore unmapped target property intention.add.unmapped.target.property=Add unmapped target property -intention.no.source.property=Add one source property intention.more.than.one.source.property=Only use one source property intention.more.than.one.default.source.property=Only use one default source property intention.not.null.checkable.property.source.used.with.default.property=Remove default properties From db0854f271f1b631d7d964026b63f086845776da Mon Sep 17 00:00:00 2001 From: hduelme Date: Wed, 27 Dec 2023 17:18:30 +0100 Subject: [PATCH 6/7] add map to map mapping no inspection test --- .../FromMapMappingMapTypeInspectionTest.java | 6 ++++ ...romMapMappingMapTypeToMapNoInspection.java | 35 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 testData/inspection/FromMapMappingMapTypeToMapNoInspection.java diff --git a/src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java b/src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java index bc223234..3f0f8ae2 100644 --- a/src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java +++ b/src/test/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspectionTest.java @@ -58,4 +58,10 @@ public void testFromMapMappingMapTypeInspectionWrongKeyType() { allQuickFixes.forEach( myFixture::launchAction ); myFixture.checkResultByFile( testName + "_after.java" ); } + + public void testFromMapMappingMapTypeToMapNoInspection() { + doTest(); + List allQuickFixes = myFixture.getAllQuickFixes(); + assertThat( allQuickFixes ).isEmpty(); + } } diff --git a/testData/inspection/FromMapMappingMapTypeToMapNoInspection.java b/testData/inspection/FromMapMappingMapTypeToMapNoInspection.java new file mode 100644 index 00000000..ec3011c2 --- /dev/null +++ b/testData/inspection/FromMapMappingMapTypeToMapNoInspection.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import java.util.HashMap; +import java.util.Map; +import java.util.Date; + +@Mapper +interface MapToMapMapper { + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + Map longDateMapToStringStringMap(Map source); + + @InheritInverseConfiguration + Map stringStringMapToLongDateMap(Map source); + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + void stringStringMapToLongDateMapUsingTargetParameter(@MappingTarget Map target, + Map source); + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + Map stringStringMapToLongDateMapUsingTargetParameterAndReturn(Map source, + @MappingTarget Map target); + + Map intIntToNumberNumberMap(Map source); +} \ No newline at end of file From b38ac06b0bc63974d144b89930c721b548d71cf6 Mon Sep 17 00:00:00 2001 From: hduelme Date: Thu, 28 Dec 2023 01:33:47 +0100 Subject: [PATCH 7/7] match mapstruct behavior --- .../FromMapMappingMapTypeInspection.java | 48 +++++++++++----- .../UnmappedTargetPropertiesInspection.java | 18 +++++- .../mapstruct/intellij/util/SourceUtils.java | 19 ++----- src/main/resources/META-INF/plugin.xml | 2 +- .../FromMapMappingMapTypeInspectionTest.java | 6 ++ ...argetPropertiesWithFromMapMappingTest.java | 35 ++++++++++++ ...FromMapMappingMapTypeInspectionRawMap.java | 8 +-- ...pMappingMapTypeInspectionWrongKeyType.java | 8 +-- ...apTypeWithSourceParameterNoInspection.java | 57 +++++++++++++++++++ ...pedTargetPropertiesWithFromMapMapping.java | 4 +- ...opertiesWithFromMapMappingMultiSource.java | 25 ++++++++ ...FromMapMappingMultiSourceWrongKeyType.java | 25 ++++++++ 12 files changed, 217 insertions(+), 38 deletions(-) create mode 100644 testData/inspection/FromMapMappingMapTypeWithSourceParameterNoInspection.java create mode 100644 testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSource.java create mode 100644 testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSourceWrongKeyType.java diff --git a/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java b/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java index 00b5f466..e72f7f8f 100644 --- a/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java +++ b/src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java @@ -17,14 +17,19 @@ import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiType; -import com.intellij.psi.impl.source.PsiClassReferenceType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mapstruct.intellij.MapStructBundle; +import org.mapstruct.intellij.util.MapStructVersion; import org.mapstruct.intellij.util.MapstructUtil; +import java.util.Set; + import static com.intellij.psi.PsiElementFactory.getInstance; -import static org.mapstruct.intellij.util.SourceUtils.getFromMapMappingParameter; +import static org.mapstruct.intellij.util.MapstructUtil.getSourceParameters; +import static org.mapstruct.intellij.util.SourceUtils.findAllDefinedMappingSources; +import static org.mapstruct.intellij.util.SourceUtils.getGenericTypes; +import static org.mapstruct.intellij.util.TargetUtils.findAllTargetProperties; import static org.mapstruct.intellij.util.TargetUtils.getTargetType; /** @@ -35,14 +40,16 @@ public class FromMapMappingMapTypeInspection extends InspectionBase { @NotNull @Override PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - return new MyJavaElementVisitor( holder ); + return new MyJavaElementVisitor( holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ) ); } private static class MyJavaElementVisitor extends JavaElementVisitor { private final ProblemsHolder holder; + private final MapStructVersion mapStructVersion; - private MyJavaElementVisitor(ProblemsHolder holder) { + private MyJavaElementVisitor(ProblemsHolder holder, MapStructVersion mapStructVersion) { this.holder = holder; + this.mapStructVersion = mapStructVersion; } @Override @@ -59,8 +66,19 @@ public void visitMethod(@NotNull PsiMethod method) { } PsiParameter fromMapMappingParameter = getFromMapMappingParameter( method ); - PsiType[] parameters = getParameters( fromMapMappingParameter ); - if ( parameters == null ) { + if (fromMapMappingParameter == null) { + return; + } + PsiType[] parameters = getGenericTypes( fromMapMappingParameter ); + if (parameters == null) { + return; + } + Set allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, method ); + if ( allTargetProperties.contains( fromMapMappingParameter.getName() ) ) { + return; + } + if ( findAllDefinedMappingSources( method, mapStructVersion ) + .anyMatch( source -> fromMapMappingParameter.getName().equals( source ) ) ) { return; } if (parameters.length == 0) { @@ -82,12 +100,16 @@ else if (parameters.length == 2) { } @Nullable - private static PsiType[] getParameters(@Nullable PsiParameter fromMapMappingParameter) { - if (fromMapMappingParameter == null || - !(fromMapMappingParameter.getType() instanceof PsiClassReferenceType)) { - return null; + private static PsiParameter getFromMapMappingParameter(@NotNull PsiMethod method) { + PsiParameter[] sourceParameters = getSourceParameters( method ); + if (sourceParameters.length == 1) { + PsiParameter parameter = sourceParameters[0]; + if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(), + method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) { + return parameter; + } } - return ((PsiClassReferenceType) fromMapMappingParameter.getType()).getParameters(); + return null; } private static class ReplaceByStringStringMapTypeFix extends LocalQuickFixOnPsiElement { @@ -113,7 +135,7 @@ public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file, } if (startElement instanceof PsiParameter) { PsiParameter parameter = (PsiParameter) startElement; - PsiType[] parameters = getParameters( parameter ); + PsiType[] parameters = getGenericTypes( parameter ); return parameters != null && parameters.length == 0; } return false; @@ -159,7 +181,7 @@ public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file, } if (startElement instanceof PsiParameter) { PsiParameter parameter = (PsiParameter) startElement; - PsiType[] parameters = getParameters( parameter ); + PsiType[] parameters = getGenericTypes( parameter ); if (parameters == null || parameters.length != 2) { return false; } diff --git a/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java b/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java index 7416df56..f01320cc 100644 --- a/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java +++ b/src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java @@ -26,6 +26,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifierListOwner; +import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiType; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.Nls; @@ -41,8 +42,9 @@ import static org.mapstruct.intellij.inspection.inheritance.InheritConfigurationUtils.findInheritedTargetProperties; import static org.mapstruct.intellij.util.MapstructAnnotationUtils.addMappingAnnotation; import static org.mapstruct.intellij.util.MapstructAnnotationUtils.getUnmappedTargetPolicy; +import static org.mapstruct.intellij.util.MapstructUtil.getSourceParameters; import static org.mapstruct.intellij.util.SourceUtils.findAllSourceProperties; -import static org.mapstruct.intellij.util.SourceUtils.isFromMapMapping; +import static org.mapstruct.intellij.util.SourceUtils.getGenericTypes; import static org.mapstruct.intellij.util.TargetUtils.findAllDefinedMappingTargets; import static org.mapstruct.intellij.util.TargetUtils.findAllSourcePropertiesForCurrentTarget; import static org.mapstruct.intellij.util.TargetUtils.findAllTargetProperties; @@ -184,6 +186,20 @@ private static boolean isBeanMappingIgnoreByDefault(PsiMethod method) { return false; } + private static boolean isFromMapMapping(@NotNull PsiMethod method) { + PsiParameter[] sourceParameters = getSourceParameters( method ); + for (PsiParameter parameter : sourceParameters) { + if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(), + method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) { + PsiType[] generics = getGenericTypes( parameter ); + if (generics != null && generics.length > 0) { + return generics[0].equalsToText( "java.lang.String" ); + } + } + } + return false; + } + } private static class UnmappedTargetPropertyFix extends LocalQuickFixOnPsiElement { diff --git a/src/main/java/org/mapstruct/intellij/util/SourceUtils.java b/src/main/java/org/mapstruct/intellij/util/SourceUtils.java index 7b11ccb5..4f918ce4 100644 --- a/src/main/java/org/mapstruct/intellij/util/SourceUtils.java +++ b/src/main/java/org/mapstruct/intellij/util/SourceUtils.java @@ -25,6 +25,7 @@ import com.intellij.psi.PsiRecordComponent; import com.intellij.psi.PsiSubstitutor; import com.intellij.psi.PsiType; +import com.intellij.psi.impl.source.PsiClassReferenceType; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -212,20 +213,12 @@ else if ( methodName.startsWith( "is" ) && ( } - public static boolean isFromMapMapping(@NotNull PsiMethod method) { - return getFromMapMappingParameter( method ) != null; - } - @Nullable - public static PsiParameter getFromMapMappingParameter(@NotNull PsiMethod method) { - PsiParameter[] sourceParameters = getSourceParameters( method ); - if (sourceParameters.length == 1) { - PsiParameter parameter = sourceParameters[0]; - if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(), - method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) { - return parameter; - } + public static PsiType[] getGenericTypes(@Nullable PsiParameter fromMapMappingParameter) { + if (fromMapMappingParameter == null || + !(fromMapMappingParameter.getType() instanceof PsiClassReferenceType)) { + return null; } - return null; + return ((PsiClassReferenceType) fromMapMappingParameter.getType()).getParameters(); } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 62863a55..c3b0b96f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -115,7 +115,7 @@ allQuickFixes = myFixture.getAllQuickFixes(); assertThat( allQuickFixes ).isEmpty(); } + + public void testFromMapMappingMapTypeWithSourceParameterNoInspection() { + doTest(); + List allQuickFixes = myFixture.getAllQuickFixes(); + assertThat( allQuickFixes ).isEmpty(); + } } diff --git a/src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java b/src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java index 54bbd6df..07df7817 100644 --- a/src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java +++ b/src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesWithFromMapMappingTest.java @@ -40,4 +40,39 @@ public void testUnmappedTargetPropertiesWithFromMapMapping() { List allQuickFixes = myFixture.getAllQuickFixes(); assertThat( allQuickFixes ).isEmpty(); } + + public void testUnmappedTargetPropertiesWithFromMapMappingMultiSource() { + doTest(); + List allQuickFixes = myFixture.getAllQuickFixes(); + assertThat( allQuickFixes ).isEmpty(); + } + + public void testUnmappedTargetPropertiesWithFromMapMappingMultiSourceWrongKeyType() { + doTest(); + List allQuickFixes = myFixture.getAllQuickFixes(); + assertThat( allQuickFixes ) + .extracting( IntentionAction::getText ) + .as( "Intent Text" ) + .containsExactly( + "Ignore unmapped target property: 'matching'", + "Add unmapped target property: 'matching'", + + "Ignore unmapped target property: 'moreTarget'", + "Add unmapped target property: 'moreTarget'", + + "Ignore unmapped target property: 'testName'", + "Add unmapped target property: 'testName'", + "Ignore all unmapped target properties", + + "Ignore unmapped target property: 'matching'", + "Add unmapped target property: 'matching'", + + "Ignore unmapped target property: 'moreTarget'", + "Add unmapped target property: 'moreTarget'", + + "Ignore unmapped target property: 'testName'", + "Add unmapped target property: 'testName'", + "Ignore all unmapped target properties" + ); + } } diff --git a/testData/inspection/FromMapMappingMapTypeInspectionRawMap.java b/testData/inspection/FromMapMappingMapTypeInspectionRawMap.java index 2ee2e297..00d1ef04 100644 --- a/testData/inspection/FromMapMappingMapTypeInspectionRawMap.java +++ b/testData/inspection/FromMapMappingMapTypeInspectionRawMap.java @@ -34,9 +34,9 @@ interface NotMapStructMapper { @Mapper interface NoMappingMapper { - Target map(Map source); + Target map(Map source); - Target map(HashMap source); + Target map(HashMap source); } @Mapper @@ -48,9 +48,9 @@ interface MultiSourceMappingsMapper { @Mapper interface UpdateMapper { - void update(@MappingTarget Target target, Map source); + void update(@MappingTarget Target target, Map source); - void update(@MappingTarget Target target, HashMap source); + void update(@MappingTarget Target target, HashMap source); } @Mapper diff --git a/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java b/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java index 9e7a082f..3cfd9ada 100644 --- a/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java +++ b/testData/inspection/FromMapMappingMapTypeInspectionWrongKeyType.java @@ -34,9 +34,9 @@ interface NotMapStructMapper { @Mapper interface NoMappingMapper { - Target map(Map source); + Target map(Map source); - Target map(HashMap source); + Target map(HashMap source); } @Mapper @@ -48,9 +48,9 @@ interface MultiSourceMappingsMapper { @Mapper interface UpdateMapper { - void update(@MappingTarget Target target, Map source); + void update(@MappingTarget Target target, Map source); - void update(@MappingTarget Target target, HashMap source); + void update(@MappingTarget Target target, HashMap source); } @Mapper diff --git a/testData/inspection/FromMapMappingMapTypeWithSourceParameterNoInspection.java b/testData/inspection/FromMapMappingMapTypeWithSourceParameterNoInspection.java new file mode 100644 index 00000000..2c941056 --- /dev/null +++ b/testData/inspection/FromMapMappingMapTypeWithSourceParameterNoInspection.java @@ -0,0 +1,57 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; + +import java.util.HashMap; +import java.util.Map; + +class Target { + + private Map innerMap; + + public Target(Map innerMap) { + this.innerMap = innerMap; + } +} + +@Mapper +interface ParameterNameMapper { + + Target map(Map innerMap); + + Target map(HashMap innerMap); + + void update(@MappingTarget Target target, Map innerMap); + + void update(@MappingTarget Target target, HashMap innerMap); +} + +@Mapper +interface MappingSourceMapper { + + @Mapping(source = "source", target = "innerMap") + Target map(Map source); + + @Mapping(source = "source", target = "innerMap") + Target map(HashMap source); + + @Mapping(source = "source", target = "innerMap") + void update(@MappingTarget Target target, Map source); + + @Mapping(source = "source", target = "innerMap") + void update(@MappingTarget Target target, HashMap source); +} + +@Mapper +interface UpdateMapper { + + + +} \ No newline at end of file diff --git a/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java b/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java index eb2dc551..18fcead5 100644 --- a/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java +++ b/testData/inspection/UnmappedTargetPropertiesWithFromMapMapping.java @@ -16,10 +16,10 @@ @Mapper interface SingleMapper { - Target map(Map source); + Target map(Map source, String secondSource); } @Mapper abstract class AbstractMapperWitAbstractMethod { - abstract Target map(Map source); + abstract Target map(Map source, String secondSource); } diff --git a/testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSource.java b/testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSource.java new file mode 100644 index 00000000..eb2dc551 --- /dev/null +++ b/testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSource.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.example.data.UnmappedTargetPropertiesData.Target; + +import java.util.Map; + +@Mapper +interface SingleMapper { + + Target map(Map source); +} +@Mapper +abstract class AbstractMapperWitAbstractMethod { + + abstract Target map(Map source); +} diff --git a/testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSourceWrongKeyType.java b/testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSourceWrongKeyType.java new file mode 100644 index 00000000..9396e4af --- /dev/null +++ b/testData/inspection/UnmappedTargetPropertiesWithFromMapMappingMultiSourceWrongKeyType.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.example.data.UnmappedTargetPropertiesData.Target; + +import java.util.Map; + +@Mapper +interface SingleMapper { + + Target map(Map source); +} +@Mapper +abstract class AbstractMapperWitAbstractMethod { + + abstract Target map(Map source); +}