Skip to content

Commit a5adaa5

Browse files
committed
#99 Add support for Java records
1 parent 5c63107 commit a5adaa5

22 files changed

+611
-2
lines changed

build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ task downloadMockJdk8() {
145145
downloadMockJdk(mockJdkLocation, mockJdkDest, "JDK-1.8")
146146
}
147147

148-
test.dependsOn( libs, downloadMockJdk7, downloadMockJdk8 )
148+
task downloadMockJdk11() {
149+
downloadMockJdk(mockJdkLocation, mockJdkDest, "JDK-11")
150+
}
151+
152+
test.dependsOn( libs, downloadMockJdk7, downloadMockJdk8, downloadMockJdk11 )
149153
prepareTestingSandbox.dependsOn( libs )
150154
prepareSandbox.dependsOn( libs )
151155

change-notes.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<h2>1.4.0</h2>
33
<ul>
44
<li>Suppress redundant default parameter value assignment warning for <code>Mapping#constant</code> and <code>Mapping#defaultValue</code></li>
5+
<li>Support for Java records</li>
56
</ul>
67
<h2>1.3.1</h2>
78
<ul>

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.intellij.psi.PsiField;
1313
import com.intellij.psi.PsiMethod;
1414
import com.intellij.psi.PsiParameter;
15+
import com.intellij.psi.PsiRecordComponent;
1516
import com.intellij.psi.PsiReference;
1617
import com.intellij.psi.PsiType;
1718
import com.intellij.psi.PsiVariable;
@@ -24,6 +25,7 @@
2425
import java.util.stream.Stream;
2526

