Skip to content

Commit 2a80757

Browse files
hduelmefiliphr
authored andcommitted
Suggest only missing constants for ValueMapping source
closes #5 closes #103
1 parent 48b5188 commit 2a80757

File tree

5 files changed

+219
-3
lines changed

5 files changed

+219
-3
lines changed

change-notes.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ <h2>1.4.0</h2>
44
<li>Suppress redundant default parameter value assignment warning for <code>Mapping#constant</code> and <code>Mapping#defaultValue</code></li>
55
<li>Support for Java records</li>
66
<li>Support MapStruct explicit <code>Builder#disableBuilder</code> through <code>@MapperConfig</code></li>
7+
<li><code>@ValueMapping</code> source code completion should only suggest unmapped source constants</li>
78
<li>Bug fix: language injections inside expressions when target is field</li>
89
</ul>
910
<h2>1.3.1</h2>

src/main/java/org/mapstruct/intellij/codeinsight/references/ValueMappingSourceReference.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
package org.mapstruct.intellij.codeinsight.references;
77

8+
import java.util.Set;
9+
import java.util.stream.Collectors;
810
import java.util.stream.Stream;
911

1012
import com.intellij.codeInsight.lookup.LookupElement;
@@ -16,8 +18,9 @@
1618
import com.intellij.psi.PsiReference;
1719
import org.jetbrains.annotations.NotNull;
1820
import org.jetbrains.annotations.Nullable;
21+
import org.mapstruct.intellij.util.MapstructUtil;
22+
import org.mapstruct.intellij.util.ValueMappingUtils;
1923

20-
import static org.mapstruct.intellij.util.MapstructUtil.asLookup;
2124
import static org.mapstruct.intellij.util.SourceUtils.getParameterClass;
2225

