diff --git a/src/main/java/org/mapstruct/intellij/inspection/MissingMapperOrMapperConfigAnnotationInspection.java b/src/main/java/org/mapstruct/intellij/inspection/MissingMapperOrMapperConfigAnnotationInspection.java index d6583b9..c43c2fd 100644 --- a/src/main/java/org/mapstruct/intellij/inspection/MissingMapperOrMapperConfigAnnotationInspection.java +++ b/src/main/java/org/mapstruct/intellij/inspection/MissingMapperOrMapperConfigAnnotationInspection.java @@ -20,7 +20,7 @@ import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig; /** - * Inspection that checks if a mapping class (a class that contains at lease one mapping method) is anntoated with + * Inspection that checks if a mapping class (a class that contains at lease one mapping method) is annotated with * {@link org.mapstruct.Mapper} or {@link org.mapstruct.MapperConfig}. * * @author Filip Hrisafov diff --git a/src/main/java/org/mapstruct/intellij/inspection/NoSourcePropertyDefinedInspection.java b/src/main/java/org/mapstruct/intellij/inspection/NoSourcePropertyDefinedInspection.java index 6fafca6..8cc75eb 100644 --- a/src/main/java/org/mapstruct/intellij/inspection/NoSourcePropertyDefinedInspection.java +++ b/src/main/java/org/mapstruct/intellij/inspection/NoSourcePropertyDefinedInspection.java @@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mapstruct.intellij.MapStructBundle; +import org.mapstruct.intellij.util.MapstructAnnotationUtils; import org.mapstruct.intellij.util.MapstructUtil; /** @@ -34,7 +35,7 @@ void visitMappingAnnotation( @NotNull ProblemsHolder problemsHolder, @NotNull Ps } private static boolean isIgnoreByDefaultEnabled(@NotNull PsiAnnotation psiAnnotation) { - PsiMethod annotatedMethod = getAnnotatedMethod( psiAnnotation ); + PsiMethod annotatedMethod = MapstructAnnotationUtils.getAnnotatedMethod(psiAnnotation ); if (annotatedMethod == null) { return false; } @@ -47,49 +48,4 @@ private static boolean isIgnoreByDefaultEnabled(@NotNull PsiAnnotation psiAnnota return ignoreByDefault instanceof PsiLiteralExpression && Boolean.TRUE.equals( ((PsiLiteralExpression) ignoreByDefault).getValue() ); } - - @Nullable - private static PsiMethod getAnnotatedMethod(@NotNull PsiAnnotation psiAnnotation) { - PsiElement psiAnnotationParent = psiAnnotation.getParent(); - if (psiAnnotationParent == null) { - return null; - } - PsiElement psiAnnotationParentParent = psiAnnotationParent.getParent(); - if (psiAnnotationParentParent instanceof PsiMethod) { - // directly annotated with @Mapping - return (PsiMethod) psiAnnotationParentParent; - } - - PsiElement psiAnnotationParentParentParent = psiAnnotationParentParent.getParent(); - if (psiAnnotationParentParentParent instanceof PsiAnnotation) { - // inside @Mappings without array - PsiElement mappingsAnnotationParent = psiAnnotationParentParentParent.getParent(); - if (mappingsAnnotationParent == null) { - return null; - } - PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent(); - if (mappingsAnnotationParentParent instanceof PsiMethod) { - return (PsiMethod) mappingsAnnotationParentParent; - } - return null; - } - else if (psiAnnotationParentParentParent instanceof PsiAnnotationParamListImpl) { - // inside @Mappings wit array - PsiElement mappingsArray = psiAnnotationParentParentParent.getParent(); - if (mappingsArray == null) { - return null; - } - PsiElement mappingsAnnotationParent = mappingsArray.getParent(); - if (mappingsAnnotationParent == null) { - return null; - } - PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent(); - if (mappingsAnnotationParentParent instanceof PsiMethod) { - return (PsiMethod) mappingsAnnotationParentParent; - } - return null; - - } - return null; - } } diff --git a/src/main/java/org/mapstruct/intellij/inspection/NonExistingTargetPropertiesInspection.java b/src/main/java/org/mapstruct/intellij/inspection/NonExistingTargetPropertiesInspection.java new file mode 100644 index 0000000..1d612a9 --- /dev/null +++ b/src/main/java/org/mapstruct/intellij/inspection/NonExistingTargetPropertiesInspection.java @@ -0,0 +1,83 @@ +package org.mapstruct.intellij.inspection; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.intention.QuickFixFactory; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.LocalQuickFixOnPsiElement; +import com.intellij.codeInspection.ProblemHighlightType; +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.PsiAnnotation; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiNameValuePair; +import com.intellij.psi.PsiType; +import org.jetbrains.annotations.NotNull; +import org.mapstruct.intellij.MapStructBundle; +import org.mapstruct.intellij.util.MapStructVersion; +import org.mapstruct.intellij.util.MapstructAnnotationUtils; +import org.mapstruct.intellij.util.MapstructUtil; +import org.mapstruct.intellij.util.TargetUtils; + +import java.util.Set; + +import static org.mapstruct.intellij.util.TargetUtils.getTargetType; + +public class NonExistingTargetPropertiesInspection extends MappingAnnotationInspectionBase { + + @Override + void visitMappingAnnotation( + @NotNull final ProblemsHolder problemsHolder, + @NotNull final PsiAnnotation psiAnnotation, + @NotNull final MappingAnnotation mappingAnnotation) { + + MapStructVersion version = MapstructUtil.resolveMapStructProjectVersion(problemsHolder.getFile()); + + PsiNameValuePair targetProperty = mappingAnnotation.getTargetProperty(); + if (targetProperty != null) { + + PsiMethod method = MapstructAnnotationUtils.getAnnotatedMethod(psiAnnotation); + if (method != null) { + PsiType targetType = getTargetType(method); + if (targetType != null && targetProperty.getValue() != null) { + Set targets = TargetUtils.findAllTargetProperties(targetType, version, method); + + String value = AnnotationUtil.getStringAttributeValue(targetProperty.getValue()); + + if (value != null && !targets.contains(getBaseTarget(value))) { + + LocalQuickFix quickFix = QuickFixFactory.getInstance().createDeleteFix( + psiAnnotation, + MapStructBundle.message( + "intention.remove.non.existing.mapping.declaration", + value + ) + ); + + problemsHolder.registerProblem( + targetProperty.getValue(), + MapStructBundle.message( + "inspection.non.existing.target.property", + value + ), + ProblemHighlightType.ERROR, + quickFix + ); + } + } + } + } + } + + @NotNull + private static String getBaseTarget(@NotNull String target) { + int dotIndex = target.indexOf( "." ); + if ( dotIndex > 0 ) { + return target.substring( 0, dotIndex ); + } + return target; + } +} diff --git a/src/main/java/org/mapstruct/intellij/util/MapstructAnnotationUtils.java b/src/main/java/org/mapstruct/intellij/util/MapstructAnnotationUtils.java index 4add335..198359d 100644 --- a/src/main/java/org/mapstruct/intellij/util/MapstructAnnotationUtils.java +++ b/src/main/java/org/mapstruct/intellij/util/MapstructAnnotationUtils.java @@ -45,6 +45,7 @@ import com.intellij.psi.PsiNameValuePair; import com.intellij.psi.PsiReference; import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.impl.source.tree.java.PsiAnnotationParamListImpl; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -610,4 +611,48 @@ private static ReportingPolicy getUnmappedTargetPolicyPolicyFromAnnotation( } } + @Nullable + public static PsiMethod getAnnotatedMethod(@NotNull PsiAnnotation psiAnnotation) { + PsiElement psiAnnotationParent = psiAnnotation.getParent(); + if (psiAnnotationParent == null) { + return null; + } + PsiElement psiAnnotationParentParent = psiAnnotationParent.getParent(); + if (psiAnnotationParentParent instanceof PsiMethod) { + // directly annotated with @Mapping + return (PsiMethod) psiAnnotationParentParent; + } + + PsiElement psiAnnotationParentParentParent = psiAnnotationParentParent.getParent(); + if (psiAnnotationParentParentParent instanceof PsiAnnotation) { + // inside @Mappings without array + PsiElement mappingsAnnotationParent = psiAnnotationParentParentParent.getParent(); + if (mappingsAnnotationParent == null) { + return null; + } + PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent(); + if (mappingsAnnotationParentParent instanceof PsiMethod) { + return (PsiMethod) mappingsAnnotationParentParent; + } + return null; + } + else if (psiAnnotationParentParentParent instanceof PsiAnnotationParamListImpl) { + // inside @Mappings wit array + PsiElement mappingsArray = psiAnnotationParentParentParent.getParent(); + if (mappingsArray == null) { + return null; + } + PsiElement mappingsAnnotationParent = mappingsArray.getParent(); + if (mappingsAnnotationParent == null) { + return null; + } + PsiElement mappingsAnnotationParentParent = mappingsAnnotationParent.getParent(); + if (mappingsAnnotationParentParent instanceof PsiMethod) { + return (PsiMethod) mappingsAnnotationParentParent; + } + return null; + + } + return null; + } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 177692f..b2d0535 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -64,6 +64,15 @@ shortName="UnmappedTargetProperties" implementationClass="org.mapstruct.intellij.inspection.UnmappedTargetPropertiesInspection"/> + + + +

+ This inspection reports when a target property does not exist. +

+

+


+//wrong
+@Mapper
+public interface EmployeeMapper {
+    @Mapping(source = "employeeName", target = "nonExistingField")
+    Employee toEmployee(EmployeeDto employeeDto);
+}
+
+

+

+


+//correct
+@Mapper
+public interface EmployeeMapper {
+    @Mapping(source = "employeeName", target = "name")
+    Employee toEmployee(EmployeeDto employeeDto);
+}
+
+

+ + + diff --git a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties index 5732d13..e26457c 100644 --- a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties +++ b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties @@ -25,6 +25,7 @@ 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.non.existing.target.property=Non-existing target property: {0} 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 @@ -34,6 +35,7 @@ intention.not.null.checkable.property.source.used.with.default.property=Remove d 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 +intention.remove.non.existing.mapping.declaration=Remove target ''{0}'' @Mapping declaration 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/NonExistingTargetPropertyInspectionTest.java b/src/test/java/org/mapstruct/intellij/inspection/NonExistingTargetPropertyInspectionTest.java new file mode 100644 index 0000000..a73ed5b --- /dev/null +++ b/src/test/java/org/mapstruct/intellij/inspection/NonExistingTargetPropertyInspectionTest.java @@ -0,0 +1,16 @@ +package org.mapstruct.intellij.inspection; + +import com.intellij.codeInspection.LocalInspectionTool; +import org.jetbrains.annotations.NotNull; + +public class NonExistingTargetPropertyInspectionTest extends BaseInspectionTest { + + @Override + protected @NotNull Class getInspection() { + return NonExistingTargetPropertiesInspection.class; + } + + public void testNonExistingProperty() { + doTest(); + } +} diff --git a/testData/inspection/NonExistingProperty.java b/testData/inspection/NonExistingProperty.java new file mode 100644 index 0000000..0f92fda --- /dev/null +++ b/testData/inspection/NonExistingProperty.java @@ -0,0 +1,41 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +class Source { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +@Mapper +interface SingleMappingMapper { + @Mapping(target = "nonExistingField", ignore = true) + Target map(Source source); +} \ No newline at end of file