diff --git a/README.md b/README.md index 3f44c83..000ea87 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/ * More than one default source in `@Mapping` annotation defined with quick fixes: Remove `defaultValue`. Remove `defaultExpression`. * `target` mapped more than once by `@Mapping` annotations with quick fixes: Remove annotation and change target property. * `*` used as a source in `@Mapping` annotation with quick fixes: Replace `*` with method parameter name. + * Unknown reference inspection for `source` and `target` in `@Mapping` and `@ValueMapping` annotation. + * Unknown reference inspection for `qualifiedByName` in `@Mapping` annotation ## Requirements diff --git a/description.html b/description.html index ef0e473..7c30808 100644 --- a/description.html +++ b/description.html @@ -43,6 +43,8 @@
  • More than one default source in @Mapping annotation defined with quick fixes: Remove defaultValue. Remove defaultExpression.
  • target mapped more than once by @Mapping annotations with quick fixes: Remove annotation and change target property.
  • * used as a source in @Mapping annotations with quick fixes: Replace * with method parameter name.
  • +
  • Unknown reference inspection for source and target in @Mapping and @ValueMapping annotation.
  • +
  • Unknown reference inspection for qualifiedByName in @Mapping annotation.
  • diff --git a/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java b/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java index f6b520b..fcdc738 100644 --- a/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java +++ b/src/main/java/org/mapstruct/intellij/codeinsight/references/BaseReference.java @@ -21,7 +21,7 @@ * * @author Filip Hrisafov */ -abstract class BaseReference extends PsiReferenceBase { +public abstract class BaseReference extends PsiReferenceBase { /** * @param element the element for which a reference should be found diff --git a/src/main/java/org/mapstruct/intellij/inspection/MapstructReferenceInspection.java b/src/main/java/org/mapstruct/intellij/inspection/MapstructReferenceInspection.java new file mode 100644 index 0000000..8e5b43e --- /dev/null +++ b/src/main/java/org/mapstruct/intellij/inspection/MapstructReferenceInspection.java @@ -0,0 +1,62 @@ +/* + * 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.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.ContributedReferenceHost; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiLanguageInjectionHost; +import com.intellij.psi.PsiReference; +import org.jetbrains.annotations.NotNull; +import org.mapstruct.intellij.codeinsight.references.BaseReference; + +/** + * Inspection that checks if mapstruct references can be resolved. + * @see BaseReference + * @author hduelme + */ +public class MapstructReferenceInspection extends InspectionBase { + + @Override + @NotNull PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new MapstructReferenceVisitor(holder); + } + + private static class MapstructReferenceVisitor extends PsiElementVisitor { + + private final ProblemsHolder holder; + + private MapstructReferenceVisitor(ProblemsHolder holder) { + this.holder = holder; + } + + /** + * Based on org.intellij.plugins.intelliLang.references.InjectedReferencesInspection + */ + @Override + public void visitElement(@NotNull PsiElement element) { + if (element instanceof ContributedReferenceHost r && element instanceof PsiLanguageInjectionHost) { + for (PsiReference psiReference : r.getReferences()) { + if (psiReference instanceof BaseReference && psiReference.resolve() == null) { + TextRange range = psiReference.getRangeInElement(); + if (range.isEmpty() && range.getStartOffset() == 1 && "\"\"".equals( element.getText() ) ) { + String message = ProblemsHolder.unresolvedReferenceMessage( psiReference ); + holder.registerProblem( element, message, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, + TextRange.create( 0, 2 ) ); + } + else { + holder.registerProblem( psiReference ); + } + } + } + } + super.visitElement( element ); + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 37cd186..22a4c1c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -147,6 +147,14 @@ key="inspection.source.property.this.used" shortName="ThisUsedAsSourcePropertyInspection" implementationClass="org.mapstruct.intellij.inspection.ThisUsedAsSourcePropertyInspection"/> + diff --git a/src/main/resources/inspectionDescriptions/MapstructReferenceInspection.html b/src/main/resources/inspectionDescriptions/MapstructReferenceInspection.html new file mode 100644 index 0000000..24b9d27 --- /dev/null +++ b/src/main/resources/inspectionDescriptions/MapstructReferenceInspection.html @@ -0,0 +1,12 @@ + + +This inspection reports unresolved mapstruct references. +
    
    +@Mapper
    +public interface EmployeeMapper {
    +    @Mapping(target = "dto", source = "no_exists") // highlighted if source doesn't exist
    +    Employee toEmployee(EmployeeDto employeeDto, @Context CycleAvoidingMappingContext context);
    +}
    +
    + + \ 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 d9311be..06c160a 100644 --- a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties +++ b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties @@ -28,6 +28,7 @@ inspection.wrong.map.mapping.map.key.change.to.string=Change key type to String inspection.target.property.mapped.more.than.once=Target property ''{0}'' must not be mapped more than once. inspection.target.property.mapped.more.than.once.title=Target properties must not be mapped more than once. inspection.source.property.this.used=''.'' should not be used as a source. +inspection.mapstruct.references=Injected mapstruct references 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 diff --git a/src/test/java/org/mapstruct/intellij/inspection/MapstructReferenceInspectionTest.java b/src/test/java/org/mapstruct/intellij/inspection/MapstructReferenceInspectionTest.java new file mode 100644 index 0000000..9770355 --- /dev/null +++ b/src/test/java/org/mapstruct/intellij/inspection/MapstructReferenceInspectionTest.java @@ -0,0 +1,52 @@ +/* + * 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.LocalInspectionTool; +import org.jetbrains.annotations.NotNull; + +/** + * @author hduelme + */ +public class MapstructReferenceInspectionTest extends BaseInspectionTest { + + @Override + protected @NotNull Class getInspection() { + return MapstructReferenceInspection.class; + } + + public void testUnknownTargetReference() { + doTest(); + } + + public void testUnknownNestedTargetReference() { + doTest(); + } + + public void testUnknownSourceReference() { + doTest(); + } + + public void testUnknownNestedSourceReference() { + doTest(); + } + + public void testUnknownValueMappingSourceReference() { + doTest(); + } + + public void testUnknownValueMappingTargetReference() { + doTest(); + } + + public void testUnknownIgnoreUnmappedSourceReference() { + doTest(); + } + + public void testUnknownQualifiedByNameReferenceReference() { + doTest(); + } +} diff --git a/testData/inspection/UnknownIgnoreUnmappedSourceReference.java b/testData/inspection/UnknownIgnoreUnmappedSourceReference.java new file mode 100644 index 0000000..9e0fb46 --- /dev/null +++ b/testData/inspection/UnknownIgnoreUnmappedSourceReference.java @@ -0,0 +1,54 @@ +/* + * 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; +import org.mapstruct.BeanMapping; + +class Source { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +class Target { + + private String testName; + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } +} + +@Mapper +interface SingleMappingMapper { + + @Mapping(target = "testName", ignore = true) + @BeanMapping(ignoreUnmappedSourceProperties = {"testName"}) + Target map(Source source); +} + +@Mapper +interface SingleMappingsMapper { + + @Mappings({ + @Mapping(target = "testName", ignore = true) + }) + @BeanMapping(ignoreUnmappedSourceProperties = {"testName"}) + Target map(Source source); +} \ No newline at end of file diff --git a/testData/inspection/UnknownNestedSourceReference.java b/testData/inspection/UnknownNestedSourceReference.java new file mode 100644 index 0000000..a3bd361 --- /dev/null +++ b/testData/inspection/UnknownNestedSourceReference.java @@ -0,0 +1,63 @@ +/* + * 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 Inner inner; + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } +} + +class Inner { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +class Target { + + private String testName; + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } +} + +@Mapper +interface SingleMappingMapper { + + @Mapping(target = "testName", source="inner.testName") + Target map(Source source); +} + +@Mapper +interface SingleMappingsMapper { + + @Mappings({ + @Mapping(target = "testName", source="inner.testName") + }) + Target map(Source source); +} \ No newline at end of file diff --git a/testData/inspection/UnknownNestedTargetReference.java b/testData/inspection/UnknownNestedTargetReference.java new file mode 100644 index 0000000..3df4a38 --- /dev/null +++ b/testData/inspection/UnknownNestedTargetReference.java @@ -0,0 +1,63 @@ +/* + * 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 Inner inner; + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } +} + +class Inner { + + private String testName; + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } +} + +@Mapper +interface SingleMappingMapper { + + @Mapping(target = "inner.name", source="name") + Target map(Source source); +} + +@Mapper +interface SingleMappingsMapper { + + @Mappings({ + @Mapping(target = "inner.name", source="name") + }) + Target map(Source source); +} \ No newline at end of file diff --git a/testData/inspection/UnknownQualifiedByNameReferenceReference.java b/testData/inspection/UnknownQualifiedByNameReferenceReference.java new file mode 100644 index 0000000..d413d4e --- /dev/null +++ b/testData/inspection/UnknownQualifiedByNameReferenceReference.java @@ -0,0 +1,51 @@ +/* + * 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 testName; + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } +} + +@Mapper +interface SingleMappingMapper { + + @Mapping(target = "testName", source = "name", qualifiedByName ="StringMapper") + Target map(Source source); +} + +@Mapper +interface SingleMappingsMapper { + + @Mappings({ + @Mapping(target = "testName", source = "name", qualifiedByName ="StringMapper") + }) + Target map(Source source); +} \ No newline at end of file diff --git a/testData/inspection/UnknownSourceReference.java b/testData/inspection/UnknownSourceReference.java new file mode 100644 index 0000000..408fdf7 --- /dev/null +++ b/testData/inspection/UnknownSourceReference.java @@ -0,0 +1,51 @@ +/* + * 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 testName; + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } +} + +@Mapper +interface SingleMappingMapper { + + @Mapping(target = "testName", source="testName") + Target map(Source source); +} + +@Mapper +interface SingleMappingsMapper { + + @Mappings({ + @Mapping(target = "testName", source="testName") + }) + Target map(Source source); +} \ No newline at end of file diff --git a/testData/inspection/UnknownTargetReference.java b/testData/inspection/UnknownTargetReference.java new file mode 100644 index 0000000..641934b --- /dev/null +++ b/testData/inspection/UnknownTargetReference.java @@ -0,0 +1,51 @@ +/* + * 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 testName; + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } +} + +@Mapper +interface SingleMappingMapper { + + @Mapping(target = "name", source="name") + Target map(Source source); +} + +@Mapper +interface SingleMappingsMapper { + + @Mappings({ + @Mapping(target = "name", source="name") + }) + Target map(Source source); +} \ No newline at end of file diff --git a/testData/inspection/UnknownValueMappingSourceReference.java b/testData/inspection/UnknownValueMappingSourceReference.java new file mode 100644 index 0000000..edd2368 --- /dev/null +++ b/testData/inspection/UnknownValueMappingSourceReference.java @@ -0,0 +1,38 @@ +/* + * 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.ValueMapping; +import org.mapstruct.ValueMappings; + +enum Target { + FIST, + SECOND, + THIRD +} + +enum Source { + FIST, + SECOND, + THIRD +} + +@Mapper +interface SingleValueMappingMapper { + + @ValueMapping(target = "FIST", source = "OTHER") + Target map(Source source); +} + +@Mapper +interface SingleValueMappingsMapper { + + @ValueMappings({ + @ValueMapping(target = "FIST", source = "OTHER") +}) +Target map(Source source); +} + diff --git a/testData/inspection/UnknownValueMappingTargetReference.java b/testData/inspection/UnknownValueMappingTargetReference.java new file mode 100644 index 0000000..de25e12 --- /dev/null +++ b/testData/inspection/UnknownValueMappingTargetReference.java @@ -0,0 +1,38 @@ +/* + * 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.ValueMapping; +import org.mapstruct.ValueMappings; + +enum Target { + FIST, + SECOND, + THIRD +} + +enum Source { + FIST, + SECOND, + THIRD +} + +@Mapper +interface SingleValueMappingMapper { + + @ValueMapping(target = "OTHER", source = "FIST") + Target map(Source source); +} + +@Mapper +interface SingleValueMappingsMapper { + + @ValueMappings({ + @ValueMapping(target = "OTHER", source = "FIST") +}) +Target map(Source source); +} +