Skip to content

Commit df6867d

Browse files
committed
#237 #238 Add @Ignored annotation support for completion, references, and inspections
Add support for the `@Ignored` annotation introduced in MapStruct 1.7. This includes: - Code completion for `@Ignored(targets=...)` with `prefix` support - Go-to-declaration and find usages for ignored target properties - UnmappedTargetPropertiesInspection considers `@Ignored` targets - TargetPropertyMappedMoreThanOnceInspection detects conflicts between `@Mapping` and `@Ignored` targeting the same property - Unknown target reference inspection for `@Ignored` targets Completion correctly excludes targets already defined in other `@Ignored` and `@Mapping` annotations on the same method, including prefix-scoped targets.
1 parent c069b5a commit df6867d

File tree

39 files changed

+1292
-230
lines changed

39 files changed

+1292
-230
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/
2424
* Code completions
2525
* Completion of `target` and `source` properties in `@Mapping` annotation (nested properties also work)
2626
* Completion of `target` and `source` properties in `@ValueMapping` annotation
27+
* Completion of `targets` in `@Ignored` annotation with `prefix` support
2728
* Completion of `componentModel` in `@Mapper` and `@MapperConfig` annotations
2829
* Completion of `qualifiedByName` in `@Mapping` annotation
2930
* Go To Declaration for properties in `target` and `source` to setters / getters
31+
* Go To Declaration for `targets` in `@Ignored` annotation
3032
* Go To Declaration for `Mapping#qualifiedByName`
31-
* Find usages of properties in `target` and `source` and find usages of setters / getters in `@Mapping` annotations
33+
* Find usages of properties in `target` and `source` and find usages of setters / getters in `@Mapping` and `@Ignored` annotations
3234
* Highlighting properties in `target` and `source`
3335
* Errors and Quick fixes:
3436
* `@Mapper` or `@MapperConfig` annotation missing
@@ -37,9 +39,10 @@ To learn more about MapStruct have a look at the [mapstruct](https://github.com/
3739
* No `source` defined in `@Mapping` annotation
3840
* More than one `source` in `@Mapping` annotation defined with quick fixes: Remove `source`. Remove `constant`. Remove `expression`. Use `constant` as `defaultValue`. Use `expression` as `defaultExpression`.
3941
* More than one default source in `@Mapping` annotation defined with quick fixes: Remove `defaultValue`. Remove `defaultExpression`.
40-
* `target` mapped more than once by `@Mapping` annotations with quick fixes: Remove annotation and change target property.
42+
* `target` mapped more than once by `@Mapping` and/or `@Ignored` annotations with quick fixes: Remove annotation and change target property.
4143
* `*` used as a source in `@Mapping` annotation with quick fixes: Replace `*` with method parameter name.
42-
* Unknown reference inspection for `source` and `target` in `@Mapping` and `@ValueMapping` annotation.
44+
* Unknown reference inspection for `source` and `target` in `@Mapping` and `@ValueMapping` annotation.
45+
* Unknown reference inspection for `targets` and `prefix` in `@Ignored` annotation.
4346
* Unknown reference inspection for `qualifiedByName` in `@Mapping` annotation
4447

4548
## Requirements

build.gradle

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ dependencies {
111111
testFramework( TestFrameworkType.Platform.INSTANCE )
112112
testFramework( TestFrameworkType.Bundled.INSTANCE )
113113
}
114-
implementation('org.mapstruct:mapstruct:1.5.3.Final')
114+
implementation('org.mapstruct:mapstruct:1.7.0.Beta1')
115115
testImplementation(platform('org.junit:junit-bom:5.11.0'))
116116
testImplementation('org.junit.platform:junit-platform-launcher')
117117
testImplementation('org.junit.jupiter:junit-jupiter-api')
118118
testImplementation('org.junit.jupiter:junit-jupiter-engine')
119119
testRuntimeOnly('org.junit.vintage:junit-vintage-engine')
120-
testImplementation('org.assertj:assertj-core:3.26.3')
120+
testImplementation('org.assertj:assertj-core:3.27.7')
121121
testImplementation('org.apache.commons:commons-text:1.15.0')
122122
testImplementation( 'junit:junit:4.13.2' )
123123
testRuntimeOnly('org.immutables:value:2.10.1')
@@ -130,7 +130,9 @@ tasks.register('libs', Sync) {
130130
include('mapstruct-intellij-*.jar')
131131
include('MapStruct-Intellij-*.jar')
132132
}
133-
rename('mapstruct-1.5.3.Final.jar', 'mapstruct.jar')
133+
rename { String fileName ->
134+
return fileName.startsWith('mapstruct-') ? 'mapstruct.jar' : fileName
135+
}
134136
}
135137

