Skip to content

Commit ded1968

Browse files
committed
#88 Fix crash when injecting Java code for expression
This commit fixes the crash that is happening when using a constant for the target
1 parent 32da403 commit ded1968

File tree

11 files changed

+142
-17
lines changed

11 files changed

+142
-17
lines changed

change-notes.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<h2>1.3.1</h2>
33
<ul>
44
<li>Improve language injections, especially for generics</li>
5+
<li>Bug fix: Crash in case using static constant in target field of <code>@Mapping</code> annotation</li>
56
</ul>
67
<h2>1.3.0</h2>
78
<ul>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import com.intellij.psi.PsiReferenceBase;
1212
import org.jetbrains.annotations.NotNull;
1313
import org.jetbrains.annotations.Nullable;
14-
import org.jetbrains.uast.ULiteralExpression;
14+
import org.jetbrains.uast.UExpression;
1515
import org.jetbrains.uast.UMethod;
1616
import org.jetbrains.uast.UastContextKt;
1717
import org.jetbrains.uast.UastUtils;
@@ -44,7 +44,7 @@ abstract class BaseReference extends PsiReferenceBase<PsiElement> {
4444
@Nullable
4545
PsiMethod getMappingMethod() {
4646
PsiElement element = getElement();
47-
ULiteralExpression expression = UastContextKt.toUElement( element, ULiteralExpression.class );
47+
UExpression expression = UastContextKt.toUElement( element, UExpression.class );
4848
if ( expression != null ) {
4949
UMethod parent = UastUtils.getParentOfType( expression, UMethod.class );
5050
if ( parent != null ) {

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

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55
*/
66
package org.mapstruct.intellij.codeinsight.references;
77

8+
import java.util.function.Function;
9+
810
import com.intellij.codeInsight.lookup.LookupElement;
911
import com.intellij.openapi.util.TextRange;
1012
import com.intellij.psi.ElementManipulator;
1113
import com.intellij.psi.ElementManipulators;
1214
import com.intellij.psi.PsiElement;
15+
import com.intellij.psi.PsiLiteralExpression;
1316
import com.intellij.psi.PsiMethod;
1417
import com.intellij.psi.PsiReference;
18+
import com.intellij.psi.PsiReferenceExpression;
1519
import com.intellij.psi.PsiType;
20+
import com.intellij.psi.util.PsiTreeUtil;
1621
import com.intellij.util.IncorrectOperationException;
1722
import org.jetbrains.annotations.NotNull;
1823
import org.jetbrains.annotations.Nullable;
@@ -28,6 +33,7 @@
2833
abstract class MapstructBaseReference extends BaseReference {
2934

3035
private final MapstructBaseReference previous;
36+
private final String value;
3137

3238
/**
3339
* Create a reference.
@@ -37,9 +43,19 @@ abstract class MapstructBaseReference extends BaseReference {
3743
* @param rangeInElement the range in the {@code element} for which this reference is valid
3844
*/
3945
MapstructBaseReference(@NotNull PsiElement element,
40-
@Nullable MapstructBaseReference previous, TextRange rangeInElement) {
46+
@Nullable MapstructBaseReference previous, TextRange rangeInElement, String value) {
4147
super( element, rangeInElement );
4248
this.previous = previous;
49+
this.value = value;
50+
}
51+
52+
@Override
53+
@NotNull
54+
public String getValue() {
55+
if ( value != null ) {
56+
return value;
57+
}
58+
return super.getValue();
4359
}
4460

4561
@Nullable
@@ -152,30 +168,74 @@ private static ElementManipulator<PsiElement> getManipulator(PsiElement psiEleme
152168
*/
153169
static <T extends MapstructBaseReference> PsiReference[] create(PsiElement psiElement,
154170
ReferenceCreator<T> creator, boolean supportsNested) {
155-
ElementManipulator<PsiElement> manipulator = getManipulator( psiElement );
156-
TextRange rangeInElement = manipulator.getRangeInElement( psiElement );
157-
String targetValue = rangeInElement.substring( psiElement.getText() );
171+
String targetValue;
172+
Function<String, TextRange> rangeCreator;
173+
if ( psiElement instanceof PsiReferenceExpression ) {
174+
PsiElement resolvedPsiElement = ( (PsiReferenceExpression) psiElement ).resolve();
175+
176+
PsiLiteralExpression expression = PsiTreeUtil.findChildOfType(
177+
resolvedPsiElement,
178+
PsiLiteralExpression.class
179+
);
180+
181+
if ( expression == null ) {
182+
return PsiReference.EMPTY_ARRAY;
183+
}
184+
185+
ElementManipulator<PsiElement> manipulator = getManipulator( expression );
186+
TextRange rangeInElement = manipulator.getRangeInElement( expression );
187+
targetValue = rangeInElement.substring( expression.getText() );
188+
rangeCreator = part -> TextRange.EMPTY_RANGE;
189+
190+
}
191+
else {
192+
ElementManipulator<PsiElement> manipulator = getManipulator( psiElement );
193+
TextRange rangeInElement = manipulator.getRangeInElement( psiElement );
194+
targetValue = rangeInElement.substring( psiElement.getText() );
195+
196+
rangeCreator = new RangeCreator( rangeInElement.getStartOffset() );
197+
198+
}
199+
158200
String[] parts = supportsNested ? targetValue.split( "\\." ) : new String[] { targetValue };
159201
if ( parts.length == 0 ) {
160202
return PsiReference.EMPTY_ARRAY;
161203
}
162-
int nextStart = rangeInElement.getStartOffset();
163204

164205
PsiReference[] references = new PsiReference[parts.length];
165206
T lastReference = null;
166207

167208
for ( int i = 0; i < parts.length; i++ ) {
168209
String part = parts[i];
210+
lastReference = creator.create( psiElement, lastReference, rangeCreator.apply( part ), part );
211+
references[i] = lastReference;
212+
}
213+
214+
return references;
215+
}
216+
217+
private static class RangeCreator implements Function<String, TextRange> {
218+
219+
private int nextStart;
220+
private boolean first = true;
221+
222+
private RangeCreator(int nextStart) {
223+
this.nextStart = nextStart;
224+
}
225+
226+
@Override
227+
public TextRange apply(String part) {
169228
int endOffset = nextStart + part.length();
170-
if ( i != 0 ) {
229+
if ( !first ) {
171230
nextStart++;
172231
endOffset++;
173232
}
174-
lastReference = creator.create( psiElement, lastReference, new TextRange( nextStart, endOffset ) );
233+
else {
234+
first = false;
235+
}
236+
TextRange range = new TextRange( nextStart, endOffset );
175237
nextStart = endOffset;
176-
references[i] = lastReference;
238+
return range;
177239
}
178-
179-
return references;
180240
}
181241
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ class MapstructSourceReference extends MapstructBaseReference {
4141
* @param element the element that the reference belongs to
4242
* @param previousReference the previous reference if there is one (in nested properties for example)
4343
* @param rangeInElement the range that the reference represent in the {@code element}
44+
* @param value the matched value (useful when {@code rangeInElement} is empty)
4445
*/
4546
private MapstructSourceReference(PsiElement element, MapstructSourceReference previousReference,
46-
TextRange rangeInElement) {
47-
super( element, previousReference, rangeInElement );
47+
TextRange rangeInElement, String value) {
48+
super( element, previousReference, rangeInElement, value );
4849
}
4950

5051
@Override

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ class MapstructTargetReference extends MapstructBaseReference {
5050
* @param element the element that the reference belongs to
5151
* @param previousReference the previous reference if there is one (in nested properties for example)
5252
* @param rangeInElement the range that the reference represent in the {@code element}
53+
* @param value the matched value (useful when {@code rangeInElement} is empty)
5354
*/
5455
private MapstructTargetReference(PsiElement element, MapstructTargetReference previousReference,
55-
TextRange rangeInElement) {
56-
super( element, previousReference, rangeInElement );
56+
TextRange rangeInElement, String value) {
57+
super( element, previousReference, rangeInElement, value );
5758
mapStructVersion = MapstructUtil.resolveMapStructProjectVersion( element.getContainingFile()
5859
.getOriginalFile() );
5960
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ interface ReferenceCreator<T extends MapstructBaseReference> {
2424
* @param psiElement the element that the reference belongs to
2525
* @param previousReference the previous reference if there is one (in nested properties for example)
2626
* @param rangeInElement the range that the reference represent in the {@code psiLiteral}
27+
* @param value the value that was matched
2728
*
2829
* @return a new reference created from the provided parameters
2930
*/
30-
T create(PsiElement psiElement, T previousReference, TextRange rangeInElement);
31+
T create(PsiElement psiElement, T previousReference, TextRange rangeInElement, String value);
3132
}

src/test/java/org/mapstruct/intellij/expression/JavaExpressionInjectionTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,41 @@ protected void withClassMapper(String attribute) {
567567
"}" );
568568
}
569569

570+
public void testExpressionWithTargetUsingStaticString() {
571+
withTargetUsingStaticString( "expression" );
572+
withTargetUsingStaticString( "defaultExpression" );
573+
}
574+
575+
protected void withTargetUsingStaticString(String attribute) {
576+
configureMapperByText(
577+
"import org.mapstruct.Mapper;\n" +
578+
"import org.mapstruct.Mapping;\n" +
579+
"import org.example.dto.CarDto;\n" +
580+
"import org.example.dto.Car;\n" +
581+
"import org.example.dto.Utils;\n" +
582+
"\n" +
583+
"@Mapper\n" +
584+
"public abstract class CarMapper {\n" +
585+
"\n" +
586+
" @Mapping( target = Utils.MANUFACTURING_YEAR, " + attribute + " = \"java(car.<caret>)\")\n" +
587+
" CarDto carToCarDto(Car car);\n" +
588+
"}" );
589+
590+
assertJavaFragment( "import CarMapper;\n" +
591+
"import org.example.dto.Car;\n" +
592+
"\n" +
593+
"@SuppressWarnings(\"unused\")\n" +
594+
"abstract class CarMapperImpl\n" +
595+
" extends CarMapper {\n" +
596+
"\n" +
597+
" void __test__(\n" +
598+
" Car car\n" +
599+
" ) {\n" +
600+
" String __target__ = car.;\n" +
601+
" }\n" +
602+
"}" );
603+
}
604+
570605
private PsiFile configureMapperByText(@Language("java") String text) {
571606
return myFixture.configureByText( JavaFileType.INSTANCE, text );
572607
}

src/test/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspectionTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public void testUnmappedTargetProperties() {
6565
"Ignore unmapped target property: 'testName'",
6666
"Add unmapped target property: 'testName'",
6767
"Ignore unmapped target property: 'moreTarget'",
68+
"Add unmapped target property: 'moreTarget'",
69+
"Ignore unmapped target property: 'moreTarget'",
6870
"Add unmapped target property: 'moreTarget'"
6971
);
7072

testData/expression/dto/Utils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@
77

88
public class Utils {
99

10+
public static final String MANUFACTURING_YEAR = "manufacturingYear";
11+
1012
}

testData/inspection/UnmappedTargetProperties.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,13 @@ interface UpdateMapper {
8080
interface MultiSourceUpdateMapper {
8181

8282
void <warning descr="Unmapped target property: moreTarget">update</warning>(@MappingTarget Target moreTarget, Source source, String testName, @org.mapstruct.Context String matching);
83+
}
84+
85+
@Mapper
86+
interface SingleMappingConstantReferenceMapper {
87+
88+
String TEST_NAME = "testName";
89+
90+
@Mapping(target = TEST_NAME, source = "name")
91+
Target <warning descr="Unmapped target property: moreTarget">map</warning>(Source source);
8392
}

0 commit comments

Comments
 (0)