2326
/**
@@ -59,9 +62,14 @@ Object[] getVariantsInternal(@NotNull PsiMethod mappingMethod) {
5962
return LookupElement.EMPTY_ARRAY;
6063
}
6164

65+
Set<String> alreadyDefinedValues = ValueMappingUtils.findAllDefinedValueMappingSources( mappingMethod )
66+
.collect( Collectors.toSet() );
67+
6268
return Stream.of( sourceClass.getFields() )
63-
.filter( psiField -> psiField instanceof PsiEnumConstant )
64-
.map( psiEnumConstant -> asLookup( (PsiEnumConstant) psiEnumConstant ) )
69+
.filter( PsiEnumConstant.class::isInstance )
70+
.map( PsiEnumConstant.class::cast )
71+
.filter( enumConstant -> !alreadyDefinedValues.contains( enumConstant.getName() ) )
72+
.map( MapstructUtil::asLookup )
6573
.toArray( LookupElement[]::new );
6674
}
6775

src/main/java/org/mapstruct/intellij/util/MapstructAnnotationUtils.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import static com.intellij.codeInsight.intention.AddAnnotationPsiFix.addPhysicalAnnotationTo;
4141
import static com.intellij.codeInsight.intention.AddAnnotationPsiFix.removePhysicalAnnotations;
4242
import static org.mapstruct.intellij.util.MapstructUtil.MAPPING_ANNOTATION_FQN;
43+
import static org.mapstruct.intellij.util.MapstructUtil.VALUE_MAPPING_ANNOTATION_FQN;
4344

4445
/**
4546
* Utils for working with mapstruct annotation.
@@ -269,6 +270,32 @@ private static Stream<PsiAnnotation> findMappingAnnotations(@NotNull PsiMethod m
269270
.filter( MapstructAnnotationUtils::isMappingAnnotation );
270271
}
271272

273+
public static Stream<PsiAnnotation> findAllDefinedValueMappingAnnotations(@NotNull PsiMethod method) {
274+
Stream<PsiAnnotation> valueMappingsAnnotations = Stream.empty();
275+
PsiAnnotation valueMappings = findAnnotation( method, true, MapstructUtil.VALUE_MAPPINGS_ANNOTATION_FQN );
276+
if ( valueMappings != null ) {
277+
PsiNameValuePair mappingsValue = findDeclaredAttribute( valueMappings, null );
278+
if ( mappingsValue != null && mappingsValue.getValue() instanceof PsiArrayInitializerMemberValue ) {
279+
valueMappingsAnnotations = Stream.of( ( (PsiArrayInitializerMemberValue) mappingsValue.getValue() )
280+
.getInitializers() )
281+
.filter( MapstructAnnotationUtils::isValueMappingPsiAnnotation )
282+
.map( memberValue -> (PsiAnnotation) memberValue );
283+
}
284+
else if ( mappingsValue != null && mappingsValue.getValue() instanceof PsiAnnotation ) {
285+
valueMappingsAnnotations = Stream.of( (PsiAnnotation) mappingsValue.getValue() );
286+
}
287+
}
288+
289+
Stream<PsiAnnotation> valueMappingAnnotations = findValueMappingAnnotations( method );
290+
291+
return Stream.concat( valueMappingAnnotations, valueMappingsAnnotations );
292+
}
293+
294+
private static Stream<PsiAnnotation> findValueMappingAnnotations(@NotNull PsiMethod method) {
295+
return Stream.of( method.getModifierList().getAnnotations() )
296+
.filter( MapstructAnnotationUtils::isValueMappingAnnotation );
297+
}
298+
272299
/**
273300
* @param memberValue that needs to be checked
274301
*
@@ -280,6 +307,27 @@ private static boolean isMappingPsiAnnotation(PsiAnnotationMemberValue memberVal
280307
&& isMappingAnnotation( (PsiAnnotation) memberValue );
281308
}
282309

310+
/**
311+
* @param memberValue that needs to be checked
312+
*
313+
* @return {@code true} if the {@code memberValue} is the {@link org.mapstruct.ValueMapping} {@link PsiAnnotation},
314+
* {@code false} otherwise
315+
*/
316+
private static boolean isValueMappingPsiAnnotation(PsiAnnotationMemberValue memberValue) {
317+
return memberValue instanceof PsiAnnotation
318+
&& isValueMappingAnnotation( (PsiAnnotation) memberValue );
319+
}
320+
321+
/**
322+
* @param psiAnnotation that needs to be checked
323+
*
324+
* @return {@code true} if the {@code psiAnnotation} is the {@link org.mapstruct.ValueMapping} annotation,
325+
* {@code false} otherwise
326+
*/
327+
private static boolean isValueMappingAnnotation(PsiAnnotation psiAnnotation) {
328+
return Objects.equals( psiAnnotation.getQualifiedName(), VALUE_MAPPING_ANNOTATION_FQN );
329+
}
330+
283331
/**
284332
* @param psiAnnotation that needs to be checked
285333
*
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.util;
7+
8+
import java.util.Objects;
9+
import java.util.stream.Stream;
10+
11+
import com.intellij.codeInsight.AnnotationUtil;
12+
import com.intellij.psi.PsiMethod;
13+
import org.jetbrains.annotations.NotNull;
14+
15+
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.findAllDefinedValueMappingAnnotations;
16+
17+
/**
18+
* @author Filip Hrisafov
19+
*/
20+
public class ValueMappingUtils {
21+
22+
private ValueMappingUtils() {
23+
}
24+
25+
/**
26+
* Find all defined {@link org.mapstruct.ValueMapping#source()} for the given method
27+
*
28+
* @param method that needs to be checked
29+
*
30+
* @return see description
31+
*/
32+
public static Stream<String> findAllDefinedValueMappingSources(@NotNull PsiMethod method) {
33+
return findAllDefinedValueMappingAnnotations( method )
34+
.map( psiAnnotation -> AnnotationUtil.getDeclaredStringAttributeValue( psiAnnotation, "source" ) )
35+
.filter( Objects::nonNull )
36+
.filter( s -> !s.isEmpty() );
37+
}
38+
}

src/test/java/org/mapstruct/intellij/ValueMappingCompletionTestCase.java

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,33 @@
2121
*/
2222
public class ValueMappingCompletionTestCase extends MapstructBaseCompletionTestCase {
2323

24+
@Language("JAVA")
25+
private static final String SOURCE_VALUE_MAPPING_DYNAMIC = "import org.mapstruct.Mapper;\n" +
26+
"import org.mapstruct.ValueMapping;\n" +
27+
"import org.mapstruct.example.ExternalRoofType;\n" +
28+
"import org.mapstruct.example.RoofType;\n" +
29+
"\n" +
30+
"@Mapper\n" +
31+
"public interface RoofTypeMapper {\n" +
32+
"\n" +
33+
" %s" +
34+
" ExternalRoofType map(RoofType type);\n" +
35+
"}";
36+
37+
@Language("JAVA")
38+
private static final String SOURCE_VALUE_MAPPINGS_DYNAMIC = "import org.mapstruct.Mapper;\n" +
39+
"import org.mapstruct.ValueMapping;\n" +
40+
"import org.mapstruct.ValueMappings;\n" +
41+
"import org.mapstruct.example.ExternalRoofType;\n" +
42+
"import org.mapstruct.example.RoofType;\n" +
43+
"\n" +
44+
"@Mapper\n" +
45+
"public interface RoofTypeMapper {\n" +
46+
"\n" +
47+
" @ValueMappings({\n%s\n})\n" +
48+
" ExternalRoofType map(RoofType type);\n" +
49+
"}";
50+
2451
@Language("JAVA")
2552
private static final String SOURCE_VALUE_MAPPING = "import org.mapstruct.Mapper;\n" +
2653
"import org.mapstruct.ValueMapping;\n" +
@@ -82,6 +109,100 @@ public void testSourceValueMappingVariants() {
82109
);
83110
}
84111