136138
tasks.register('testLibs', Sync) {

change-notes.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
<html lang="en">
2+
<h2>1.10.0</h2>
3+
<ul>
4+
<li>Support for <code>@Ignored</code> annotation (MapStruct 1.7):
5+
<ul>
6+
<li>Code completion for <code>targets</code> in <code>@Ignored</code> annotation with <code>prefix</code> support</li>
7+
<li>Go to declaration and find usages for <code>@Ignored</code> target properties</li>
8+
<li>Unknown reference inspection for <code>targets</code> and <code>prefix</code> in <code>@Ignored</code> annotation</li>
9+
<li>Unmapped target properties inspection considers <code>@Ignored</code> targets</li>
10+
<li>Target mapped more than once inspection detects conflicts between <code>@Mapping</code> and <code>@Ignored</code></li>
11+
</ul>
12+
</li>
13+
</ul>
214
<h2>1.9.1</h2>
315
<ul>
416
<li>Improve error messages for reference inspection</li>

description.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
<ul>
2424
<li>Completion of <code>target</code> and <code>source</code> properties in <code>@Mapping</code> annotation (nested properties also work)</li>
2525
<li>Completion of <code>target</code> and <code>source</code> properties in <code>@ValueMapping</code> annotation</li>
26+
<li>Completion of <code>targets</code> in <code>@Ignored</code> annotation with <code>prefix</code> support</li>
2627
<li>Completion of <code>componentModel</code> in <code>@Mapper</code> and <code>@MapperConfig</code> annotations</li>
2728
<li>Completion of <code>qualifiedByName</code> in <code>@Mapping</code> annotation</li>
2829
</ul>
2930
</li>
3031
<li>Go To Declaration for properties in <code>target</code> and <code>source</code> to setters / getters</li>
31-
<li>Find usages of properties in <code>target</code> and <code>source</code> and find usages of setters / getters in <code>@Mapping</code> annotations</li>
32+
<li>Go To Declaration for <code>targets</code> in <code>@Ignored</code> annotation</li>
33+
<li>Find usages of properties in <code>target</code> and <code>source</code> and find usages of setters / getters in <code>@Mapping</code> and <code>@Ignored</code> annotations</li>
3234
<li>Go To Declaration for <code>Mapping#qualifiedByName</code></li>
3335
<li>Highlighting properties in <code>target</code> and <code>source</code></li>
3436
<li>Refactoring support for properties and methods renaming</li>
@@ -41,9 +43,10 @@
4143
<li>No <code>source</code> defined in <code>@Mapping</code> annotation</li>
4244
<li>More than one <code>source</code> in <code>@Mapping</code> annotation defined with quick fixes: Remove <code>source</code>. Remove <code>constant</code>. Remove <code>expression</code>. Use <code>constant</code> as <code>defaultValue</code>. Use <code>expression</code> as <code>defaultExpression</code>.</li>
4345
<li>More than one default source in <code>@Mapping</code> annotation defined with quick fixes: Remove <code>defaultValue</code>. Remove <code>defaultExpression</code>.</li>
44-
<li><code>target</code> mapped more than once by <code>@Mapping</code> annotations with quick fixes: Remove annotation and change target property.</li>
46+
<li><code>target</code> mapped more than once by <code>@Mapping</code> and/or <code>@Ignored</code> annotations with quick fixes: Remove annotation and change target property.</li>
4547
<li><code>*</code> used as a source in <code>@Mapping</code> annotations with quick fixes: Replace <code>*</code> with method parameter name.</li>
46-
<li>Unknown reference inspection for <code>source</code> and <code>target</code> in <code>@Mapping</code> and <code>@ValueMapping</code> annotation. </li>
48+
<li>Unknown reference inspection for <code>source</code> and <code>target</code> in <code>@Mapping</code> and <code>@ValueMapping</code> annotation.</li>
49+
<li>Unknown reference inspection for <code>targets</code> and <code>prefix</code> in <code>@Ignored</code> annotation.</li>
4750
<li>Unknown reference inspection for <code>qualifiedByName</code> in <code>@Mapping</code> annotation. </li>
4851
</ul>
4952
</li>
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.codeinsight.references;
7+
8+
import java.util.Map;
9+
import java.util.stream.Stream;
10+
11+
import com.intellij.openapi.util.Pair;
12+
import com.intellij.openapi.util.TextRange;
13+
import com.intellij.psi.PsiClass;
14+
import com.intellij.psi.PsiElement;
15+
import com.intellij.psi.PsiField;
16+
import com.intellij.psi.PsiMethod;
17+
import com.intellij.psi.PsiParameter;
18+
import com.intellij.psi.PsiParameterList;
19+
import com.intellij.psi.PsiRecordComponent;
20+
import com.intellij.psi.PsiSubstitutor;
21+
import com.intellij.psi.PsiType;
22+
import com.intellij.psi.PsiVariable;
23+
import com.intellij.psi.util.PsiUtil;
24+
import org.jetbrains.annotations.NotNull;
25+
import org.jetbrains.annotations.Nullable;
26+
import org.mapstruct.intellij.util.MapStructVersion;
27+
import org.mapstruct.intellij.util.MapstructUtil;
28+
import org.mapstruct.intellij.util.TargetType;
29+
import org.mapstruct.intellij.util.TargetUtils;
30+
31+
import static org.mapstruct.intellij.util.MapstructUtil.asLookup;
32+
import static org.mapstruct.intellij.util.MapstructUtil.findRecordComponent;
33+
import static org.mapstruct.intellij.util.MapstructUtil.isPublicModifiable;
34+
import static org.mapstruct.intellij.util.MapstructUtil.isPublicNonStatic;
35+
import static org.mapstruct.intellij.util.TargetUtils.findAllDefinedMappingTargets;
36+
import static org.mapstruct.intellij.util.TargetUtils.findAllIgnoredTargets;
37+
import static org.mapstruct.intellij.util.TargetUtils.isBuilderEnabled;
38+
import static org.mapstruct.intellij.util.TargetUtils.publicWriteAccessors;
39+
import static org.mapstruct.intellij.util.TargetUtils.resolveBuilderOrSelfClass;
40+
import static org.mapstruct.intellij.util.TypeUtils.firstParameterPsiType;
41+
42+
/**
43+
* Base class for target references ({@link MapstructTargetReference} and {@link MapstructIgnoredTargetReference}).
44+
* Provides shared implementations for resolving the type of target elements.
45+
*
46+
* @author Filip Hrisafov
47+
*/
48+
abstract class BaseTargetReference extends BaseMappingReference {
49+
50+
protected final MapStructVersion mapStructVersion;
51+
52+
BaseTargetReference(@NotNull PsiElement element, @Nullable MapstructBaseReference previousReference,
53+
TextRange rangeInElement, String value) {
54+
super( element, previousReference, rangeInElement, value );
55+
mapStructVersion = MapstructUtil.resolveMapStructProjectVersion( element.getContainingFile()
56+
.getOriginalFile() );
57+
}
58+
59+
@Override
60+
PsiElement resolveInternal(@NotNull String value, @NotNull PsiType psiType) {
61+
return resolveTargetElement( value, psiType, getMappingMethod() );
62+
}
63+
64+
PsiElement resolveTargetElement(@NotNull String value, @NotNull PsiType psiType,
65+
@Nullable PsiMethod mappingMethod) {
66+
boolean builderSupportPresent = mapStructVersion.isBuilderSupported();
67+
Pair<PsiClass, TargetType> pair = resolveBuilderOrSelfClass(
68+
psiType,
69+
builderSupportPresent && isBuilderEnabled( mappingMethod )
70+
);
71+
if ( pair == null ) {
72+
return null;
73+
}
74+
75+
PsiClass psiClass = pair.getFirst();
76+
TargetType targetType = pair.getSecond();
77+
PsiType typeToUse = targetType.type();
78+
79+
PsiRecordComponent recordComponent = findRecordComponent( value, psiClass );
80+
if ( recordComponent != null ) {
81+
return recordComponent;
82+
}
83+
84+
if ( mapStructVersion.isConstructorSupported() && !targetType.builder() ) {
85+
PsiMethod constructor = TargetUtils.resolveMappingConstructor( psiClass );
86+
if ( constructor != null && constructor.hasParameters() ) {
87+
for ( PsiParameter parameter : constructor.getParameterList().getParameters() ) {
88+
if ( value.equals( parameter.getName() ) ) {
89+
return parameter;
90+
}
91+
}
92+
}
93+
}
94+
95+
String capitalizedName = MapstructUtil.capitalize( value );
96+
PsiMethod[] methods = psiClass.findMethodsByName( "set" + capitalizedName, true );
97+
if ( methods.length != 0 && isPublicNonStatic( methods[0] ) ) {
98+
return methods[0];
99+
}
100+
101+
// If there is no such setter we need to check if there is a collection getter
102+
methods = psiClass.findMethodsByName( "get" + capitalizedName, true );
103+
if ( methods.length != 0 && isCollectionGetterWriteAccessor( methods[0] ) ) {
104+
return methods[0];
105+
}
106+
107+
if ( builderSupportPresent ) {
108+
for ( Pair<PsiMethod, PsiSubstitutor> builderPair : psiClass.findMethodsAndTheirSubstitutorsByName(
109+
value,
110+
true
111+
) ) {
112+
PsiMethod method = builderPair.getFirst();
113+
if ( method.getParameterList().getParametersCount() == 1 &&
114+
mapstructUtil.isFluentSetter( method, typeToUse, builderPair.getSecond() ) ) {
115+
return method;
116+
}
117+
}
118+
}
119+
120+
PsiClass selfClass = PsiUtil.resolveClassInType( psiType );
121+
if ( selfClass != null ) {
122+
PsiField field = selfClass.findFieldByName( value, true );
123+
if ( field != null && isPublicModifiable( field ) ) {
124+
return field;
125+
}
126+
}
127+
128+
return null;
129+
}
130+
131+
@NotNull
132+
@Override
133+
Object[] getVariantsInternal(@NotNull PsiType psiType) {
134+
135+
PsiMethod mappingMethod = getMappingMethod();
136+
137+
Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors = publicWriteAccessors(
138+
psiType,
139+
mapStructVersion,
140+
mapstructUtil,
141+
mappingMethod
142+
);
143+
144+
if ( mappingMethod != null ) {
145+
findAllDefinedTargets( mappingMethod ).forEach( accessors::remove );
146+
}
147+
148+
return asLookup(
149+
accessors,
150+
BaseTargetReference::memberPsiType
151+
);
152+
}
153+
154+
protected Stream<String> findAllDefinedTargets(PsiMethod mappingMethod) {
155+
return Stream.concat(
156+
findAllDefinedMappingTargets( mappingMethod, mapStructVersion ),
157+
findAllIgnoredTargets( mappingMethod )
158+
);
159+
}
160+
161+
@Nullable
162+
@Override
163+
PsiType resolvedType() {
164+
PsiElement element = resolve();
165+
if ( element instanceof PsiMethod psiMethod ) {
166+
return firstParameterPsiType( psiMethod );
167+
}
168+
else if ( element instanceof PsiParameter psiParameter ) {
169+
return psiParameter.getType();
170+
}
171+
else if ( element instanceof PsiRecordComponent psiRecordComponent ) {
172+
return psiRecordComponent.getType();
173+
}
174+
else if ( element instanceof PsiField psiField ) {
175+
return psiField.getType();
176+
}
177+
178+
return null;
179+
}
180+
181+
static PsiType memberPsiType(PsiElement psiMember) {
182+
if ( psiMember instanceof PsiMethod psiMemberMethod ) {
183+
return resolveMethodType( psiMemberMethod );
184+
}
185+
else if ( psiMember instanceof PsiVariable psiMemberVariable ) {
186+
return psiMemberVariable.getType();
187+
}
188+
return null;
189+
}
190+
191+
static PsiType resolveMethodType(PsiMethod psiMethod) {
192+
PsiParameter[] psiParameters = psiMethod.getParameterList().getParameters();
193+
if ( psiParameters.length == 0 ) {
194+
return psiMethod.getReturnType();
195+
}
196+
return psiParameters[0].getType();
197+
}
198+
199+
private static boolean isCollectionGetterWriteAccessor(@NotNull PsiMethod method) {
200+
if ( !isPublicNonStatic( method ) ) {
201+
return false;
202+
}
203+
PsiParameterList parameterList = method.getParameterList();
204+
if ( parameterList.getParametersCount() > 0 ) {
205+
return false;
206+
}
207+
return TargetUtils.isMethodReturnTypeAssignableToCollectionOrMap( method );
208+
}
209+
}

0 commit comments

Comments
 (0)