From 5f88c2257bc0b06fe9b438a6daaa96e7a5429451 Mon Sep 17 00:00:00 2001 From: hduelme Date: Sun, 22 Dec 2024 20:32:08 +0100 Subject: [PATCH] add unknown reference inspection --- README.md | 2 + description.html | 2 + .../codeinsight/references/BaseReference.java | 2 +- .../MapstructReferenceInspection.java | 62 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 8 +++ .../MapstructReferenceInspection.html | 12 ++++ .../messages/MapStructBundle.properties | 1 + .../MapstructReferenceInspectionTest.java | 52 +++++++++++++++ .../UnknownIgnoreUnmappedSourceReference.java | 54 ++++++++++++++++ .../UnknownNestedSourceReference.java | 63 +++++++++++++++++++ .../UnknownNestedTargetReference.java | 63 +++++++++++++++++++ ...nownQualifiedByNameReferenceReference.java | 51 +++++++++++++++ .../inspection/UnknownSourceReference.java | 51 +++++++++++++++ .../inspection/UnknownTargetReference.java | 51 +++++++++++++++ .../UnknownValueMappingSourceReference.java | 38 +++++++++++ .../UnknownValueMappingTargetReference.java | 38 +++++++++++ 16 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/mapstruct/intellij/inspection/MapstructReferenceInspection.java create mode 100644 src/main/resources/inspectionDescriptions/MapstructReferenceInspection.html create mode 100644 src/test/java/org/mapstruct/intellij/inspection/MapstructReferenceInspectionTest.java create mode 100644 testData/inspection/UnknownIgnoreUnmappedSourceReference.java create mode 100644 testData/inspection/UnknownNestedSourceReference.java create mode 100644 testData/inspection/UnknownNestedTargetReference.java create mode 100644 testData/inspection/UnknownQualifiedByNameReferenceReference.java create mode 100644 testData/inspection/UnknownSourceReference.java create mode 100644 testData/inspection/UnknownTargetReference.java create mode 100644 testData/inspection/UnknownValueMappingSourceReference.java create mode 100644 testData/inspection/UnknownValueMappingTargetReference.java diff --git a/README.md b/README.md index 3f44c831..000ea87d 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 ef0e473f..7c30808c 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 f6b520b9..fcdc738a 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 00000000..8e5b43ee --- /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 37cd1864..22a4c1c5 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 00000000..24b9d279 --- /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 d9311bee..06c160a4 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 00000000..97703559 --- /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 00000000..9e0fb467 --- /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 00000000..a3bd3613 --- /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 00000000..3df4a387 --- /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 00000000..d413d4e5 --- /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 00000000..408fdf75 --- /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 00000000..641934b4 --- /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 00000000..edd23684 --- /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 00000000..de25e128 --- /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); +} +