112+
public void testSourceValueMappingWithExisting() {
113+
String source = String.format(
114+
SOURCE_VALUE_MAPPING_DYNAMIC,
115+
"@ValueMapping(source = \"GAMBREL\", target = \"NORMAL\")\n" +
116+
"@ValueMapping(source = \"<caret>%s\", target = \"STANDARD\")\n"
117+
);
118+
myFixture.configureByText( JavaFileType.INSTANCE, source );
119+
complete();
120+
121+
assertThat( myItems )
122+
.extracting( LookupElement::getLookupString )
123+
.containsExactlyInAnyOrder(
124+
"OPEN",
125+
"BOX",
126+
"NORMAL"
127+
);
128+
assertThat( myItems )
129+
.extracting( LookupElementPresentation::renderElement )
130+
.usingElementComparatorIgnoringFields( "myIcon" )
131+
.containsExactlyInAnyOrder(
132+
createField( "OPEN", "RoofType" ),
133+
createField( "BOX", "RoofType" ),
134+
createField( "NORMAL", "RoofType" )
135+
);
136+
}
137+
138+
public void testSourceValueMappingsWithExisting() {
139+
String source = String.format(
140+
SOURCE_VALUE_MAPPINGS_DYNAMIC,
141+
"@ValueMapping(source = \"GAMBREL\", target = \"NORMAL\"),\n" +
142+
"@ValueMapping(source = \"<caret>%s\", target = \"STANDARD\")\n"
143+
);
144+
myFixture.configureByText( JavaFileType.INSTANCE, source );
145+
complete();
146+
147+
assertThat( myItems )
148+
.extracting( LookupElement::getLookupString )
149+
.containsExactlyInAnyOrder(
150+
"OPEN",
151+
"BOX",
152+
"NORMAL"
153+
);
154+
assertThat( myItems )
155+
.extracting( LookupElementPresentation::renderElement )
156+
.usingElementComparatorIgnoringFields( "myIcon" )
157+
.containsExactlyInAnyOrder(
158+
createField( "OPEN", "RoofType" ),
159+
createField( "BOX", "RoofType" ),
160+
createField( "NORMAL", "RoofType" )
161+
);
162+
}
163+
164+
public void testSourceValueMappingAllValuesAlreadyMapped() {
165+
String source = String.format(
166+
SOURCE_VALUE_MAPPING_DYNAMIC,
167+
"@ValueMapping(source = \"OPEN\", target = \"NORMAL\")\n" +
168+
"@ValueMapping(source = \"BOX\", target = \"NORMAL\")\n" +
169+
"@ValueMapping(source = \"GAMBREL\", target = \"NORMAL\")\n" +
170+
"@ValueMapping(source = \"NORMAL\", target = \"NORMAL\")\n" +
171+
"@ValueMapping(source = \"<caret>%s\", target = \"STANDARD\")\n"
172+
);
173+
myFixture.configureByText( JavaFileType.INSTANCE, source );
174+
complete();
175+
176+
assertThat( myItems )
177+
.extracting( LookupElement::getLookupString )
178+
.isEmpty();
179+
assertThat( myItems )
180+
.extracting( LookupElementPresentation::renderElement )
181+
.usingElementComparatorIgnoringFields( "myIcon" )
182+
.isEmpty();
183+
}
184+
185+
public void testSourceValueMappingsAllValuesAlreadyMapped() {
186+
String source = String.format(
187+
SOURCE_VALUE_MAPPINGS_DYNAMIC,
188+
"@ValueMapping(source = \"OPEN\", target = \"NORMAL\"),\n" +
189+
"@ValueMapping(source = \"BOX\", target = \"NORMAL\"),\n" +
190+
"@ValueMapping(source = \"GAMBREL\", target = \"NORMAL\"),\n" +
191+
"@ValueMapping(source = \"NORMAL\", target = \"NORMAL\"),\n" +
192+
"@ValueMapping(source = \"<caret>%s\", target = \"STANDARD\")\n"
193+
);
194+
myFixture.configureByText( JavaFileType.INSTANCE, source );
195+
complete();
196+
197+
assertThat( myItems )
198+
.extracting( LookupElement::getLookupString )
199+
.isEmpty();
200+
assertThat( myItems )
201+
.extracting( LookupElementPresentation::renderElement )
202+
.usingElementComparatorIgnoringFields( "myIcon" )
203+
.isEmpty();
204+
}
205+
85206
public void testSourceValueMappingResolveToEnum() {
86207
myFixture.configureByText( JavaFileType.INSTANCE, String.format( SOURCE_VALUE_MAPPING, "NORMAL" ) );
87208

0 commit comments

Comments
 (0)