2627
import static org.mapstruct.intellij.util.MapstructUtil.asLookup;
28+
import static org.mapstruct.intellij.util.MapstructUtil.findRecordComponent;
2729
import static org.mapstruct.intellij.util.MapstructUtil.isPublicNonStatic;
2830
import static org.mapstruct.intellij.util.SourceUtils.getParameterType;
2931
import static org.mapstruct.intellij.util.SourceUtils.publicReadAccessors;
@@ -54,6 +56,12 @@ PsiElement resolveInternal(@NotNull String value, @NotNull PsiType psiType) {
5456
if ( psiClass == null ) {
5557
return null;
5658
}
59+
60+
PsiRecordComponent recordComponent = findRecordComponent( value, psiClass );
61+
if ( recordComponent != null ) {
62+
return recordComponent;
63+
}
64+
5765
PsiMethod[] methods = psiClass.findMethodsByName( "get" + MapstructUtil.capitalize( value ), true );
5866

5967
if ( methods.length == 0 ) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.intellij.psi.PsiField;
1717
import com.intellij.psi.PsiMethod;
1818
import com.intellij.psi.PsiParameter;
19+
import com.intellij.psi.PsiRecordComponent;
1920
import com.intellij.psi.PsiReference;
2021
import com.intellij.psi.PsiType;
2122
import com.intellij.psi.PsiVariable;
@@ -28,6 +29,7 @@
2829
import org.mapstruct.intellij.util.TargetUtils;
2930

3031
import static org.mapstruct.intellij.util.MapstructUtil.asLookup;
32+
import static org.mapstruct.intellij.util.MapstructUtil.findRecordComponent;
3133
import static org.mapstruct.intellij.util.MapstructUtil.isPublicModifiable;
3234
import static org.mapstruct.intellij.util.MapstructUtil.isPublicNonStatic;
3335
import static org.mapstruct.intellij.util.TargetUtils.getRelevantType;
@@ -74,6 +76,11 @@ builderSupportPresent && isBuilderEnabled( getMappingMethod() )
7476
TargetType targetType = pair.getSecond();
7577
PsiType typeToUse = targetType.type();
7678

79+
PsiRecordComponent recordComponent = findRecordComponent( value, psiClass );
80+
if ( recordComponent != null ) {
81+
return recordComponent;
82+
}
83+
7784
if ( mapStructVersion.isConstructorSupported() && !targetType.builder() ) {
7885
PsiMethod constructor = TargetUtils.resolveMappingConstructor( psiClass );
7986
if ( constructor != null && constructor.hasParameters() ) {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.intellij.psi.PsiModifier;
3636
import com.intellij.psi.PsiModifierList;
3737
import com.intellij.psi.PsiParameter;
38+
import com.intellij.psi.PsiRecordComponent;
3839
import com.intellij.psi.PsiSubstitutor;
3940
import com.intellij.psi.PsiType;
4041
import com.intellij.psi.impl.PsiClassImplUtil;
@@ -394,6 +395,17 @@ public static Map<String, Pair<PsiField, PsiSubstitutor>> publicFields(PsiClass
394395
return publicFields;
395396
}
396397

398+
public static PsiRecordComponent findRecordComponent(@NotNull String componentName, @NotNull PsiClass psiClass) {
399+
if ( psiClass.isRecord() ) {
400+
for ( PsiRecordComponent recordComponent : psiClass.getRecordComponents() ) {
401+
if ( componentName.equals( recordComponent.getName() ) ) {
402+
return recordComponent;
403+
}
404+
}
405+
}
406+
return null;
407+
}
408+
397409
/**
398410
* Checks if MapStruct can descend into a type. MapStruct, cannot descend into following types:
399411
* <ul>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.intellij.psi.PsiElement;
2020
import com.intellij.psi.PsiMethod;
2121
import com.intellij.psi.PsiParameter;
22+
import com.intellij.psi.PsiRecordComponent;
2223
import com.intellij.psi.PsiSubstitutor;
2324
import com.intellij.psi.PsiType;
2425
import com.intellij.psi.util.PsiUtil;
@@ -121,6 +122,14 @@ public static Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicRead
121122

122123
publicReadAccessors.putAll( publicGetters( psiClass ) );
123124
publicReadAccessors.putAll( publicFields( psiClass ) );
125+
if ( psiClass.isRecord() ) {
126+
for ( PsiRecordComponent recordComponent : psiClass.getRecordComponents() ) {
127+
publicReadAccessors.put(
128+
recordComponent.getName(),
129+
Pair.create( recordComponent, PsiSubstitutor.EMPTY )
130+
);
131+
}
132+
}
124133

125134
return publicReadAccessors;
126135
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.intellij.testFramework.PsiTestUtil;
2222
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
2323
import com.intellij.util.PathUtil;
24+
import com.intellij.util.lang.JavaVersion;
2425
import org.jetbrains.annotations.NotNull;
2526

2627
/**
@@ -59,7 +60,15 @@ protected LightProjectDescriptor getProjectDescriptor() {
5960
return new DefaultLightProjectDescriptor() {
6061
@Override
6162
public Sdk getSdk() {
62-
String compilerOption = languageLevel.toJavaVersion().toString();
63+
JavaVersion version = languageLevel.toJavaVersion();
64+
int mockJdk;
65+
if ( version.feature >= 11 ) {
66+
mockJdk = 11;
67+
}
68+
else {
69+
mockJdk = version.feature;
70+
}
71+
String compilerOption = ( mockJdk < 11 ? "1." : "" ) + mockJdk;
6372
return JavaSdk.getInstance()
6473
.createJdk( "java " + compilerOption, BUILD_MOCK_JDK_DIRECTORY + compilerOption, false );
6574
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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;
7+
8+
import com.intellij.codeInsight.lookup.LookupElement;
9+
import com.intellij.codeInsight.lookup.LookupElementPresentation;
10+
import com.intellij.pom.java.LanguageLevel;
11+
import com.intellij.psi.PsiElement;
12+
import com.intellij.psi.PsiRecordComponent;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.mapstruct.intellij.testutil.TestUtils.createVariable;
16+
17+
/**
18+
* @author Filip Hrisafov
19+
*/
20+
public class MapstructCompletionJdk17TestCase extends MapstructBaseCompletionTestCase {
21+
22+
@Override
23+
protected String getTestDataPath() {
24+
return "testData/mapping";
25+
}
26+
27+
@Override
28+
protected void setUp() throws Exception {
29+
super.setUp();
30+
31+
addDirectoryToProject( "dto" );
32+
}
33+
34+
@Override
35+
protected LanguageLevel getLanguageLevel() {
36+
return LanguageLevel.JDK_17;
37+
}
38+
39+
private void assertCarDtoRecordAutoComplete() {
40+
assertThat( myItems )
41+
.extracting( LookupElement::getLookupString )
42+
.containsExactlyInAnyOrder(
43+
"make",
44+
"seatCount",
45+
"manufacturingYear",
46+
"myDriver",
47+
"passengers",
48+
"price",
49+
"category",
50+
"available"
51+
);
52+
53+
assertThat( myItems )
54+
.extracting( LookupElementPresentation::renderElement )
55+
.usingRecursiveFieldByFieldElementComparator()
56+
.containsExactlyInAnyOrder(
57+
createVariable( "make", "String" ),
58+
createVariable( "seatCount", "int" ),
59+
createVariable( "manufacturingYear", "String" ),
60+
createVariable( "myDriver", "PersonDto" ),
61+
createVariable( "passengers", "List<PersonDto>" ),
62+
createVariable( "price", "Long" ),
63+
createVariable( "category", "String" ),
64+
createVariable( "available", "boolean" )
65+
);
66+
}
67+
68+
private void assertCarRecordAutoComplete() {
69+
assertThat( myItems )
70+
.extracting( LookupElement::getLookupString )
71+
.containsExactlyInAnyOrder(
72+
"make",
73+
"numberOfSeats",
74+
"manufacturingDate",
75+
"driver",
76+
"passengers",
77+
"price",
78+
"category",
79+
"free"
80+
);
81+
82+
assertThat( myItems )
83+
.extracting( LookupElementPresentation::renderElement )
84+
.usingRecursiveFieldByFieldElementComparator()
85+
.containsExactlyInAnyOrder(
86+
createVariable( "make", "String" ),
87+
createVariable( "numberOfSeats", "int" ),
88+
createVariable( "manufacturingDate", "Date" ),
89+
createVariable( "driver", "Person" ),
90+
createVariable( "passengers", "List<Person>" ),
91+
createVariable( "price", "int" ),
92+
createVariable( "category", "Category" ),
93+
createVariable( "free", "boolean" )
94+
);
95+
}
96+
97+
public void testCarMapperReturnTargetCarDtoRecord() {
98+
myFixture.copyFileToProject( "CarDtoRecord.java", "org/example/dto/CarDtoRecord.java" );
99+
configureByTestName();
100+
assertCarDtoRecordAutoComplete();
101+
}
102+
103+
public void testTargetPropertyReferencesRecordComponent() {
104+
myFixture.copyFileToProject( "CarDtoRecord.java", "org/example/dto/CarDtoRecord.java" );
105+
myFixture.configureByFile( "CarMapperReferenceRecordTargetProperty.java" );
106+
PsiElement reference = myFixture.getElementAtCaret();
107+
assertThat( reference )
108+
.isInstanceOfSatisfying( PsiRecordComponent.class, recordComponent -> {
109+
assertThat( recordComponent.getName() ).isEqualTo( "seatCount" );
110+
assertThat( recordComponent.getType() ).isNotNull();
111+
assertThat( recordComponent.getType().getPresentableText() ).isEqualTo( "int" );
112+
} );
113+
}
114+
115+
public void testCarMapperSimpleSingleSourceCarRecord() {
116+
myFixture.copyFileToProject( "CarRecord.java", "org/example/dto/CarRecord.java" );
117+
configureByTestName();
118+
assertCarRecordAutoComplete();
119+
}
120+
121+
public void testSourcePropertyReferencesRecordComponent() {
122+
myFixture.copyFileToProject( "CarRecord.java", "org/example/dto/CarRecord.java" );
123+
myFixture.configureByFile( "CarMapperReferenceRecordSourceProperty.java" );
124+
PsiElement reference = myFixture.getElementAtCaret();
125+
assertThat( reference )
126+
.isInstanceOfSatisfying( PsiRecordComponent.class, recordComponent -> {
127+
assertThat( recordComponent.getName() ).isEqualTo( "numberOfSeats" );
128+
assertThat( recordComponent.getType() ).isNotNull();
129+
assertThat( recordComponent.getType().getPresentableText() ).isEqualTo( "int" );
130+
} );
131+
}
132+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.inspection;
7+
8+
import java.util.List;
9+
10+
import com.intellij.codeInsight.intention.IntentionAction;
11+
import com.intellij.pom.java.LanguageLevel;
12+
import org.jetbrains.annotations.NotNull;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
16+
/**
17+
* @author Filip Hrisafov
18+
*/
19+
public class UnmappedRecordTargetPropertiesInspectionTest extends BaseInspectionTest {
20+
21+
@NotNull
22+
@Override
23+
protected Class<UnmappedTargetPropertiesInspection> getInspection() {
24+
return UnmappedTargetPropertiesInspection.class;
25+
}
26+
27+
@Override
28+
protected void setUp() throws Exception {
29+
super.setUp();
30+
}
31+
32+
@Override
33+
protected LanguageLevel getLanguageLevel() {
34+
return LanguageLevel.JDK_17;
35+
}
36+
37+
public void testUnmappedRecordTargetProperties() {
38+
doTest();
39+
String testName = getTestName( false );
40+
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();
41+
42+
assertThat( allQuickFixes )
43+
.extracting( IntentionAction::getText )
44+
.as( "Intent Text" )
45+
.containsExactly(
46+
"Ignore unmapped target property: 'testName'",
47+
"Add unmapped target property: 'testName'",
48+
"Ignore unmapped target property: 'moreTarget'",
49+
"Add unmapped target property: 'moreTarget'",
50+
"Ignore unmapped target property: 'moreTarget'",
51+
"Add unmapped target property: 'moreTarget'",
52+
"Ignore unmapped target property: 'testName'",
53+
"Add unmapped target property: 'testName'",
54+
"Ignore all unmapped target properties"
55+
);
56+
57+
allQuickFixes.forEach( myFixture::launchAction );
58+
myFixture.checkResultByFile( testName + "_after.java" );
59+
}
60+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.rename;
7+
8+
import com.intellij.pom.java.LanguageLevel;
9+
import org.mapstruct.intellij.MapstructBaseCompletionTestCase;
10+
11+
/**
12+
* @author Filip Hrisafov
13+
*/
14+
public class RenameHandlerJdk17Test extends MapstructBaseCompletionTestCase {
15+
16+
@Override
17+
protected String getTestDataPath() {
18+
return "testData/rename";
19+
}
20+
21+
@Override
22+
protected LanguageLevel getLanguageLevel() {
23+
return LanguageLevel.JDK_17;
24+
}
25+
26+
public void testRenameRecordSourceParameter() {
27+
myFixture.configureByFile( "RenameRecordSourceParameter.java" );
28+
myFixture.renameElementAtCaret( "anotherName" );
29+
myFixture.checkResultByFile( "RenameRecordSourceParameterAfter.java" );
30+
}
31+
32+
public void testRenameRecordTargetParameter() {
33+
myFixture.configureByFile( "RenameRecordTargetParameter.java" );
34+
myFixture.renameElementAtCaret( "newName" );
35+
myFixture.checkResultByFile( "RenameRecordTargetParameterAfter.java" );
36+
}
37+
}

0 commit comments

Comments
 (0)